microsoft/playwright · error · Error

Access to "file:" protocol is blocked. Attempted URL: "${url

Error message

Access to "file:" protocol is blocked. Attempted URL: "${url}"

What it means

Thrown by Context.checkUrlAllowed when a navigation/request targets a file: URL and the config flag allowUnrestrictedFileAccess is not set. The check is skipped entirely (returns early) when URL.canParse fails, but a well-formed file:// URL with the flag disabled is blocked to keep LLM-driven navigation from touching the local filesystem.

Source

Thrown at packages/playwright-core/src/tools/backend/context.ts:349

    await this._setupRequestInterception(browserContext);

    for (const initScript of this.config.browser?.initScript || [])
      this._disposables.push(await browserContext.addInitScript({ path: path.resolve(this.options.cwd, initScript) }));

    for (const page of browserContext.pages())
      this._onPageCreated(page);
    this._disposables.push(eventsHelper.addEventListener(browserContext, 'page', page => this._onPageCreated(page)));

    return browserContext;
  }

  checkUrlAllowed(url: string) {
    if (this.config.allowUnrestrictedFileAccess)
      return;
    if (!URL.canParse(url))
      return;
    if (new URL(url).protocol === 'file:')
      throw new Error(`Access to "file:" protocol is blocked. Attempted URL: "${url}"`);
  }

  lookupSecret(secretName: string): { value: string, code: string, isSecret: boolean } {
    if (!this.config.secrets?.[secretName])
      return { value: secretName, code: escapeWithQuotes(secretName, '\''), isSecret: false };
    const codegen = this.config.codegen ?? 'typescript';
    return {
      value: this.config.secrets[secretName]!,
      code: secretCode(codegen === 'none' ? 'typescript' : codegen, secretName),
      isSecret: true,
    };
  }

  redactSecrets(text: string): string {
    for (const [secretName, secretValue] of Object.entries(this.config.secrets ?? {})) {
      if (!secretValue)
        continue;
      text = text.replaceAll(secretValue, `<secret>${secretName}</secret>`);

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Serve the file over http(s) (e.g. a local static server) and navigate to that URL instead.
  2. If local file access is intended and the trust boundary allows it, enable allowUnrestrictedFileAccess in the MCP/browser config.
  3. Rewrite the target so checkUrlAllowed sees an http(s) URL.

Example fix

// before
await page.goto('file:///home/user/report.html'); // throws via checkUrlAllowed

// after (option A - serve)
await page.goto('http://localhost:8080/report.html');

// after (option B - allow)
// context config: { allowUnrestrictedFileAccess: true }
Defensive patterns

Strategy: validation

Validate before calling

function isAllowedUrl(url: string, allowFile = false): boolean {
  if (!URL.canParse(url)) return true; // mirrors checkUrlAllowed early-out
  const proto = new URL(url).protocol;
  if (proto === 'file:') return allowFile;
  return true;
}

if (!isAllowedUrl(target, !!config.allowUnrestrictedFileAccess)) {
  throw new Error(`Refusing file: URL: ${target}`);
}

Type guard

function isFileProtocol(url: string): boolean {
  return URL.canParse(url) && new URL(url).protocol === 'file:';
}

Try / catch

try {
  await page.goto(target);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Access to "file:" protocol is blocked')) {
    // serve locally instead, or re-run with allowUnrestrictedFileAccess after user consent
  } else throw e;
}

Prevention

When it happens

Trigger: Any navigation, goto, or request-initiating MCP tool called with a file:// URL while ContextConfig.allowUnrestrictedFileAccess is unset/false.

Common situations: MCP agent opening a locally generated HTML report; a test harness that tries to view file:// fixtures through the MCP browser; default config which ships with file access blocked for safety.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/f3a199710b26af99. Report an issue: GitHub.