docker/compose · error
git subdirectory escapes base directory: %s
Error message
git subdirectory escapes base directory: %s
What it means
Final containment check of `validateGitSubDir`: even after cleaning and joining, the resolved target's path relative to the base escapes upward (`..` prefix), so the sub-directory would resolve outside the git checkout. Compose blocks it to keep includes confined to the cloned repo.
Source
Thrown at pkg/remote/git.go:166
return fmt.Errorf("git subdirectory path traversal detected: %s", subDir)
}
if len(cleanSubDir) >= 2 && cleanSubDir[1] == ':' {
return fmt.Errorf("git subdirectory must be relative, got: %s", subDir)
}
targetPath := filepath.Join(base, cleanSubDir)
cleanBase := filepath.Clean(base)
cleanTarget := filepath.Clean(targetPath)
// Ensure the target starts with the base path
relPath, err := filepath.Rel(cleanBase, cleanTarget)
if err != nil {
return fmt.Errorf("invalid git subdirectory path: %w", err)
}
if relPath == ".." || strings.HasPrefix(relPath, "../") || strings.HasPrefix(relPath, "..\\") {
return fmt.Errorf("git subdirectory escapes base directory: %s", subDir)
}
return nil
}
func (g gitRemoteLoader) resolveGitRef(ctx context.Context, path string, ref *gitutil.GitRef) error {
if !commitSHA.MatchString(ref.Ref) {
cmd := exec.CommandContext(ctx, "git", "ls-remote", "--exit-code", ref.Remote, ref.Ref)
cmd.Env = g.gitCommandEnv()
out, err := cmd.CombinedOutput()
if err != nil {
if cmd.ProcessState.ExitCode() == 2 {
return fmt.Errorf("repository does not contain ref %s, output: %q: %w", path, string(out), err)
}
return fmt.Errorf("failed to access repository at %s:\n %s", ref.Remote, out)
}
if len(out) < 40 {
return fmt.Errorf("unexpected git command output: %q", string(out))View on GitHub (pinned to ddc4b044b6)
Solutions
- Simplify the fragment to a direct sub-path within the repository
- Treat hitting this error as a red flag if the path came from an untrusted source (CI variable, remote config)
Example fix
# before include: - path: git://github.com/org/repo.git#main:sub/../../escape # after include: - path: git://github.com/org/repo.git#main:sub
Defensive patterns
Strategy: validation
Validate before calling
# normalize then assert containment, mirroring the library check
python3 - <<'EOF'
import sys, posixpath
base = '/checkout/root'
for sub in sys.argv[1:]:
rel = posixpath.relpath(posixpath.normpath(posixpath.join(base, sub)), posixpath.normpath(base))
if rel == '..' or rel.startswith('../'):
sys.exit(f"fragment escapes base: {sub}")
EOF Prevention
- Treat any 'escapes base directory' hit on untrusted input as a security signal
- Simplify include fragments; deep relative gymnastics are both fragile and suspicious
When it happens
Trigger: Sub-paths like `a/../../escape` where intermediate components rejoin under base per cleaning rules but `filepath.Join` + `Rel` still yields an escaping relative path; symlink-free lexical traversal that the earlier prefix checks didn't catch.
Common situations: Maliciously crafted include fragments attempting cache escape; over-clever relative paths that try to reference repos cached next to each other.
Related errors
- git subdirectory path traversal detected: %s
- COMPOSE_EXPERIMENTAL_GIT_REMOTE environment variable expects
- git remote resource is disabled by %q
- initializing remote resource cache: %w
- git subdirectory must be relative, got: %s
AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15).
Data as JSON: /api/errors/fb83cd7fad431e08.
Report an issue: GitHub.