Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5b207ec
feat(vercel): add Stagehand code-mode MCP example
shrey150 Aug 6, 2026
85e9ac3
test(integrations): smoke Vercel code mode in CI
shrey150 Aug 6, 2026
95902d6
Merge updated code-mode guidance parent
shrey150 Aug 6, 2026
eca28f1
Merge updated code-mode guidance parent
shrey150 Aug 7, 2026
fd2003a
Merge updated code-mode guidance parent
shrey150 Aug 7, 2026
175128f
Merge refreshed code-mode guidance parent
shrey150 Aug 7, 2026
f95a3bd
fix(vercel): use current agent instructions field
shrey150 Aug 7, 2026
d41e0f9
feat: add sandboxed code-mode image
shrey150 Aug 8, 2026
a434b4c
Merge remote-tracking branch 'origin/shrey/stg-2765-codemode-sandbox'…
shrey150 Aug 8, 2026
bd2c800
feat(vercel): run code mode in an E2B sandbox
shrey150 Aug 8, 2026
e4bbe74
fix: cover tsconfig changes in image CI
shrey150 Aug 8, 2026
c43ed42
Merge sandbox foundation updates
shrey150 Aug 8, 2026
df3959d
feat: add a source-installed E2B MCP boundary
shrey150 Aug 8, 2026
a804d84
fix: make E2B egress default deny
shrey150 Aug 8, 2026
8155c03
fix: keep MCP session setup offline
shrey150 Aug 8, 2026
8cf5b40
fix: poll E2B build readiness safely
shrey150 Aug 8, 2026
3a54a90
fix: terminate the offline setup branch
shrey150 Aug 8, 2026
bc92188
fix: validate the offline source mirror
shrey150 Aug 8, 2026
f8c6e80
docs: bound E2B custom servers to one tool call
shrey150 Aug 8, 2026
a6c7954
feat: run code-mode MCP in Vercel Sandbox
shrey150 Aug 8, 2026
ec7a7ee
fix: use the locked sandbox gateway
shrey150 Aug 8, 2026
e9c6682
fix(vercel): harden sandbox lifecycle boundary
shrey150 Aug 8, 2026
938efe1
test(vercel): make sandbox contracts deterministic
shrey150 Aug 8, 2026
48b87ba
Merge remote-tracking branch 'origin/shrey/stg-2765-codemode-publish'…
shrey150 Aug 8, 2026
4678772
refactor(vercel): install exact code-mode package artifacts
shrey150 Aug 8, 2026
245c2bb
test(vercel): cover invalid proxy port configuration
shrey150 Aug 8, 2026
75aa26c
fix(vercel): harden package-installed sandbox setup
shrey150 Aug 8, 2026
6a01338
fix(ci): register sandbox build task
shrey150 Aug 8, 2026
63ddf58
fix(vercel): close remaining setup review gaps
shrey150 Aug 8, 2026
35c1578
test(vercel): lock credential and pack failure branches
shrey150 Aug 8, 2026
e3e70f2
test(vercel): make stderr sentinel deterministic
shrey150 Aug 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/codemode-framework-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Code-mode sandbox examples

on:
pull_request:
types: [opened, synchronize, reopened, labeled]
paths:
- ".github/workflows/codemode-framework-examples.yml"
- "packages/integrations/**"
- "packages/extension/**"
- "packages/protocol/**"
- "packages/sdk-ts/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "tsconfig.json"
- "turbo.json"
push:
branches: [main, v4-spike]
paths:
- ".github/workflows/codemode-framework-examples.yml"
- "packages/integrations/**"
- "packages/extension/**"
- "packages/protocol/**"
- "packages/sdk-ts/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "tsconfig.json"
- "turbo.json"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
example:
name: ${{ matrix.name }}
if: >-
github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name == github.repository ||
contains(github.event.pull_request.labels.*.name, 'safe-to-test')
runs-on: ubuntu-latest
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- name: Vercel Sandbox source-installed MCP
package: "@browserbasehq/stagehand-integrations-example-vercel-sandbox"
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: ./.github/actions/setup-node-pnpm
with:
use-prebuilt-artifacts: "false"

- uses: ./.github/actions/setup-chrome-verified
id: setup-chrome

- run: pnpm exec turbo run build --filter @browserbasehq/stagehand-integrations
- run: pnpm --filter ${{ matrix.package }} typecheck
- run: pnpm --filter ${{ matrix.package }} test:contract
- run: pnpm --filter ${{ matrix.package }} smoke
env:
CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
STAGEHAND_BROWSER: local
8 changes: 8 additions & 0 deletions packages/integrations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ STAGEHAND_BROWSER=browserbase

The process stays alive across calls and closes when its input stream ends. `SIGINT` and `SIGTERM` perform bounded graceful cleanup and preserve signal-style exit codes. If generated JavaScript blocks the JavaScript event loop, the server cannot run its cleanup handlers. The owner must terminate the entire process tree, escalate to `SIGKILL` after its own deadline, and start a new process before accepting more work. Killing only the Node process can leave its local browser child alive.

### Framework examples

- [Vercel Sandbox](./examples/vercel-sandbox) source-installs the stdio server inside a Firecracker microVM and returns a framework-neutral, bearer-authenticated MCP connection.

### Configuration

`stagehandCodeConfigFromEnv()` recognizes:
Expand All @@ -69,3 +73,7 @@ Native callers run generated JavaScript in their own process. An `AbortSignal` c
### Security boundary

The code-mode executor does not provide a sandbox. Generated JavaScript runs in the host process and inherits that process's filesystem, network, and environment access. A framework may place the tool inside its own sandbox, container, or other isolation boundary.

For untrusted generated code, use the source-installed microVM architecture in the
[Vercel Sandbox example](./examples/vercel-sandbox). The sandbox provider supplies the security
boundary.
143 changes: 143 additions & 0 deletions packages/integrations/examples/vercel-sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Run Stagehand code mode in Vercel Sandbox

Use this example when an agent framework runs on your host but Stagehand code mode must execute
untrusted JavaScript behind a microVM boundary.

```text
Your MCP client
└─ bearer-authenticated Streamable HTTP
└─ Vercel Sandbox exposed port
└─ SHA-256 auth proxy (stagehand-proxy user)
└─ stateful HTTP-to-stdio bridge (stagehand-mcp user)
└─ Stagehand MCP over stdio
└─ generated JavaScript + Browserbase browser
```

The private workspace package exports one framework-neutral contract:

```ts
type StagehandSandboxConnection = {
url: URL;
token: string;
close: () => Promise<void>;
};
```

`createStagehandSandbox()` creates a fresh Vercel Firecracker microVM with open setup egress, checks
out a complete Stagehand commit, installs its frozen lockfile, builds code mode from source, and
installs the pinned HTTP-to-stdio bridge. Before the MCP server starts, it replaces setup egress with
an allowlist containing only Browserbase's API and the regional CDP hostname discovered for the
configured project.

## Install and run

Authenticate the host for [Vercel Sandbox](https://vercel.com/docs/vercel-sandbox), then set:

```bash
STAGEHAND_REVISION=<40-character-stagehand-commit>
BROWSERBASE_API_KEY=<browserbase-api-key>
BROWSERBASE_PROJECT_ID=<browserbase-project-id>

pnpm --filter @browserbasehq/stagehand-integrations-example-vercel-sandbox e2e
```

The revision must be a full commit hash. This prevents the trusted install from following a moving
branch or tag. Vercel's credential-brokering header transforms are currently available on Pro and
Enterprise plans. Check the
[credential-brokering announcement](https://vercel.com/changelog/safely-inject-credentials-in-http-headers-with-vercel-sandbox)
before relying on this example with another plan.

Vercel credentials authenticate the host to the Sandbox control plane so it can create, update,
stop, and delete the microVM. They are separate from the random application bearer returned by this
helper, which protects only the MCP port exposed by this sandbox.

## Connect an MCP client

This raw [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk)
example is the adapter boundary that agent frameworks build on:

```ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import {
createStagehandSandbox,
stagehandTransport,
} from "@browserbasehq/stagehand-integrations-example-vercel-sandbox";

const stagehand = await createStagehandSandbox({
stagehandRevision: process.env.STAGEHAND_REVISION!,
browserbaseApiKey: process.env.BROWSERBASE_API_KEY!,
browserbaseProjectId: process.env.BROWSERBASE_PROJECT_ID!,
});
const client = new Client({ name: "my-agent", version: "1.0.0" });

try {
await client.connect(stagehandTransport(stagehand));
const tools = await client.listTools();
console.log(tools);
} finally {
await client.close();
await stagehand.close();
}
```

Create one external MCP client and one MCP session per sandbox. The bridge holds one Stagehand stdio
process for that session, so browser pages, DOM changes, cookies, and guest files survive across tool
calls. Destroy the sandbox after the agent run; generated JavaScript can mutate its guest filesystem,
so reconnecting or reusing that VM would cross a trust boundary.

The authenticated `/mcp` endpoint accepts POST and DELETE. It returns `405 Method Not Allowed` for
the optional standalone GET event stream because Vercel's public edge buffers an idle SSE response
and Stagehand does not send server-initiated notifications. MCP calls still stream their responses
over POST.

## Use the cross-language lease

Python and other non-Node adapters can launch the same provider implementation without copying its
setup or network-policy logic:

```bash
pnpm --filter @browserbasehq/stagehand-integrations-example-vercel-sandbox lease
```

The launcher writes exactly one JSON line to stdout:

```json
{ "url": "https://<sandbox-domain>/mcp", "token": "<bearer-token>" }
```

It then holds stdin open as the sandbox lease. Keep the process and stdin pipe alive for the entire
MCP session. Close stdin for normal cleanup; `SIGINT` and `SIGTERM` trigger bounded cleanup and retain
signal-style exit semantics. Spawn it with an explicit environment allowlist containing only the
runtime variables it needs: `PATH`, the relevant Vercel authentication variables,
`STAGEHAND_REVISION`, `BROWSERBASE_API_KEY`, and `BROWSERBASE_PROJECT_ID`. The token is emitted once
over the trusted parent pipe and is never placed in command arguments or environment variables.

## Security boundary

[Vercel Sandbox](https://vercel.com/sandbox) supplies the microVM boundary. Process users are an
additional defense inside that VM, not a substitute for it:

- `stagehand-mcp` runs supergateway, the Stagehand stdio server, and generated JavaScript without
sudo.
- `stagehand-proxy` runs only the exposed-port auth proxy without sudo. Its bootstrap environment
receives the SHA-256 digest of a random 32-byte bearer, not the raw bearer.
- The host retains the raw bearer and Browserbase key. The guest MCP process receives a fixed
placeholder key. Vercel's [credential-brokering transform](https://vercel.com/changelog/safely-inject-credentials-in-http-headers-with-vercel-sandbox)
overwrites the Browserbase API header at the network boundary.
- The host discovers and validates the exact regional Browserbase CDP hostname before lockdown. The
running VM allows only that hostname and `api.browserbase.com`; all other egress is denied.
- Source, dependencies, and bridge code become root-owned and read-only before untrusted code runs.
- The only published guest port is the authenticated proxy. The stateful bridge listens on guest
loopback.

Credential brokering prevents key disclosure, but it still grants the sandbox the Browserbase API
capabilities of that key. Use a separately scoped project/key and host-side timeouts. AI-backed
Stagehand methods require a separately scoped model credential and an exact provider-host policy;
this example intentionally does not forward outer-agent model keys or broaden egress.

The Vercel policy constrains network requests made by guest processes. The browser itself runs
remotely on Browserbase, so this policy does not restrict which URLs that browser can navigate to.
Apply separate browser-navigation controls when the agent must stay within an approved site set.

`close()` is idempotent and attempts both stop and permanent delete even when one cleanup operation
fails. The lease adds a bounded fallback. Always close the MCP client first, then the connection.
31 changes: 31 additions & 0 deletions packages/integrations/examples/vercel-sandbox/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@browserbasehq/stagehand-integrations-example-vercel-sandbox",
"version": "4.0.0",
"private": true,
"type": "module",
"exports": {
".": "./src/sandbox.ts",
"./lease": "./src/lease.ts"
},
"scripts": {
"e2e": "tsx src/e2e.ts",
"lease": "tsx src/lease.ts",
"smoke": "tsx src/smoke.ts",
"test:contract": "node --test src/*.test.mjs src/guest/*.test.mjs",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@browserbasehq/stagehand-integrations": "workspace:*",
"@modelcontextprotocol/sdk": "catalog:",
"@vercel/sandbox": "catalog:",
"supergateway": "catalog:"
},
"devDependencies": {
"@types/node": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:"
},
"engines": {
"node": ">=22.18.0"
}
}
Loading
Loading