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

  1. Add the intended directory to 'allowedLocalRoots' in the cloud-storage source config.
  2. Request operations with paths directly under an already-allowed root.
  3. 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.
  4. 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

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


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/452e64daf2cd74dc. Report an issue: GitHub.