feat: Allow sandbox-SDK user-land control over HTTP requests from the sandbox#117
Open
feat: Allow sandbox-SDK user-land control over HTTP requests from the sandbox#117
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
6dfea49 to
f614801
Compare
Add two new packages that allow JS callers to intercept and control all
outbound HTTP and HTTPS requests from inside a Vercel Sandbox. A Go-based
HTTP proxy runs inside the sandbox VM, tunneling requests over WebSocket
to TypeScript callbacks where they can be inspected, modified, or blocked.
## Packages
- `@vercel/http-proxy-server` — Go binary (HTTP proxy + WS server + TLS MITM)
- `@vercel/http-proxy-tunnel` — TypeScript client (`WsProxy` class)
## Usage
### Basic: intercept all requests (HTTP and HTTPS)
```ts
import { Sandbox } from "@vercel/sandbox";
import { createWsProxy } from "@vercel/http-proxy-tunnel";
const sandbox = await Sandbox.create({ ports: [5000] });
const proxy = createWsProxy();
await proxy.attach(sandbox, { wsPort: 5000 });
const httpProxy = proxy.handle((request) => {
// Works for both http:// and https:// URLs
console.log(`${request.method} ${request.url}`);
return new Response("blocked", { status: 403 });
});
await sandbox.runCommand({
cmd: "curl",
args: ["-s", "https://example.com"],
env: {
HTTP_PROXY: httpProxy, http_proxy: httpProxy,
HTTPS_PROXY: httpProxy, https_proxy: httpProxy,
},
});
```
### HTTPS interception with real fetching
The proxy does TLS MITM — it generates a CA cert at startup, installs
it in the sandbox trust store, and terminates TLS on CONNECT. The JS
handler sees the full decrypted URL and can fetch on behalf of the sandbox:
```ts
const sandbox = await Sandbox.create({
ports: [5000],
networkPolicy: "deny-all", // no direct internet access
});
const proxy = createWsProxy();
await proxy.attach(sandbox, { wsPort: 5000 });
const httpProxy = proxy.handle(async (req) => {
// req.url is "https://vercel.com/robots.txt" — fully decrypted
// The handler runs OUTSIDE the sandbox, so it CAN fetch
return fetch(req.url);
});
const result = await sandbox.runCommand({
cmd: "curl",
args: ["-s", "https://vercel.com/robots.txt"],
env: { HTTPS_PROXY: httpProxy, https_proxy: httpProxy },
});
// result.stdout() contains the actual robots.txt content
```
### Per-command security policies
```ts
const allowList = proxy.handle(async (req) => {
const url = new URL(req.url);
if (url.hostname === "registry.npmjs.org") {
return fetch(req.url);
}
return new Response("Forbidden", { status: 403 });
});
const allowAll = proxy.handle((req) => fetch(req.url));
// Untrusted code can only reach npm
await sandbox.runCommand({
cmd: "node", args: ["untrusted.js"],
env: { HTTP_PROXY: allowList, HTTPS_PROXY: allowList },
});
// Trusted code has full access
await sandbox.runCommand({
cmd: "node", args: ["trusted.js"],
env: { HTTP_PROXY: allowAll, HTTPS_PROXY: allowAll },
});
```
### HTTPS CONNECT allow/deny
The optional `connectHandler` controls whether HTTPS connections are
allowed before MITM begins:
```ts
const httpProxy = proxy.handle(
async (req) => fetch(req.url),
(host) => host.endsWith(".github.com"), // only MITM GitHub
);
```
### Multiple independent clients sharing one sandbox
```ts
const proxyA = createWsProxy();
await proxyA.attach(sandbox, { wsPort: 5000 }); // starts server
const proxyB = createWsProxy();
await proxyB.attach(sandbox, { wsPort: 5000 }); // reuses existing server
const handleA = proxyA.handle(() => new Response("from A"));
const handleB = proxyB.handle(() => new Response("from B"));
// Requests route to the correct client via session registration
const [a, b] = await Promise.all([
sandbox.runCommand({ cmd: "curl", args: ["-s", "http://x.com"],
env: { HTTP_PROXY: handleA } }),
sandbox.runCommand({ cmd: "curl", args: ["-s", "http://x.com"],
env: { HTTP_PROXY: handleB } }),
]);
// a.stdout() === "from A", b.stdout() === "from B"
```
## Architecture
- Session routing via HTTP proxy auth (`http://<sessionId>:x@localhost:<port>`)
- JSON protocol over WebSocket (request/response with base64 bodies)
- HTTPS MITM: in-memory CA + per-hostname leaf certs, auto-installed in sandbox trust store
- Multi-client: Go hub tracks session→client mappings via register/unregister
- Config persistence at `/tmp/vercel/http-proxy/config.json` for server reuse
## Tests
- 25 Go unit tests (protocol, hub, proxy server, MITM, session extraction)
- 8 TypeScript process tests (spawn binary, WS client, HTTP/HTTPS proxy)
- 8 sandbox integration tests (real Sandbox, deny-all + HTTPS fetch, multi-client)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
f614801 to
6bc3bf7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add two new packages that allow JS callers to intercept and control all outbound HTTP and HTTPS requests from inside a Vercel Sandbox. A Go-based HTTP proxy runs inside the sandbox VM, tunneling requests over WebSocket to TypeScript callbacks where they can be inspected, modified, or blocked.
@vercel/http-proxy-server— Go binary (HTTP proxy + WS server + TLS MITM)@vercel/http-proxy-tunnel— TypeScript client (WsProxyclass)Usage
Basic: intercept all requests (HTTP and HTTPS)
HTTPS interception with real fetching
The proxy does TLS MITM — it generates a CA cert at startup, installs it in the sandbox trust store, and terminates TLS on CONNECT. The JS handler sees the full decrypted URL and can fetch on behalf of the sandbox:
Per-command security policies
HTTPS CONNECT allow/deny
The optional connectHandler controls whether HTTPS connections are allowed before MITM begins:
Multiple independent clients sharing one sandbox
ProxyHandle object
proxy.handle()returns aProxyHandlewith:handle.url— the raw proxy URL stringhandle.env— ready-made env record (HTTP_PROXY,http_proxy,HTTPS_PROXY,https_proxy)handle.toString()— returns URL for string coercionArchitecture
http://<sessionId>:x@localhost:<port>)Test plan