Security安全10 min read15 分钟阅读

Playwright Chromium Lag for Coding Agents: A Recurring Trust Problem, Not a One-Off CVE编程代理的 Playwright Chromium 滞后:反复出现的信任问题,而非一次性 CVE

Chrome ships fixes; Playwright pins Chromium later. Decide by workload—own-app E2E, agent untrusted browsing, Playwright MCP—using chromiumSandbox, channel chrome, non-root hosts, and egress allowlists. A decision matrix for Claude Code, Cursor, and MCP.Chrome 发布修复;Playwright 稍后才固定 Chromium。按工作负载做决定——自有应用 E2E、代理不受信任浏览、Playwright MCP——使用 chromiumSandbox、channel chrome、非 root 主机与出站允许列表。一份面向 Claude Code、Cursor 与 MCP 的决策矩阵。

  • playwright
  • chromium
  • coding-agents
  • claude-code
  • cursor
  • playwright-mcp
  • browser-security
  • chromium-sandbox
On this page
  1. What you are actually deciding
  2. The lag is structural, not personal
  3. A concrete November 2026 snapshot (facts, not the thesis)
  4. The second half of the gap: sandbox off by default
  5. Three workloads, three answers
  6. 1) Own-app E2E (CI and local regression)
  7. 2) Agent untrusted browsing (Claude Code, Cursor agents, research)
  8. 3) Playwright MCP (Cursor, Claude Code, VS Code, and friends)
  9. Controls that survive the next CVE train
  10. A. Engine freshness
  11. B. Chromium sandbox
  12. C. Non-root host
  13. D. Egress allowlist
  14. E. Credential hygiene
  15. Axis check: why this is not a “sandbox escape” article
  16. Config patterns (defense only)
  17. Decision examples
  18. Practical rollout for teams using Claude Code and Cursor
  19. What “good” looks like six months from now
  20. Sources (primary)
本页目录
  1. 你实际在决定什么
  2. 滞后是结构性的,不是个人问题
  3. 一个具体的 2026 年 11 月快照(事实,而非论点)
  4. 缺口的另一半:沙箱默认关闭
  5. 三种工作负载,三种答案
  6. 1)自有应用 E2E(CI 与本地回归)
  7. 2)代理不受信任浏览(Claude Code、Cursor 代理、调研)
  8. 3)Playwright MCP(Cursor、Claude Code、VS Code 及其同类)
  9. 能经受下一次 CVE 列车的控制措施
  10. A. 引擎新鲜度
  11. B. Chromium 沙箱
  12. C. 非 root 主机
  13. D. 出站允许列表
  14. E. 凭据卫生
  15. 轴检查:为什么这不是一篇“沙箱逃逸”文章
  16. 配置模式(仅防御)
  17. 决策示例
  18. 面向使用 Claude Code 与 Cursor 的团队的实际推广
  19. 六个月后“好”是什么样子
  20. 来源(一手)

Chrome updates on a security train. Playwright updates on a release train. Coding agents sit on whatever binary Playwright last pinned—and often launch it with Chromium’s sandbox off.

That combination is not a November 2026 curiosity. It is a recurring trust gap: every time Google ships a Stable fix, agent browsers that still use an older Playwright pin remain behind until someone upgrades Playwright and reinstalls browsers—or until you stop using the bundled build for untrusted pages.

This guide is a decision document, not a CVE brief. It maps three workloads—own-app E2E, agent untrusted browsing, and Playwright MCP—to concrete controls: chromiumSandbox, channel: 'chrome', non-root execution, and egress allowlists. It names Claude Code, Cursor, and Playwright MCP because those are where the lag meets day-to-day agent work. It deliberately stays on a different axis from “sandbox escape checklists” for agent privileges: here the issue is engine freshness and browser containment, not whether an agent can rm -rf your repo.

What you are actually deciding

Write the decision as one sentence before you change configs:

Given this workload’s page trust and blast radius, will I accept Playwright’s bundled Chromium pin (knowing it lags Chrome Stable), and which of chromiumSandbox, branded Chrome, host isolation, and network allowlists must be on before the agent browses?

If your answer collapses to “someone posted a CVE number, panic,” you will over-harden trusted CI and under-harden open-web agents. The matrix below exists to stop that.

The lag is structural, not personal

Playwright does not auto-track Chrome Stable day by day. Each Playwright release pins browser builds in packages/playwright-core/browsers.json. The Playwright browsers guide is explicit: every Playwright version needs specific browser binaries; updating Playwright usually means running npx playwright install again.

That creates a predictable sequence:

  1. Chrome Stable ships a security update.
  2. Playwright later rolls a newer Chromium (often on main first).
  3. A Playwright release ships the new pin.
  4. You upgrade the package and install browsers—or you keep running the old pin forever.

Timeline: Chrome fix, Playwright browsers.json roll, Playwright release, agents stuck on old pin

Figure 1. The recurring lag window. Agents do not inherit Chrome’s Stable channel unless you point Playwright at installed Chrome or wait for a Playwright release that pins a post-fix build.

A concrete November 2026 snapshot (facts, not the thesis)

Use this window as evidence of the pattern—not as the article’s spine.

  • Playwright v1.64.0 published 2026-10-07 (GitHub release).
  • Playwright v1.64.1 browsers.json still pins Chromium 156.0.8078.4 (raw browsers.json @ v1.64.1).
  • Chrome Stable on 2026-10-27 moved to 156.0.8078.73/.74 (Windows/Mac) and 156.0.8078.73 (Linux), with 52 security fixes, including critical CVE-2026-118306 (FileSystem memory corruption; Chromium severity Critical) per the Chrome Releases post and the CVE description.
  • Playwright main can already show a newer pin (for example Chromium 158.0.8127.2) while stable npm packages still ship the older one. Rolling on main is not the same as your agent’s installed package.

Google’s advisory language for CVE-2026-118306 is the usual high-level severity statement. This article will not reconstruct exploitation steps. Treat the CVE as a freshness anchor: a Critical fix landed in Stable while the then-current Playwright release pin was still on an earlier 156.0.8078.x build.

When the next Chrome security train lands, expect the same diagram with new version numbers.

The second half of the gap: sandbox off by default

Freshness is only half the story. Playwright’s launch API documents chromiumSandbox as:

Enable Chromium sandboxing. Defaults to false.

Source: BrowserType.launch options.

That default exists for pragmatic reasons (containers, root, missing setuid helpers, CI images). It is still a security default that coding-agent setups inherit unless someone opts in. Untrusted HTML in a browser without Chromium’s sandbox is a different risk class than the same HTML in a current, sandboxed Chrome.

Playwright MCP exposes the same tension as CLI flags: --sandbox to enable, --no-sandbox to disable (Playwright MCP README options). Docker examples often show --no-sandbox because containers make the Chromium sandbox awkward. That convenience must not silently become your laptop agent’s permanent posture.

Decision rule: for any workload that can load pages you did not author, treat chromiumSandbox: false / --no-sandbox as an explicit risk acceptance—not as “Playwright recommended.”

Three workloads, three answers

Trust matrix for own-app E2E, agent untrusted browsing, and Playwright MCP

Figure 2. Trust matrix. Match controls to page trust and blast radius. Do not apply CI E2E defaults to open-web agent browsing.

1) Own-app E2E (CI and local regression)

Page trust: high. You load your app, fixtures, and maybe a stub API.

Bundled Chromium lag: annoying, rarely urgent. You still want Playwright updated on a schedule so you catch upcoming Chrome breakage early—the browsers docs recommend staying current for that reason. Security urgency is lower because attackers do not choose your test HTML.

Sandbox: leaving the default off is a common CI tradeoff. Prefer non-root runners anyway. If your CI image can run with chromiumSandbox: true, turn it on; if the zygote fails with “No usable sandbox!”, fix the image (user namespaces / AppArmor / non-root) rather than cargo-culting --no-sandbox onto every developer laptop.

Minimum controls

  1. Pin allowed base URLs (your staging / localhost).
  2. Keep Playwright + npx playwright install on a cadence (weekly or every release).
  3. Run as non-root.
  4. Do not store production cookies in the test profile.

2) Agent untrusted browsing (Claude Code, Cursor agents, research)

Page trust: low. The agent follows links, opens docs, “just checks one more site.”

Bundled Chromium lag: high. This is where the Chrome→Playwright delay bites. Prefer a browser that receives Stable security updates without waiting for Playwright’s next pin.

Sandbox: on. Set chromiumSandbox: true when you launch via Playwright APIs. If the host cannot sandbox, move the browser into a disposable VM/container that has no host credentials—not the other way around.

Minimum controls

  1. channel: 'chrome' (or msedge) so Playwright drives an installed, auto-updating branded browser (channel docs; Google Chrome & Edge section).
  2. chromiumSandbox: true.
  3. Egress allowlist at the network layer (proxy, firewall, or MCP origin rules)—default-deny except docs domains you actually need.
  4. Disposable profile: no daily-driver cookies, no password manager, no SSO session to production.
  5. Prefer a throwaway VM/container over “browser on the same user that holds AWS keys.”

Claude Code and Cursor often reach the web through shell + Playwright, through a computer-use path, or through MCP. Whatever the path, classify the session as untrusted browsing the moment the model can choose a URL.

3) Playwright MCP (Cursor, Claude Code, VS Code, and friends)

Official docs state Playwright MCP works with VS Code, Cursor, Windsurf, Claude Code, Claude Desktop, and other MCP clients (Playwright MCP getting started). Installation is typically npx @playwright/mcp@latest.

Treat MCP as untrusted browsing with a tool API, unless you have locked origins to first-party apps.

Browser selection: pass --browser=chrome (or msedge) when you need Stable freshness instead of the downloaded Chromium pin. Supported values include chrome, firefox, webkit, msedge.

Sandbox flags: prefer --sandbox. Avoid baking --no-sandbox into laptop configs just because a Docker snippet used it.

Network: --allowed-origins can list trusted origins. MCP docs warn it does not serve as a full security boundary and does not affect redirects—so pair it with host egress controls, not faith in a single flag.

Profile: default persistent profiles keep cookies between sessions under a cache path. For research agents, prefer --isolated (or a dedicated --user-data-dir) so yesterday’s login is not tomorrow’s credential leak. Do not point automation at your personal Chrome profile; Playwright’s own docs warn against automating the default user profile.

Dangerous tool: browser_run_code_unsafe is RCE-equivalent in the Playwright server process. Enable only for trusted clients. That is agent-privilege risk, orthogonal to Chromium lag—but it raises the cost of a compromised browsing session.

Controls that survive the next CVE train

Copy these as a standing checklist. Re-run after every Chrome Stable security blast and every Playwright upgrade.

A. Engine freshness

  • Record npx playwright --version and the Chromium version from browsers.json / npx playwright install --list.
  • After Chrome Stable security posts, ask: “Are we on bundled Chromium or channel: 'chrome'?”
  • If bundled: either wait for a Playwright release that pins a post-fix build, then npm install + npx playwright install, or switch untrusted workloads to branded Chrome now.
  • Do not assume main’s browsers.json is what your agents run.

B. Chromium sandbox

  • Library/API: chromium.launch({ chromiumSandbox: true, ... }) / equivalent in config use.
  • MCP: --sandbox; do not set --no-sandbox unless the environment cannot start otherwise—and then isolate that environment.
  • If sandbox fails to start, remediate the host (non-root, user namespaces) instead of permanently disabling containment for agents.

C. Non-root host

  • Do not run agent browsers as root “to make CI green.”
  • Root + --no-sandbox is a common container pattern. Keep it in CI images that only hit trusted URLs, not on developer machines browsing the open web for the agent.

D. Egress allowlist

  • Prefer default-deny egress for agent browser processes.
  • Allow only the domains required for the task; expand deliberately.
  • For MCP, combine origin allowlists with OS/network policy. Remember redirects can leave an origin list behind.

E. Credential hygiene

  • Separate profiles for agents vs humans.
  • No production cloud consoles, banking, or password-manager unlock in the agent browser.
  • Rotate any cookie jar that may have touched untrusted HTML while sandbox/freshness controls were weak.

Axis check: why this is not a “sandbox escape” article

Security Twitter often mixes two different problems:

  1. Browser engine lag + Chromium sandbox defaults (this article).
  2. Agent sandbox escape / privilege checks—whether the coding agent can break out of its tool jail, write outside a workspace, or abuse host APIs.

They share the word “sandbox.” They do not share the fix list. Escape checklists talk about FS permissions, approval prompts, and tool policy. This checklist talks about which Chromium you launch, whether Chromium’s sandbox is on, and how far that browser can talk on the network.

If you only run escape drills, you can still browse Critical-patched Chrome bugs on an old pin with --no-sandbox. If you only bump Chromium, you can still give the agent a browser that shares your SSO cookies. Do both tracks; do not collapse them.

Config patterns (defense only)

These snippets are configuration checks, not attack recipes. Adapt to your stack.

Playwright Test — untrusted / agent helper project

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'agent-untrusted-chrome',
      use: {
        ...devices['Desktop Chrome'],
        channel: 'chrome',
        chromiumSandbox: true,
        launchOptions: {
          // Keep args minimal; do not add --no-sandbox here.
        },
      },
    },
  ],
});

Library launch — same posture

const browser = await chromium.launch({
  channel: 'chrome',
  chromiumSandbox: true,
  headless: true,
});

Playwright MCP — Cursor / Claude Code args sketch

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--browser=chrome",
        "--sandbox",
        "--isolated",
        "--allowed-origins=https://docs.example.com;https://developer.example.com"
      ]
    }
  }
}

Notes that matter in review:

  1. --allowed-origins is a trust hint, not a complete boundary; still enforce egress at the host or proxy.
  2. If Chrome is missing on a machine, install it deliberately—or fail closed—rather than silently falling back to an old bundled pin for untrusted work.
  3. For own-app E2E in CI, a separate project can keep bundled Chromium for hermetic browsers; do not reuse that project’s launch options for open-web agent sessions.

Decision examples

Example A — monorepo CI. Playwright hits http://localhost:3000 and a mock API. Label: own-app E2E. Keep hermetic Chromium if you want reproducible CI. Schedule weekly Playwright bumps. Non-root. Sandbox on if the image supports it. No open egress.

Example B — Cursor agent “read this RFC and competing blog posts.” Label: untrusted. Require channel: 'chrome' or MCP --browser=chrome, sandbox on, isolated profile, allowlist only the RFC host plus one docs CDN, run under a dedicated OS user without cloud CLIs in PATH credentials.

Example C — Claude Code debugging a customer staging site the team owns. Still not “internet random,” but not fully first-party either. Prefer branded Chrome + sandbox on + allowlist the staging hostname. Do not reuse the engineer’s personal profile that holds production admin cookies.

Example D — someone pasted a Docker MCP line with --no-sandbox into laptop Cursor config. Reject in review. Containers that need --no-sandbox should be disposable and network-locked; laptops should not inherit that flag by copy-paste.

Practical rollout for teams using Claude Code and Cursor

Step 1 — Inventory. List every path that launches a browser for an agent: Playwright test configs, ad-hoc scripts, Playwright MCP server args, computer-use runners, internal “browse this” tools.

Step 2 — Label each path as own-app E2E or untrusted. If unsure, label untrusted.

Step 3 — Apply the matrix. Untrusted paths get branded Chrome (or equivalent current channel), sandbox on, allowlisted egress, isolated profile, non-root. E2E paths get scheduled Playwright upgrades and URL pins; sandbox on when the runner supports it.

Step 4 — Make freshness observable. Add a weekly job that prints Playwright version + Chromium pin + Chrome Stable version. When Stable is ahead of your pin by a security release, open a ticket automatically for untrusted workloads.

Step 5 — Write the exception. Any --no-sandbox or bundled-Chromium-on-open-web exception needs an owner, an expiry date, and a compensating control (usually a locked-down VM).

What “good” looks like six months from now

You will still see Chrome security posts. Playwright will still pin browsers per release. That will not go away.

What should go away:

  • Agents on developer laptops browsing arbitrary sites with Playwright’s default unsandboxed bundled Chromium.
  • MCP configs copied from Docker that disable the sandbox “because the sample did.”
  • Security responses that stop at citing one CVE number without classifying the workload.

What should remain:

  • A living trust matrix.
  • Freshness monitoring tied to Chrome Stable.
  • Explicit opt-in for weaker containment.

Sources (primary)

The next Chrome security train will make new version numbers. Your decision matrix should not need a rewrite—only a freshness check.

Chrome 按安全列车更新。Playwright 按发布列车更新。编程代理停留在 Playwright 上次固定的那个二进制上——而且常常在 Chromium 沙箱关闭的情况下启动它。

这种组合并不是 2026 年 11 月的猎奇。它是一个反复出现的信任缺口:每当 Google 发布 Stable 修复,仍使用较旧 Playwright 固定版本的代理浏览器都会落后,直到有人升级 Playwright 并且 重新安装浏览器——或者直到你不再把捆绑构建用于不受信任的页面。

本指南是一份决策文档,不是 CVE 简报。它把三种工作负载——自有应用 E2E、代理不受信任浏览、Playwright MCP——映射到具体控制:chromiumSandbox、channel: 'chrome'、非 root 执行,以及出站允许列表。它点名 Claude Code、Cursor 与 Playwright MCP,因为滞后与日常代理工作相遇的地方正是这些产品。它有意停留在与代理权限“沙箱逃逸检查清单”不同的轴上:这里的问题是引擎新鲜度与浏览器遏制,而不是代理能否 rm -rf 你的仓库。

你实际在决定什么

在更改配置之前,把决定写成一句话:

给定这一工作负载的页面信任度与爆炸半径,我是否接受 Playwright 捆绑的 Chromium 固定版本(明知它落后于 Chrome Stable),以及在代理浏览之前,chromiumSandbox、品牌 Chrome、主机隔离与网络允许列表中的哪些必须开启?

如果你的答案塌缩成“有人发了一个 CVE 编号,于是恐慌”,你就会过度加固受信任的 CI,并让开放网络上的代理加固不足。下面的矩阵就是为了阻止这种情况。

滞后是结构性的,不是个人问题

Playwright 不会逐日自动跟踪 Chrome Stable。每个 Playwright 发布都在 packages/playwright-core/browsers.json 中固定浏览器构建。Playwright 浏览器指南写得很明确:每个 Playwright 版本都需要特定的浏览器二进制;更新 Playwright 通常意味着再次运行 npx playwright install。

这会形成一条可预测的序列:

  1. Chrome Stable 发布一项安全更新。
  2. Playwright 稍后滚动到更新的 Chromium(往往先出现在 main 上)。
  3. 一次 Playwright 发布 带上新的固定版本。
  4. 你升级软件包 并且 安装浏览器——否则你会永远继续运行旧的固定版本。

时间线:Chrome 修复、Playwright browsers.json 滚动、Playwright 发布、代理卡在旧固定版本

图 1. 反复出现的滞后窗口。除非你把 Playwright 指向已安装的 Chrome,或等待一次固定了修复后构建的 Playwright 发布,否则代理不会继承 Chrome 的 Stable 通道。

一个具体的 2026 年 11 月快照(事实,而非论点)

把这个窗口用作该模式的证据——而不是文章的主干。

  • Playwright v1.64.0 发布于 2026-10-07(GitHub 发布)。
  • Playwright v1.64.1 的 browsers.json 仍然固定 Chromium 156.0.8078.4(v1.64.1 的原始 browsers.json)。
  • Chrome Stable 在 2026-10-27 推进到 156.0.8078.73/.74(Windows/Mac)以及 156.0.8078.73(Linux),含 52 项安全修复,其中包括严重的 CVE-2026-118306(FileSystem 内存损坏;Chromium 严重级别为 Critical),依据 Chrome Releases 帖子 与 CVE 描述。
  • Playwright 的 main 可能已经显示更新的固定版本(例如 Chromium 158.0.8127.2),而稳定版 npm 包仍在交付更旧的那个。在 main 上滚动,并不等同于你的代理已安装的包。

Google 针对 CVE-2026-118306 的公告措辞是惯常的高层严重性陈述。本文不会重构利用步骤。把该 CVE 当作新鲜度锚点:一项 Critical 修复已经进入 Stable,而当时的 Playwright 发布固定版本仍停留在更早的 156.0.8078.x 构建上。

当下一次 Chrome 安全列车到来时,预期同一张图会换成新的版本号。

缺口的另一半:沙箱默认关闭

新鲜度只是故事的一半。Playwright 的启动 API 将 chromiumSandbox 记载为:

启用 Chromium 沙箱。默认值为 false。

来源:BrowserType.launch 选项。

该默认值的存在出于务实原因(容器、root、缺少 setuid 辅助程序、CI 镜像)。它仍然是编程代理环境会继承的安全默认值,除非有人主动选择开启。在没有 Chromium 沙箱的浏览器中加载不受信任的 HTML,与在当前且已启用沙箱的 Chrome 中加载同一份 HTML,属于不同的风险类别。

Playwright MCP 以 CLI 标志暴露出同样的张力:--sandbox 用于启用,--no-sandbox 用于禁用(Playwright MCP README 选项)。Docker 示例常常展示 --no-sandbox,因为容器会让 Chromium 沙箱变得棘手。这种便利绝不能在无声中变成笔记本上代理的永久姿态。

决策规则: 对于任何可能加载并非你所编写页面的工作负载,把 chromiumSandbox: false / --no-sandbox 视为明确的风险接受——而不是“Playwright 所推荐的”。

三种工作负载,三种答案

自有应用 E2E、代理不受信任浏览与 Playwright MCP 的信任矩阵

图 2. 信任矩阵。让控制措施与页面信任度及爆炸半径相匹配。不要把 CI E2E 的默认设置套用到开放网络上的代理浏览。

1)自有应用 E2E(CI 与本地回归)

页面信任度: 高。你加载自己的应用、测试夹具,也许还有一个桩 API。

捆绑 Chromium 滞后: 烦人,很少紧急。你仍然希望按计划更新 Playwright,以便尽早抓住即将到来的 Chrome 破坏性变更——浏览器文档正是出于这个原因建议保持最新。安全紧迫性较低,因为攻击者并不能挑选你的测试 HTML。

沙箱: 保持默认关闭是常见的 CI 权衡。无论如何都优先使用非 root 运行器。如果你的 CI 镜像能以 chromiumSandbox: true 运行,就打开它;如果 zygote 因“No usable sandbox!”失败,就修复镜像(用户命名空间 / AppArmor / 非 root),而不是把 --no-sandbox 盲从复制到每一台开发者笔记本上。

最低控制

  1. 固定允许的基础 URL(你的 staging / localhost)。
  2. 按固定节奏维护 Playwright + npx playwright install(每周或每次发布)。
  3. 以非 root 身份运行。
  4. 不要把生产 Cookie 存放在测试配置文件中。

2)代理不受信任浏览(Claude Code、Cursor 代理、调研)

页面信任度: 低。代理会跟随链接、打开文档、“再查一个站点就好”。

捆绑 Chromium 滞后: 高。这正是 Chrome→Playwright 延迟造成伤害的地方。优先选择无需等待 Playwright 下一次固定版本就能收到 Stable 安全更新的浏览器。

沙箱: 开启。通过 Playwright API 启动时设置 chromiumSandbox: true。如果主机无法启用沙箱,就把浏览器移入一台不含主机凭据的一次性 VM/容器——而不是反过来。

最低控制

  1. channel: 'chrome'(或 msedge),以便 Playwright 驱动已安装、会自动更新的品牌浏览器(channel 文档;Google Chrome 与 Edge 一节)。
  2. chromiumSandbox: true。
  3. 在网络层做出站允许列表(代理、防火墙或 MCP 源站规则)——除你确实需要的文档域名外一律默认拒绝。
  4. 一次性配置文件:没有日常浏览器的 Cookie,没有密码管理器,没有通往生产环境的 SSO 会话。
  5. 优先使用一次性 VM/容器,而不是“浏览器运行在持有 AWS 密钥的同一用户之下”。

Claude Code 与 Cursor 常常经由 shell + Playwright、经由 computer-use 路径,或经由 MCP 访问网页。无论路径如何,一旦模型能够选择 URL,就把该会话归类为不受信任浏览。

3)Playwright MCP(Cursor、Claude Code、VS Code 及其同类)

官方文档指出,Playwright MCP 可用于 VS Code、Cursor、Windsurf、Claude Code、Claude Desktop 以及其他 MCP 客户端(Playwright MCP 入门)。安装通常是 npx @playwright/mcp@latest。

把 MCP 视为带工具 API 的不受信任浏览,除非你已把源站锁定到第一方应用。

浏览器选择: 当你需要 Stable 的新鲜度、而不是已下载的 Chromium 固定版本时,传入 --browser=chrome(或 msedge)。支持的值包括 chrome、firefox、webkit、msedge。

沙箱标志: 优先使用 --sandbox。不要仅仅因为某段 Docker 片段用过 --no-sandbox,就把它固化进笔记本配置。

网络: --allowed-origins 可以列出受信任的源站。MCP 文档警告,它并不充当完整的安全边界,也不会影响重定向——因此把它与主机出站控制搭配使用,而不要只信赖单一标志。

配置文件: 默认的持久配置文件会在缓存路径下于会话之间保留 Cookie。对调研代理,优先使用 --isolated(或专用的 --user-data-dir),以免昨天的登录变成明天的凭据泄露。不要把自动化指向你的个人 Chrome 配置文件;Playwright 自己的文档警告不要自动化默认用户配置文件。

危险工具: browser_run_code_unsafe 在 Playwright 服务器进程中等价于 RCE。仅对受信任的客户端启用。这是代理权限风险,与 Chromium 滞后正交——但它提高了浏览会话一旦被攻破的代价。

能经受下一次 CVE 列车的控制措施

把这些抄成一份常设检查清单。每次 Chrome Stable 安全爆发以及每次 Playwright 升级之后都重新执行。

A. 引擎新鲜度

  • 记录 npx playwright --version,以及来自 browsers.json / npx playwright install --list 的 Chromium 版本。
  • 在 Chrome Stable 安全帖之后,问:“我们用的是捆绑 Chromium,还是 channel: 'chrome'?”
  • 如果是捆绑版本:要么等待一次固定了修复后构建的 Playwright 发布,然后执行 npm install + npx playwright install,或者现在就把不受信任的工作负载切换到品牌 Chrome。
  • 不要假定 main 的 browsers.json 就是你的代理正在运行的内容。

B. Chromium 沙箱

  • 库/API:chromium.launch({ chromiumSandbox: true, ... }) / 配置 use 中的等价写法。
  • MCP:--sandbox;除非环境不如此就无法启动,否则不要设置 --no-sandbox——然后隔离该环境。
  • 如果沙箱无法启动,就整治主机(非 root、用户命名空间),而不是永久关闭代理的遏制。

C. 非 root 主机

  • 不要为了“让 CI 变绿”而以 root 运行代理浏览器。
  • Root + --no-sandbox 是常见的容器模式。把它留在只访问受信任 URL 的 CI 镜像中,不要留在为代理浏览开放网络的开发者机器上。

D. 出站允许列表

  • 对代理浏览器进程优先采用默认拒绝的出站。
  • 只允许任务所需的域名;扩大时要有意为之。
  • 对 MCP,把源站允许列表与操作系统/网络策略结合起来。记住,重定向可以让流量离开源站列表。

E. 凭据卫生

  • 代理与人类使用各自独立的配置文件。
  • 代理浏览器中不要出现生产云控制台、银行业务,或密码管理器解锁。
  • 轮换任何在沙箱/新鲜度控制薄弱期间可能接触过不受信任 HTML 的 Cookie 罐。

轴检查:为什么这不是一篇“沙箱逃逸”文章

安全 Twitter 常常把两个不同的问题混在一起:

  1. 浏览器引擎滞后 + Chromium 沙箱默认值(本文)。
  2. 代理沙箱逃逸 / 权限检查——编程代理能否逃出其工具牢笼、写到工作区之外,或滥用主机 API。

它们共用“沙箱”这个词。它们并不共用同一份修复清单。逃逸检查清单谈的是 FS 权限、批准提示与工具策略。这份检查清单谈的是你启动的是哪一个 Chromium、Chromium 的沙箱是否开启,以及该浏览器在网络上能通信到多远。

如果你只做逃逸演练,你仍然可能在带 --no-sandbox 的旧固定版本上接触那些已有 Critical 补丁的 Chrome 漏洞。如果你只提升 Chromium 版本,你仍然可能给代理一个共享你 SSO Cookie 的浏览器。两条轨道都要做;不要把它们塌缩成一条。

配置模式(仅防御)

这些片段是配置检查,不是攻击配方。请按你的技术栈改编。

Playwright Test — 不受信任 / 代理辅助项目

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'agent-untrusted-chrome',
      use: {
        ...devices['Desktop Chrome'],
        channel: 'chrome',
        chromiumSandbox: true,
        launchOptions: {
          // Keep args minimal; do not add --no-sandbox here.
        },
      },
    },
  ],
});

库启动 — 同一姿态

const browser = await chromium.launch({
  channel: 'chrome',
  chromiumSandbox: true,
  headless: true,
});

Playwright MCP — Cursor / Claude Code 参数草图

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--browser=chrome",
        "--sandbox",
        "--isolated",
        "--allowed-origins=https://docs.example.com;https://developer.example.com"
      ]
    }
  }
}

评审中要紧的说明:

  1. --allowed-origins 是信任提示,不是完整边界;仍要在主机或代理处强制执行出站控制。
  2. 如果某台机器上缺少 Chrome,就有意安装它——或者失败关闭——而不是在不受信任的工作中静默回退到旧的捆绑固定版本。
  3. 对于 CI 中的自有应用 E2E,单独的项目可以为封闭浏览器保留捆绑 Chromium;不要把该项目的启动选项复用到开放网络的代理会话。

决策示例

示例 A — monorepo CI。 Playwright 访问 http://localhost:3000 和一个模拟 API。标签:自有应用 E2E。若你希望 CI 可复现,就保留封闭的 Chromium。安排每周的 Playwright 升级。非 root。镜像支持时开启沙箱。没有开放出站。

示例 B — Cursor 代理“读这篇 RFC 以及竞品博客文章”。 标签:不受信任。要求 channel: 'chrome' 或 MCP --browser=chrome,沙箱开启,隔离的配置文件,允许列表仅限 RFC 主机再加一个文档 CDN,在专用操作系统用户下运行,且该用户的 PATH 凭据中没有云 CLI。

示例 C — Claude Code 调试团队所拥有的客户 staging 站点。 这仍然不是“互联网上的随机站点”,但也并非完全的第一方。优先品牌 Chrome + 沙箱开启 + 允许列表仅限 staging 主机名。不要复用持有生产管理 Cookie 的工程师个人配置文件。

示例 D — 有人把带 --no-sandbox 的一行 Docker MCP 配置粘贴进笔记本上的 Cursor 配置。 在评审中拒绝。需要 --no-sandbox 的容器应当是一次性的并锁定网络;笔记本不应靠复制粘贴继承该标志。

面向使用 Claude Code 与 Cursor 的团队的实际推广

第 1 步 — 盘点。 列出为代理启动浏览器的每一条路径:Playwright 测试配置、临时脚本、Playwright MCP 服务器参数、computer-use 运行器、内部的“浏览这个”工具。

第 2 步 — 为每条路径贴标签,标为自有应用 E2E 或不受信任。如果不确定,就标为不受信任。

第 3 步 — 应用矩阵。 不受信任路径获得品牌 Chrome(或等价的当前通道)、开启沙箱、允许列表出站、隔离配置文件、非 root。E2E 路径获得按计划的 Playwright 升级与 URL 固定;运行器支持时开启沙箱。

第 4 步 — 让新鲜度可观察。 增加一个每周任务,打印 Playwright 版本 + Chromium 固定版本 + Chrome Stable 版本。当 Stable 因一次安全发布而领先于你的固定版本时,自动为不受信任工作负载开一张工单。

第 5 步 — 写下例外。 任何 --no-sandbox 或在开放网络上使用捆绑 Chromium 的例外,都需要一名负责人、一个到期日,以及一项补偿控制(通常是一台锁定的 VM)。

六个月后“好”是什么样子

你仍会看到 Chrome 安全帖。Playwright 仍会按每次发布固定浏览器。这一点不会消失。

应当消失的:

  • 开发者笔记本上的代理,使用 Playwright 默认未开启沙箱的捆绑 Chromium 浏览任意站点。
  • 从 Docker 复制来的、以“因为示例这么做”为由关闭沙箱的 MCP 配置。
  • 停在引用一个 CVE 编号、却不对工作负载分类的安全响应。

应当留下的:

  • 一份活的信任矩阵。
  • 与 Chrome Stable 绑定的新鲜度监控。
  • 对更弱遏制的显式选择加入。

来源(一手)

下一次 Chrome 安全列车会带来新的版本号。你的决策矩阵应当不需要重写——只需要一次新鲜度检查。