microsoft/playwright · error · Error
File access denied: ${resolvedFilename} is outside allowed r
Error message
File access denied: ${resolvedFilename} is outside allowed roots. Allowed roots: ${output}, ${workspace} What it means
Thrown by checkFile (used by workspaceFile/outputFile) when an LLM-origin (flags.origin === 'llm') file path resolves outside both the output directory and the workspace (options.cwd). Code-origin calls, allowUnrestrictedFileAccess, and skillMode all bypass this sandbox check.
Source
Thrown at packages/playwright-core/src/tools/backend/context.ts:423
export async function outputFile(options: ContextOptions, fileName: string, flags: { origin: 'code' | 'llm' }): Promise<string> {
const resolvedFile = path.resolve(outputDir(options), fileName);
await checkFile(options, resolvedFile, flags);
await fs.promises.mkdir(path.dirname(resolvedFile), { recursive: true });
debug('pw:mcp:file')(resolvedFile);
return resolvedFile;
}
async function checkFile(options: ContextOptions, resolvedFilename: string, flags: { origin: 'code' | 'llm' }) {
// Trust code and unrestricted file access.
if (flags.origin === 'code' || options.config.allowUnrestrictedFileAccess || options.config.skillMode)
return;
// Trust llm to use valid characters in file names.
const output = outputDir(options);
const workspace = options.cwd;
if (!isPathInside(output, resolvedFilename) && !isPathInside(workspace, resolvedFilename))
throw new Error(`File access denied: ${resolvedFilename} is outside allowed roots. Allowed roots: ${output}, ${workspace}`);
}
View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Use a workspace-relative path (relative to options.cwd) for the file.
- If writing tool artifacts, route through outputFile() which already anchors to outputDir.
- For trusted automation, enable allowUnrestrictedFileAccess or skillMode in the config to bypass the sandbox.
- Verify the resolved absolute path with path.resolve and confirm it lives under cwd before invoking the tool.
Example fix
// before (agent supplies absolute path outside roots) await workspaceFile(options, '/etc/secrets.txt'); // throws // after await workspaceFile(options, 'artifacts/secrets.txt'); // resolves under options.cwd
Defensive patterns
Strategy: validation
Validate before calling
import path from 'path';
function isInside(root: string, candidate: string): boolean {
const rel = path.relative(root, candidate);
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
}
function assertFileAllowed(opts: { cwd: string; outputDir?: string }, absPath: string) {
const output = opts.outputDir ?? /* mirror outputDir() logic */ path.join(opts.cwd, '.playwright-mcp');
if (!isInside(output, absPath) && !isInside(opts.cwd, absPath))
throw new Error(`Refusing path outside roots: ${absPath}`);
} Type guard
function isPathInsideRoot(root: string, candidate: string): boolean {
const rel = path.relative(root, candidate);
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
} Try / catch
try {
await context.workspaceFile(name, perCallDir);
} catch (e) {
if (e instanceof Error && e.message.startsWith('File access denied:')) {
// rewrite to a workspace-relative path and retry
} else throw e;
} Prevention
- Always resolve agent-supplied paths with path.resolve(workspace, name) before use.
- Restrict LLM tool inputs to relative paths; reject absolute paths upstream.
- If unrestricted access is genuinely needed, set allowUnrestrictedFileAccess deliberately and document the trust decision.
When it happens
Trigger: An LLM-driven MCP tool resolving a file path (read, write, screenshot output, PDF output, upload) that resolves via path.resolve to a location outside outputDir(options) AND outside options.cwd.
Common situations: Agent passes an absolute path like /etc/passwd or /tmp/secret; relative path with ../ that escapes the workspace; outputDir falling back to a tmp dir while the agent assumed the workspace dir.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Access to "file:" protocol is blocked. Attempted URL: "${url
- Trace entry '${entry}' escapes output directory
- Attachment name '${fileName}' escapes output directory
- Path is not available when connecting remotely. Use saveAs()
- Cannot write to a directory
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/fedb315175f81eb6.
Report an issue: GitHub.