{"record":{"id":"b6c3771a9c75e192","repo":"docker/compose","slug":"git-subdirectory-must-be-relative-got-s","errorCode":null,"errorMessage":"git subdirectory must be relative, got: %s","messagePattern":"git subdirectory must be relative, got: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/remote/git.go","lineNumber":144,"sourceCode":"\t}\n\tif stat.IsDir() {\n\t\tlocal, err = findFile(cli.DefaultFileNames, local)\n\t}\n\treturn local, err\n}\n\nfunc (g gitRemoteLoader) Dir(path string) string {\n\treturn g.known[path]\n}\n\n// validateGitSubDir ensures a subdirectory path is contained within the base directory\n// and doesn't escape via path traversal. Unlike validatePathInBase for OCI artifacts,\n// this allows nested directories but prevents traversal outside the base.\nfunc validateGitSubDir(base, subDir string) error {\n\tcleanSubDir := filepath.Clean(subDir)\n\n\tif filepath.IsAbs(cleanSubDir) {\n\t\treturn fmt.Errorf(\"git subdirectory must be relative, got: %s\", subDir)\n\t}\n\n\tif cleanSubDir == \"..\" || strings.HasPrefix(cleanSubDir, \"../\") || strings.HasPrefix(cleanSubDir, \"..\\\\\") {\n\t\treturn fmt.Errorf(\"git subdirectory path traversal detected: %s\", subDir)\n\t}\n\n\tif len(cleanSubDir) >= 2 && cleanSubDir[1] == ':' {\n\t\treturn fmt.Errorf(\"git subdirectory must be relative, got: %s\", subDir)\n\t}\n\n\ttargetPath := filepath.Join(base, cleanSubDir)\n\tcleanBase := filepath.Clean(base)\n\tcleanTarget := filepath.Clean(targetPath)\n\n\t// Ensure the target starts with the base path\n\trelPath, err := filepath.Rel(cleanBase, cleanTarget)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid git subdirectory path: %w\", err)","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/pkg/remote/git.go#L126-L162","documentation":"`validateGitSubDir` guards the sub-directory fragment of a git remote include. This variant fires when the cleaned sub-path is absolute — e.g. starts with `/` on Unix. The loader refuses absolute sub-paths because they cannot be joined onto the checkout root safely.","triggerScenarios":"An include path like `git://host/repo.git#main:/etc/config` where the fragment after the ref parses as an absolute path; also a Windows path like `\\server\\share` cleaning to an absolute form.","commonSituations":"Users writing sub-paths with a leading slash out of habit; converting a local `include: /abs/path` to a git include and keeping the leading slash; copy-paste from shell examples with absolute paths.","solutions":["Remove the leading slash: use `git://host/repo.git#main:subdir` not `git://host/repo.git#main:/subdir`","Keep the fragment relative to the repository root"],"exampleFix":"# before\ninclude:\n  - path: git://github.com/org/repo.git#main:/compose/prod\n\n# after\ninclude:\n  - path: git://github.com/org/repo.git#main:compose/prod","handlingStrategy":"validation","validationCode":"# keep include fragments relative (no leading slash)\npython3 - <<'EOF'\nimport sys\nfor arg in sys.argv[1:]:\n    frag = arg.split('#', 1)[1] if '#' in arg else ''\n    sub = frag.split(':', 1)[1] if ':' in frag else ''\n    if sub.startswith(('/', '\\\\')):\n        sys.exit(f\"absolute include fragment: {arg}\")\nEOF \"$@\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Author git include fragments relative to the repo root, mirror-image of local relative paths","Code-review include paths for leading slashes after ref (`#main:/sub` is invalid)"],"tags":["compose","git-remote","path-validation"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}