Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
97 changes: 97 additions & 0 deletions .github/workflows/codemode-framework-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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 package-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-codemode
- run: pnpm --filter ${{ matrix.package }} typecheck
- run: pnpm --filter ${{ matrix.package }} test:contract
- run: pnpm --filter ${{ matrix.package }} pack:artifacts
- run: pnpm --filter ${{ matrix.package }} smoke
env:
CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
STAGEHAND_BROWSER: local
- name: Detect live test credentials
id: live-credentials
env:
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
VERCEL_OIDC_TOKEN: ${{ secrets.VERCEL_OIDC_TOKEN }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
if [[ -n "$BROWSERBASE_API_KEY" && -n "$BROWSERBASE_PROJECT_ID" ]] && \
[[ -n "$VERCEL_OIDC_TOKEN" || ( -n "$VERCEL_TEAM_ID" && -n "$VERCEL_PROJECT_ID" && -n "$VERCEL_TOKEN" ) ]]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
fi
- name: Run live package-installed sandbox proof
if: steps.live-credentials.outputs.available == 'true'
run: pnpm --filter ${{ matrix.package }} e2e
env:
STAGEHAND_SANDBOX_ARTIFACTS: ${{ github.workspace }}/packages/integrations/examples/vercel-sandbox/.artifacts
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
VERCEL_OIDC_TOKEN: ${{ secrets.VERCEL_OIDC_TOKEN }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_modules
dist
dist-ssr
packages/extension/artifacts
packages/integrations/examples/vercel-sandbox/.artifacts/
.pnpm-store
*.local
*.tgz
Expand Down
5 changes: 5 additions & 0 deletions packages/integrations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,8 @@ The published package includes the model-facing assets as exported package files

The first public release intentionally supports the executable and these two assets only. Internal
implementation modules and an in-process arbitrary-code executor are not public package APIs.

## Framework examples

- [Vercel Sandbox](./examples/vercel-sandbox) installs the exact packed artifact inside a
Firecracker microVM and returns a framework-neutral, bearer-authenticated MCP connection.
161 changes: 161 additions & 0 deletions packages/integrations/examples/vercel-sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# 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, uploads
the exact packed Stagehand and code-mode artifacts supplied by the trusted host, verifies their
SHA-256 digests in the guest, and installs them with 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
pnpm --filter @browserbasehq/stagehand-integrations-example-vercel-sandbox pack:artifacts

STAGEHAND_SANDBOX_ARTIFACTS="$PWD/packages/integrations/examples/vercel-sandbox/.artifacts"
BROWSERBASE_API_KEY=<browserbase-api-key>
BROWSERBASE_PROJECT_ID=<browserbase-project-id>

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

The artifact directory path must be absolute. The pack step builds the exact checkout under review,
produces stable local tarball names, and records an integrity-pinned npm installation lock; no
moving branch, tag, registry package version, or guest-side repository checkout participates in the
proof. 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. The SDK uses
`VERCEL_OIDC_TOKEN` when available. Outside Vercel, pass `{ teamId, projectId, token }` as
`vercelCredentials`; the E2E and lease derive it from `VERCEL_TEAM_ID`, `VERCEL_PROJECT_ID`, and
`VERCEL_TOKEN` when the token is present.

## 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({
packageArtifactsPath: process.env.STAGEHAND_SANDBOX_ARTIFACTS!,
browserbaseApiKey: process.env.BROWSERBASE_API_KEY!,
browserbaseProjectId: process.env.BROWSERBASE_PROJECT_ID!,
...(process.env.VERCEL_TOKEN
? {
vercelCredentials: {
teamId: process.env.VERCEL_TEAM_ID!,
projectId: process.env.VERCEL_PROJECT_ID!,
token: process.env.VERCEL_TOKEN,
},
}
: {}),
});
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_SANDBOX_ARTIFACTS`, `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.
- Installed package artifacts, 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.
33 changes: 33 additions & 0 deletions packages/integrations/examples/vercel-sandbox/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@browserbasehq/stagehand-integrations-example-vercel-sandbox",
"version": "4.0.0",
"private": true,
"type": "module",
"exports": {
".": "./src/sandbox.ts",
"./lease": "./src/lease.ts"
},
"scripts": {
"build": "tsc --noEmit",
"e2e": "tsx src/e2e.ts",
"lease": "tsx src/lease.ts",
"pack:artifacts": "node scripts/pack-artifacts.mjs",
"smoke": "tsx src/smoke.ts",
"test:contract": "tsx --test src/*.test.ts src/*.test.mjs src/guest/*.test.mjs",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@browserbasehq/stagehand-codemode": "workspace:*",
"@modelcontextprotocol/sdk": "catalog:",
"@vercel/sandbox": "catalog:",
"supergateway": "catalog:"
},
"devDependencies": {
"@types/node": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:"
},
"engines": {
"node": ">=22.18.0"
}
}
Loading
Loading