Commit 6c26d68
authored
fix: repair sandbox browser driver lifecycle (#186)
## Summary
- Fixes browser initialization that was blocked by the driver's own
global provider-fetch restriction.
- Gives the driver explicit ownership of Chromium so startup failures
and shutdowns cannot leak browser processes.
- Makes readiness represent a fully initialized Stagehand context and
reduces stale-driver detection from 90 seconds to 5 seconds.
- Disables PTY echo before credentials are delivered over stdin so
secrets cannot enter Daytona session logs.
## Context
Production acceptance QA exposed this incident directly; there is no
separate Linear issue or plan document.
## Architecture
The browser driver now has two explicit transport boundaries:
1. A provider-scoped, response-bounded fetch implementation injected
into Stagehand's AI SDK client.
2. Native loopback CDP transport whose WebSocket endpoint is fetched
with a byte limit and validated against the exact owned Chromium port.
The driver launches Chromium itself, attaches Stagehand through the
validated CDP URL, and retains the launch handle for deterministic
cleanup. The readiness endpoint returns success only after Stagehand and
the browser connection guard are installed.
## Decisions Made
| Decision | Choice | Alternatives Considered | Reasoning |
|---|---|---|---|
| Provider egress enforcement | Inject a restricted fetch into the AI
SDK provider | Replace global fetch and allow-list local CDP | Keeps
provider security policy scoped to provider I/O and cannot interfere
with browser internals |
| Chromium lifecycle | Driver-owned chrome-launcher process | Let
Stagehand launch Chromium | Gives the driver a handle that can always be
killed, including partial initialization failures |
| Readiness | Eager initialization with stateful readiness | Lazy
initialization from health/actions | Prevents the process manager from
accepting an unusable driver |
| Credential delivery | Disable PTY echo before stdin bootstrap | Redact
logs after capture | Prevents secret persistence at the source |
## Edge Cases Handled
| Scenario | Handling |
|---|---|
| CDP response is oversized or points off loopback | Reject before
Stagehand attaches |
| Stagehand fails after Chromium launches | Close Stagehand, kill the
exact Chromium process, log a sanitized diagnostic, and exit non-zero |
| Driver receives SIGINT, SIGTERM, or expires | Close Stagehand and kill
the owned Chromium process |
| Existing driver is stale | Health probe fails after 5 seconds and is
replaced |
| Daytona PTY echoes stdin | Echo is disabled before bootstrap input is
sent |
| Diagnostic contains a request-scoped secret | Exact credentials and
provider-key patterns are redacted before the bounded message is written
|
## How to Review
1. Start with infra/containers/sandbox/browser-driver/server.js for
transport ownership, readiness, and cleanup.
2. Review infra/containers/sandbox/scripts/start-browser-driver.sh for
the credential-delivery boundary.
3. Review packages/agent-core/src/tools/browser/actions.ts for
stale-driver detection.
4. Confirm the lock and sandbox README match the implementation.
## Verification
- [x] pnpm lint
- [x] pnpm typecheck
- [x] pnpm turbo build --force
- [x] pnpm deadcode
- [x] pnpm architecture:check
- [x] pnpm turbo skills:build
- [x] node --check infra/containers/sandbox/browser-driver/server.js
- [x] git diff --check
- [ ] Protected immutable sandbox snapshot build
- [ ] Promote the reviewed snapshot in the agent-worker configuration
- [ ] Repeat the production Pomodoro build and browser verification flow
- [ ] Confirm bootstrap credentials are absent from the new Daytona
session log
- [ ] Confirm driver termination leaves no owned Chromium process1 parent c730ec0 commit 6c26d68
6 files changed
Lines changed: 245 additions & 75 deletions
File tree
- infra/containers/sandbox
- browser-driver
- scripts
- packages/agent-core/src/tools/browser
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
121 | 123 | | |
122 | 124 | | |
123 | 125 | | |
| |||
137 | 139 | | |
138 | 140 | | |
139 | 141 | | |
140 | | - | |
141 | | - | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
142 | 145 | | |
143 | 146 | | |
144 | 147 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
0 commit comments