googleapis/mcp-toolbox · error
local path %q is not under any allowed local roots for sourc
Error message
local path %q is not under any allowed local roots for source %q
What it means
For local file transfers (download/upload), the CloudStorage source enforces 'allowedLocalRoots'. The requested path must sit under at least one allowed root as written (before symlink resolution); otherwise it is rejected. This is the first of two checks — a second pass rejects paths that escape the roots via symlinks after resolution.
Source
Thrown at internal/sources/cloudstorage/cloudstorage.go:133
// still matches.
func (s *Source) validateLocalPath(p string) error {
clean, err := cloudstoragecommon.ValidateLocalPath(p)
if err != nil {
return err
}
if len(s.AllowedLocalRoots) == 0 {
return nil
}
nameMatched := false
for _, root := range s.AllowedLocalRoots {
if isUnderRoot(clean, root) {
nameMatched = true
break
}
}
if !nameMatched {
return fmt.Errorf("local path %q is not under any allowed local roots for source %q", p, s.Name)
}
resolved, err := cloudstoragecommon.ResolveSymlinks(clean)
if err != nil {
return fmt.Errorf("local path %q cannot be resolved for source %q: %w", p, s.Name, err)
}
for _, root := range s.AllowedLocalRoots {
// A root we cannot resolve authorizes nothing; skip it rather than
// falling back to the name-level match we already passed.
resolvedRoot, err := cloudstoragecommon.ResolveSymlinks(root)
if err != nil {
continue
}
if isUnderRoot(resolved, resolvedRoot) {
return nil
}
}
return fmt.Errorf("local path %q resolves through a symbolic link to a target outside the allowed local roots for source %q", p, s.Name)View on GitHub (pinned to 8cc6e09de2)
Solutions
- Add the intended directory to 'allowedLocalRoots' in the cloud-storage source config.
- Request operations with paths directly under an already-allowed root.
- If the root itself is a symlink (e.g. /tmp on macOS), add the resolved real path (e.g. /private/tmp) to the roots list.
- Verify with filepath.Clean-equivalent logic that the path does not traverse above the root via '..'.
Example fix
# before allowedLocalRoots: ["/home/user/data"] # request: /tmp/out.csv -> rejected # after allowedLocalRoots: ["/home/user/data", "/tmp"]
Defensive patterns
Strategy: validation
Validate before calling
roots := []string{"/home/user/data"}
p := filepath.Clean(localPath)
for _, r := range roots {
if strings.HasPrefix(p, filepath.Clean(r)+string(os.PathSeparator)) || p == filepath.Clean(r) {
return nil // path is under an allowed root
}
}
return errors.New("path outside allowedLocalRoots") Prevention
- Add every directory agents need to allowedLocalRoots up front
- Always request absolute, cleaned paths inside a root
- On macOS, account for /tmp -> /private/tmp when listing roots
- Remember symlink resolution is checked separately; verify targets resolve inside roots
When it happens
Trigger: Calling DownloadObject or UploadObject with a local path outside every configured allowedLocalRoot (while roots are configured); also hit by internal symlink-escape tests. Absolute paths pointing elsewhere, sibling directories, or ../ traversals out of the root all trigger this.
Common situations: Agent tries to write downloaded objects to /tmp or the workspace when only ~/data is allowlisted; config lists roots on a different machine/container layout; macOS /tmp-vs-/private/tmp symlink differences causing the path-level match to fail before resolution.
Related errors
- URL host must be an allowed FHIR host, got %q
- bucket %q is not allowed by source %q configuration
- path %q cannot be resolved: %w
- path %q resolves through a symbolic link to a target outside
- ErrReadSizeLimitExceeded
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/452e64daf2cd74dc.
Report an issue: GitHub.