CherryHQ/cherry-studio · error · Error
Access denied: Path is outside the configured workspace root
Error message
Access denied: Path is outside the configured workspace root: ${requestedPath} What it means
Thrown by validatePath (types.ts:85) when, after expanding ~, resolving to absolute, and running realpath/nearest-existing-ancestor resolution on both the root and the target, isPathWithinRoot returns false. This is the security boundary of the filesystem MCP server: it prevents path traversal (../), symlink escapes, and absolute-path hijacks from reaching read/write/edit/grep tools. The original requestedPath (pre-resolution) is included in the message.
Source
Thrown at src/main/ai/mcp/servers/filesystem/types.ts:94
if (normalizedTargetPath === normalizedRootPath) {
return true
}
const relativePath = path.relative(normalizedRootPath, normalizedTargetPath)
return relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath)
}
// Security validation
export async function validatePath(requestedPath: string, baseDir?: string): Promise<string> {
const expandedPath = expandHome(requestedPath)
const root = expandHome(baseDir ?? process.cwd())
const absolute = path.isAbsolute(expandedPath) ? path.resolve(expandedPath) : path.resolve(root, expandedPath)
const resolvedRoot = await resolveRealOrNearestExistingPath(path.resolve(root))
const resolvedPath = await resolveRealOrNearestExistingPath(absolute)
if (!isPathWithinRoot(resolvedPath, resolvedRoot)) {
throw new Error(`Access denied: Path is outside the configured workspace root: ${requestedPath}`)
}
return resolvedPath
}
// ============================================================================
// Edit Tool Utilities - Fuzzy matching replacers from opencode
// ============================================================================
export type Replacer = (content: string, find: string) => Generator<string, void, unknown>
// Similarity thresholds for block anchor fallback matching
const SINGLE_CANDIDATE_SIMILARITY_THRESHOLD = 0.0
const MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD = 0.3
/**
* Levenshtein distance algorithm implementation
*/View on GitHub (pinned to 726446b54c)
Solutions
- Ensure file_path resolves under the same baseDir passed to the tool; strip ../ sequences or reject them upstream.
- Pass the correct baseDir explicitly rather than relying on process.cwd().
- Resolve symlinks on the caller side and confirm the realpath is still within the workspace root.
- On Windows, keep paths on the same drive as baseDir.
Example fix
// before
await handleReadTool({ file_path: '/etc/passwd' }, '/workspace') // throws: Access denied
await handleReadTool({ file_path: '../../../secret' }, '/workspace') // throws: Access denied
// after
await handleReadTool({ file_path: 'src/config.json' }, '/workspace') Defensive patterns
Strategy: validation
Validate before calling
import { validatePath } from '../types'
// Pre-flight: throws the same error before the tool does any work
const resolved = await validatePath(requestedPath, baseDir)
// or, lightweight local check:
function withinRoot(p: string, root: string): boolean {
const rel = path.relative(path.resolve(root), path.resolve(root, p))
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))
} Try / catch
try {
await handleReadTool(args, baseDir)
} catch (e) {
if (e instanceof Error && e.message.startsWith('Access denied')) {
// reject the user input; do not retry with a different escape attempt
} else throw e
} Prevention
- Always pass an explicit baseDir; do not rely on process.cwd().
- Reject or sanitize ../ and absolute paths in user input before they reach the tool.
- Resolve symlinks upstream and re-check the realpath is within root.
- On Windows, keep paths on the same drive as baseDir.
When it happens
Trigger: Passing an absolute path outside baseDir; using ../ to climb above the workspace root; a symlink inside the workspace whose realpath target is outside the root; on Windows, a different-drive path (C:\ vs D:\) whose relative path becomes absolute; baseDir undefined causing resolution against process.cwd() that the caller did not intend.
Common situations: Caller builds a path from user input without confining it; symlink in node_modules pointing outside the project; baseDir not passed (defaults to cwd) when the server is launched from an unexpected working directory; home-dir expansion (~) resolving outside the configured root.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Agent storage path escapes its root: ${target}
- Agent storage root must be a real directory: ${root}
- Agent storage path contains a symbolic link: ${current}
- Agent storage path resolves outside its root: ${target}
- Invalid agent id for data directory: ${agentId}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/58bf0f474f28d636.
Report an issue: GitHub.