{"record":{"id":"9613a10d904ebaf1","repo":"docker/compose","slug":"git-subdirectory-path-traversal-detected-s","errorCode":null,"errorMessage":"git subdirectory path traversal detected: %s","messagePattern":"git subdirectory path traversal detected: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/remote/git.go","lineNumber":148,"sourceCode":"\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)\n\t}\n\n\tif relPath == \"..\" || strings.HasPrefix(relPath, \"../\") || strings.HasPrefix(relPath, \"..\\\\\") {\n\t\treturn fmt.Errorf(\"git subdirectory escapes base directory: %s\", subDir)","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/pkg/remote/git.go#L130-L166","documentation":"The path-traversal guard of `validateGitSubDir`: after cleaning, the sub-directory equals `..` or starts with `../` (or `..\\`), meaning it would escape the checked-out repository root. Compose rejects it to prevent includes from reading files outside the git cache directory.","triggerScenarios":"An include fragment crafted (or typo'd) as `..`, `../other-project`, `..\\other`, or a path like `subdir/../../..` that survives cleaning with a `..` prefix.","commonSituations":"Attempting to include a sibling checkout via relative `..` paths; malformed include strings from templating; the error is also the expected response to malicious path-traversal payloads.","solutions":["Point the include at a path inside the repository: `git://host/repo.git#ref:valid/subdir`","If you need files from another repo, declare a second include for that repo instead of traversing with `..`"],"exampleFix":"# before\ninclude:\n  - path: git://github.com/org/repo.git#main:../shared\n\n# after\ninclude:\n  - path: git://github.com/org/repo.git#main:shared\n  - path: git://github.com/org/shared.git#main","handlingStrategy":"validation","validationCode":"# reject traversal fragments before invoking compose\npython3 - <<'EOF'\nimport sys, posixpath\nfor arg in sys.argv[1:]:\n    if '#' not in arg: continue\n    sub = arg.split('#',1)[1].split(':',1)[1] if ':' in arg.split('#',1)[1] else ''\n    c = posixpath.normpath(sub)\n    if c == '..' or c.startswith('../') or c.startswith('..\\\\'):\n        sys.exit(f\"traversal in include fragment: {arg}\")\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never hand-build include fragments from untrusted input without normalizing","Reference sibling repos as separate includes instead of `..` paths"],"tags":["compose","git-remote","path-traversal","security"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}