Repository navigation
fix(engine): keep Chrome's sandbox on for local renders where the host supports it - #7625
Conversation
…t supports it Chrome launched with --no-sandbox on every host. Keep the sandbox on macOS, Windows and non-root Linux outside containers; drop it as root, in Docker/Podman/Kubernetes, on Lambda and Cloud Run, and where Linux restricts unprivileged user namespaces. --no-zygote now ships only with --no-sandbox, since Chrome refuses it while sandboxed. A sandboxed Linux launch that fails at startup retries once without the sandbox and warns. HYPERFRAMES_CHROME_SANDBOX=1|0 overrides the choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
The off-macOS closed-pipe case now passes --no-sandbox, so it still pins "no seatbelt relaunch" rather than the new Linux sandbox retry, which gets its own case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit accuracy: accurate 267 (base branch 267), smooth 104 of thoseThe gate passes. Quarantined, measured but not gated (8)
Unstable (1)
|
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 1 important · 2 nits · be8366b
Host table and wiring check out. I ran chrome-headless-shell 157 here: a non-root sandboxed launch works, including --single-process over a pipe (the CLI noListen shape). Fake root, and user namespaces exhausted through max_user_namespaces (no sysctl row covers that), both fail as TargetCloseError, so the retry catches them. Findings are inline.
Scope
Read in full: chromeSandbox.ts and its tests, the browserManager.ts launchBrowser/buildChromeArgs hunks, and cli/src/browser/launch.ts. I checked every buildChromeArgs caller: each one launches through acquireBrowser or launchManagedBrowser, so all of them get the fallback. HDR capture still hardcodes --no-sandbox (unchanged). CI: every workflow on this head has a successful run; the cancelled runs are superseded duplicates. No prior reviews.
— Genesis
| timeout: fingerprint.browserTimeoutMs, | ||
| protocolTimeout: fingerprint.protocolTimeoutMs, | ||
| }), | ||
| ).catch(async (error: unknown) => { |
There was a problem hiding this comment.
nit: When the unsandboxed retry fails too, the retry's error lands in this catch, but defaults (a few lines down) is built from the original sandboxed args. diagnoseChromeStartup then re-runs the sandboxed shape. On a host where the sandbox is refused and something else is also broken, the appended diagnosis shows the sandbox failure, not the real one. The same applies to withStartupDiagnosis(..., options, ...) in cli/src/browser/launch.ts. Diagnosing with withoutChromeSandbox(args) after a retry, or after the latch is set, would fix it.
There was a problem hiding this comment.
Deferred to a follow-up, as the author proposed. It only changes the diagnostic text when both launches fail.
…k on clear evidence only With the override set to 1, a sandboxed launch Chrome refuses now fails instead of retrying unsandboxed. A bare startup exit (TargetCloseError) retries that launch without the sandbox but only latches the sandbox off after a second one, so a single unrelated early kill no longer disables it for the rest of a long-lived process. Chrome naming the sandbox in its error still latches at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Local renders now keep Chrome's sandbox on where the OS can provide it (macOS, Windows, and Linux for a non-root user outside a container). Root, container, Lambda and Cloud Run renders keep running without it, as before. If a sandboxed Linux launch still fails, the render warns once and continues without the sandbox.
HYPERFRAMES_CHROME_SANDBOX=1|0overrides the choice.Claude Opus 5.5
What
buildChromeArgsalways passed--no-sandbox --disable-setuid-sandbox. It now adds them only when this host can't sandbox Chrome. Local CLI and Studio users often render projects they did not write, so the sandbox is worth having on their machines.Why
Hardening for local renders. Turning the sandbox on everywhere would break root, Docker, Lambda and Cloud Run renders, and Linux hosts that restrict unprivileged user namespaces. So the decision is per host, and a launch that fails gets one retry.
Related work
Refs #7623. That issue proposes sandbox-on behind an opt-out env var. This PR takes the host-aware route so existing serverless and container renders keep working. Site-isolation flags are unchanged.
How
chromeSandboxEnabled()inpackages/engine/src/services/chromeSandbox.ts:HYPERFRAMES_CHROME_SANDBOX=1/=0getuid() === 0)AWS_LAMBDA_FUNCTION_NAMEorAWS_EXECUTION_ENVK_SERVICE,CLOUD_RUN_JOBorKUBERNETES_SERVICE_HOST/.dockerenvor/run/.containerenvkernel.apparmor_restrict_unprivileged_userns=1(Ubuntu 23.10+) orkernel.unprivileged_userns_clone=0--no-zygotewhile the sandbox is on ("Zygote cannot be disabled if sandbox is enabled"). So--no-zygotenow ships only with--no-sandboxand--disable-setuid-sandbox, as one set. A test pins this.launchWithChromeSandboxFallbackwraps the engine's pooled launch (acquireBrowser: render, Studio thumbnails, audio FX) and the CLI'slaunchManagedBrowser(validate,layout,snapshot, motion shots, composition frames). On Linux, if a sandboxed launch fails at startup, it retries once without the sandbox and prints one warning. The sandbox stays off for the rest of the process once Chrome names it as the cause, or after a second bare startup exit (one alone may be an unrelated kill). WithHYPERFRAMES_CHROME_SANDBOX=1there is no retry: a refused launch fails. "Fails at startup" means Chrome's sandbox error text, or theTargetCloseErrora pipe launch reports when Chrome exits during startup. Timeouts, aborts and other errors are rethrown unchanged.buildChromeArgsacceptssandbox?: booleanfor callers and tests that need a fixed answer.--no-sandboxthemselves. The Lambda and Cloud Run images resolve to "off" by env or root, including the Cloud Run Dockerfile's build-timebuildChromeArgscall, which runs as root.Test plan
chromeSandbox.test.tscovers each platform, root, Lambda (both vars), Cloud Run service and job, Kubernetes, Docker, Podman, both sysctls and the override. It also pins--no-zygoteagainst the sandbox, and checks the fallback: retry once, warn once, skip the sandbox afterwards, no retry on unrelated errors or on macOS/Windows, and the retry's own error surfaces.sandboxDenied.test.ts(CLI): a closed pipe launch on Linux retries once throughlaunchManagedBrowserwith the no-sandbox set. The existing off-macOS case now passes--no-sandbox, so it still pins "no seatbelt relaunch" (unwiring the CLI fallback fails the new case).--no-zygotefrom the no-sandbox set fails 1. Removing the retry fails 3. Removing the root check fails 1. Removing the AppArmor sysctl check fails 1.--no-sandbox, zygote processes present), no warning;max_user_namespaces=1): Chrome failed with "No usable sandbox!", the render warned once, retried without the sandbox and finished;All three MP4s are byte-identical.
hyperframes snapshotunder blocked namespaces took the CLI fallback the same way.docs/packages/cli.mdx).— Rames Jusso
🤖 Generated with Claude Code