{"record":{"id":"686504f465d1f820","repo":"googleapis/mcp-toolbox","slug":"path-q-resolves-through-a-symbolic-link-to-a-targ","errorCode":null,"errorMessage":"path %q resolves through a symbolic link to a target outside configured directory %q","messagePattern":"path %q resolves through a symbolic link to a target outside configured directory %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/tools/cloudstorage/cloudstoragecommon/paths.go","lineNumber":159,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"path %q escapes configured directory %q\", rel, cleanDir)\n\t}\n\n\t// Repeat the check against the real targets. A symlink under cleanDir can\n\t// point anywhere, so the name-level check above proves nothing on its own.\n\tresolvedDir, err := ResolveSymlinks(cleanDir)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"directory %q cannot be resolved: %w\", cleanDir, err)\n\t}\n\tresolvedDest, err := ResolveSymlinks(cleanDest)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"path %q cannot be resolved: %w\", rel, err)\n\t}\n\tout, err = escapes(resolvedDir, resolvedDest)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"path %q cannot be resolved within %q: %w\", rel, cleanDir, err)\n\t}\n\tif out {\n\t\treturn \"\", fmt.Errorf(\"path %q resolves through a symbolic link to a target outside configured directory %q\", rel, cleanDir)\n\t}\n\treturn cleanDest, nil\n}\n","sourceCodeStart":141,"sourceCodeEnd":163,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/cloudstorage/cloudstoragecommon/paths.go#L141-L163","documentation":"This is the deliberate security rejection of ResolveWithinDir: after symlink resolution, the destination path points outside the configured allowed directory. The tool refuses to operate to prevent symlink-based path traversal (writing or reading outside the sandboxed directory). It is not a bug — it is the path-traversal guard firing.","triggerScenarios":"A user-supplied 'dest' path contains (or traverses through) a symlink whose target resolves outside the configured directory, so escapes() returns true and the tool rejects the operation.","commonSituations":"Attacker or careless user places a symlink inside the upload dir pointing to /etc or another bucket-visible path; Docker/K8s volume mounts that create symlinks outside the allowed root; users using '..' components combined with symlinks.","solutions":["Choose a destination path that does not traverse symlinks outside the allowed directory","Replace the symlink inside the allowed directory with a real file/directory","If the escape is intentional, reconfigure the allowed directory in the source to include the real target","Audit the path with `readlink -f <path>` to see where it actually resolves"],"exampleFix":"// before: ln -s /etc/passwd uploads/escape && copy to uploads/escape\n// after\nrm uploads/escape\ntouch uploads/realfile.txt  # use a real path inside the allowed directory","handlingStrategy":"validation","validationCode":"func resolvesInside(base, p string) (bool, error) {\n    r, err := filepath.EvalSymlinks(p); if err != nil { return false, err }\n    rb, err := filepath.EvalSymlinks(base); if err != nil { return false, err }\n    rel, err := filepath.Rel(rb, r); if err != nil { return false, err }\n    return rel == \"..\" || strings.HasPrefix(rel, \"..\"+string(os.PathSeparator)), nil\n}","typeGuard":"func isRealPathInside(base, p string) bool {\n    escaped, err := resolvesInside(base, p)\n    return err == nil && !escaped\n}","tryCatchPattern":"out, err := ResolveWithinDir(baseDir, userPath)\nif err != nil && strings.Contains(err.Error(), \"resolves through a symbolic link\") {\n    http.Error(w, \"destination escapes allowed directory\", http.StatusBadRequest)\n    return\n}\nif err != nil { return err }","preventionTips":["Reject or replace symlinks in user-writable directories","readlink -f audit paths in shared volumes before trusting them","Run uploads with the allowed directory on a dedicated, symlink-free volume","Treat this error as a security signal, not a bug — investigate who created the link"],"tags":["security","symlink","path-traversal","filesystem"],"backgroundTag":"path-traversal-detected","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}