microsoft/playwright · error · Error
File access denied: is outside allowed roots. Allowed roots
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.
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
- Attachment name ' ' escapes output directory
- Trace entry ' ' escapes output directory
- Cannot close a directory
- Cannot write to a directory
AI-assisted analysis of microsoft/playwright@f1d33b5029 (2026-09-15).
Data as JSON: /api/errors/fedb315175f81eb6.
Report an issue: GitHub.
Appendix: source
Thrown at packages/playwright-core/src/tools/backend/context.ts:511
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;
// Follow symlinks, an unresolvable root cannot be traversed anyway.
const [realOutput, realWorkspace, realFilename] = await Promise.all([
resolveSymlinks(output).catch(() => output),
resolveSymlinks(workspace).catch(() => workspace),
resolveSymlinks(resolvedFilename),
]);
if (!isPathInside(realOutput, realFilename) && !isPathInside(realWorkspace, realFilename))
throw new Error(`File access denied: ${resolvedFilename} is outside allowed roots. Allowed roots: ${output}, ${workspace}`);
}
View on GitHub (pinned to f1d33b5029)