googleapis/mcp-toolbox · error
path %q resolves through a symbolic link to a target outside
Error message
path %q resolves through a symbolic link to a target outside configured directory %q
What it means
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.
Source
Thrown at internal/tools/cloudstorage/cloudstoragecommon/paths.go:159
return "", fmt.Errorf("path %q escapes configured directory %q", rel, cleanDir)
}
// Repeat the check against the real targets. A symlink under cleanDir can
// point anywhere, so the name-level check above proves nothing on its own.
resolvedDir, err := ResolveSymlinks(cleanDir)
if err != nil {
return "", fmt.Errorf("directory %q cannot be resolved: %w", cleanDir, err)
}
resolvedDest, err := ResolveSymlinks(cleanDest)
if err != nil {
return "", fmt.Errorf("path %q cannot be resolved: %w", rel, err)
}
out, err = escapes(resolvedDir, resolvedDest)
if err != nil {
return "", fmt.Errorf("path %q cannot be resolved within %q: %w", rel, cleanDir, err)
}
if out {
return "", fmt.Errorf("path %q resolves through a symbolic link to a target outside configured directory %q", rel, cleanDir)
}
return cleanDest, nil
}
View on GitHub (pinned to 8cc6e09de2)
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
Example fix
// before: ln -s /etc/passwd uploads/escape && copy to uploads/escape // after rm uploads/escape touch uploads/realfile.txt # use a real path inside the allowed directory
Defensive patterns
Strategy: validation
Validate before calling
func resolvesInside(base, p string) (bool, error) {
r, err := filepath.EvalSymlinks(p); if err != nil { return false, err }
rb, err := filepath.EvalSymlinks(base); if err != nil { return false, err }
rel, err := filepath.Rel(rb, r); if err != nil { return false, err }
return rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)), nil
} Type guard
func isRealPathInside(base, p string) bool {
escaped, err := resolvesInside(base, p)
return err == nil && !escaped
} Try / catch
out, err := ResolveWithinDir(baseDir, userPath)
if err != nil && strings.Contains(err.Error(), "resolves through a symbolic link") {
http.Error(w, "destination escapes allowed directory", http.StatusBadRequest)
return
}
if err != nil { return err } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- local path %q resolves through a symbolic link to a target o
- path %q cannot be resolved: %w
- local path %q is not under any allowed local roots for sourc
- local path %q cannot be resolved for source %q: %w
- path cannot contain dot segments (..)
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/686504f465d1f820.
Report an issue: GitHub.