[BUG] Cowork local sandbox: proxy returns 403 'Connection blocked by network allowlist' for cdn.playwright.dev although it is in the session's default egressAllowedDomains

Status Open
Maintainer reply None cached
Activity 3 comments · opened Sep 16, 2026

Preflight Checklist

  • [x] I have searched existing issues — closest are #93643 (allowlist ignored, regression since ~2026-09-04) and #38984 ("Additional allowed domains" non-functional). This report differs: the refused host is in the sandbox's default package-manager list, not a custom entry, and it is refused while other entries of the same list pass.
  • [x] This is a single bug report
  • [x] I am using the latest version (Claude desktop app 2.110.0)

What's Wrong?

Product: Claude desktop app 2.110.0 — Cowork mode, local Linux sandbox on macOS 27.0 (26A428), Apple Silicon. Sandbox: @anthropic-ai/sandbox-runtime, arm64, Node 22.23.2.
Observed: 2026-09-16, in a scheduled-task session (00:26 UTC) and in an interactive session (00:47 UTC) — identical result.
Regression: the same download from the same sandbox worked on 2026-08-27.

Inside the sandbox, npx playwright install chromium-headless-shell fails with HTTP 403 from the egress proxy, body Connection blocked by network allowlist, for both Playwright download hosts — cdn.playwright.dev and playwright.download.prss.microsoft.com. Yet the session's own configuration file (local_<id>.json, field egressAllowedDomains) lists both hosts explicitly, as part of the default list:

"egressAllowedDomains": ["registry.npmjs.org", "npmjs.com", "www.npmjs.com", "npmjs.org", "www.npmjs.org",
 "yarnpkg.com", "registry.yarnpkg.com", "pypi.org", "files.pythonhosted.org", "pythonhosted.org",
 "github.com", "objects.githubusercontent.com", "archive.ubuntu.com", "security.ubuntu.com",
 "crates.io", "index.crates.io", "static.crates.io",
 "playwright.download.prss.microsoft.com", "cdn.playwright.dev",
 "*.anthropic.com", "anthropic.com", "claude.com", "*.claude.com"]

Other hosts of that same list are reachable through the same proxy in the same session (registry.npmjs.org → 200, github.com → 200, pypi.org → 200), so the allowlist is enforced — just not for these two entries. A bare curl -I https://cdn.playwright.dev/ fails at CONNECT (curl exit 56), so the block is at the domain level, not a path or size limit.

What Should Happen?

Hosts present in the session's egressAllowedDomains should be reachable through the egress proxy. Either the proxy should honour the two Playwright entries of the default list, or the session config should not advertise them.

Error Messages/Logs

$ PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=1 DEBUG=pw:install npx playwright install chromium-headless-shell 2>&1 | head -8
pw:install downloading Chrome Headless Shell 153.0.8010.12 (playwright chromium-headless-shell v1243) - attempt #1
Downloading Chrome Headless Shell 153.0.8010.12 (playwright chromium-headless-shell v1243) from https://cdn.playwright.dev/builds/cft/153.0.8010.12/linux-arm64/chrome-headless-shell-linux-arm64.zip
pw:install -- response status code: 403
Error: Download failed: server returned code 403 body 'Connection blocked by network allowlist'. URL: https://cdn.playwright.dev/builds/cft/153.0.8010.12/linux-arm64/chrome-headless-shell-linux-arm64.zip

$ PLAYWRIGHT_DOWNLOAD_HOST=https://playwright.download.prss.microsoft.com npx playwright install chromium-headless-shell 2>&1 | head -3
Error: Download failed: server returned code 403 body 'Connection blocked by network allowlist'. URL: https://playwright.download.prss.microsoft.com/builds/cft/153.0.8010.12/linux-arm64/chrome-headless-shell-linux-arm64.zip

$ for u in https://cdn.playwright.dev/ https://playwright.download.prss.microsoft.com/ https://registry.npmjs.org/ https://github.com/; do printf "%-48s " "$u"; curl -s -o /dev/null -m 25 -w "%{http_code}\n" -I "$u" || echo "curl exit $?"; done
https://cdn.playwright.dev/                      000  (curl exit 56)
https://playwright.download.prss.microsoft.com/  000  (curl exit 56)
https://registry.npmjs.org/                      200
https://github.com/                              200

Steps to Reproduce

  1. Start any Cowork session with the local sandbox (macOS).
  2. mkdir -p ~/browser && cd ~/browser && npm install --silent playwright (npm registry is reachable; installs 1.63.0).
  3. PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=1 DEBUG=pw:install npx playwright install chromium-headless-shell 2>&1 | head -8
  4. Compare with curl -sI https://registry.npmjs.org/ (200) and curl -sI https://cdn.playwright.dev/ (refused) in the same shell.

Claude Model

Opus

Is this a regression?

Yes

Last Working Version

Worked on 2026-08-27 (desktop app version at that date not recorded).

Claude Code Version

Claude desktop app 2.110.0

Platform

Claude.ai (Pro)

Operating System

macOS

Terminal/Shell

Other

Additional Information

Impact: any Cowork task that renders HTML for a visual check (scheduled reports, dashboards, a daily brief) can no longer obtain a browser and silently degrades to a CSS-partial fallback (WeasyPrint). Note for others hitting this: Playwright's installer hides the cause unless run with DEBUG=pw:install, and the useful line is at the head of the output, not the tail.

Workaround in use: download the exact Chrome-for-Testing headless-shell zip outside the sandbox, keep it in a mounted folder, and unpack it into ~/.cache/ms-playwright/chromium_headless_shell-<rev>/… with an INSTALLATION_COMPLETE marker so playwright treats it as installed. Works, but defeats the point of the allowlist entries.

Full diagnostic log (proxy environment minus credentials, curl probes, complete pw:install debug output) available on request.

View original on GitHub ↗

3 Comments

ywadjas · 29 days ago

Additional data point that narrows this down (same machine, same day, 2026-09-16 01:52 UTC):

The refusal only happens in the default "Package managers only" allowlist mode. After switching Settings → Capabilities → Domain allowlist to "All domains" and starting a fresh Cowork session, the exact same command succeeds:

$ PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=1 DEBUG=pw:install npx playwright install chromium-headless-shell
pw:install -- from url: https://cdn.playwright.dev/builds/cft/153.0.8010.12/linux-arm64/chrome-headless-shell-linux-arm64.zip
pw:install -- response status code: 200
pw:install SUCCESS installing Chrome Headless Shell 153.0.8010.12 (playwright chromium-headless-shell v1243)
pw:install SUCCESS installing FFmpeg (playwright ffmpeg v1011)

curl -sI https://cdn.playwright.dev/builds/cft/153.0.8010.12/linux-arm64/chrome-headless-shell-linux-arm64.zip → 307 to storage.googleapis.com/chrome-for-testing-public/…, i.e. the CDN's normal behaviour. Also in "All domains" mode the http_proxy/https_proxy/ALL_PROXY variables are absent from the sandbox environment, whereas in "Package managers only" mode they point at the egress proxy that returns the 403.

So the two Playwright hosts are advertised in egressAllowedDomains in "Package managers only" mode but not actually honoured by that mode's proxy; in "All domains" mode there is no proxy in the path and the download works. (Playwright's redirect target storage.googleapis.com is not in the default list either — if the proxy follows the client through the 307, that host would need to be allowed too.)

Unrelated: playwright.download.prss.microsoft.com returns a plain 403 from the server itself for builds/cft/… paths in both modes (no "network allowlist" body) — that mirror just doesn't serve Chrome-for-Testing builds, so the fallback host would not help even if the proxy were fixed.

I've switched back to "Package managers only" for the narrower egress and am working around it by seeding the browser zip from outside the sandbox.

ywadjas · 29 days ago

One more observation that may help whoever looks at the proxy side — offered as data, not as a diagnosis.

In "Package managers only" mode, the proxy URL injected into the sandbox environment (http_proxy, https_proxy, ALL_PROXY, CLOUDSDK_PROXY_USERNAME, …) carries a username of the form srt.<base64>, and the base64 decodes to the exact command line that was launched in that bash call (here: bash -c 'bash "$(ls -d /sessions/*/mnt/…)/_sandbox/diag-playwright.sh"'). Two runs of the same script from different bash invocations got different usernames matching their respective command lines. So the egress proxy identifies each connection by the command that opened it.

Also consistent across runs: in "All domains" mode none of the *_proxy variables are set at all — the sandbox talks out directly — which is why that mode "fixes" the download rather than the allowlist being applied differently.

Reproduced a third time at 02:16 UTC in a brand-new session with the Playwright cache wiped beforehand, "Package managers only": five attempts on cdn.playwright.dev, five on playwright.download.prss.microsoft.com, all 403 'Connection blocked by network allowlist'; registry.npmjs.org and github.com 200 in the same shell.

tonydehnke · 8 days ago

Same symptom in Claude Code on the web (claude.ai/code cloud environment), not only Cowork: on 2026-10-07 playwright install --with-deps chromium (Playwright 1.63.0) failed with HTTP 403 from cdn.playwright.dev during the environment's setup script and SessionStart hook. Other downloads in the same setup run succeeded (Node from nodejs.org, yarn packages from registry.yarnpkg.com). Workaround for now: skip the browser install in cloud and run browser tests locally/CI.