Skip to content

Commit 4c4b208

Browse files
committed
fix(sandbox): undefine the raw fetch host bridge before user code runs
1 parent be20df9 commit 4c4b208

2 files changed

Lines changed: 62 additions & 5 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @vitest-environment node
3+
*
4+
* Guards the isolate hardening contract in `isolated-vm-worker.cjs`: no raw
5+
* `ivm.Reference` host bridge may survive as an isolate global once user code
6+
* runs. The bootstrap must capture each bridge in a closure and list its global
7+
* name in `undefined_globals`.
8+
*/
9+
import { readFileSync } from 'node:fs'
10+
import { join } from 'node:path'
11+
import { describe, expect, it } from 'vitest'
12+
13+
const WORKER_SOURCE = readFileSync(join(__dirname, 'isolated-vm-worker.cjs'), 'utf8')
14+
15+
/** Global names bound to a raw `ivm.Reference` (as opposed to an `ivm.Callback`). */
16+
const REFERENCE_BRIDGES = [
17+
'__fetchRef',
18+
'__brokerRef',
19+
'__setTimeoutRef',
20+
'__clearTimeoutRef',
21+
'__setIntervalRef',
22+
]
23+
24+
function hardeningLists(): string[] {
25+
const lists = WORKER_SOURCE.match(/const undefined_globals = \[[\s\S]*?\]/g)
26+
expect(lists).not.toBeNull()
27+
return lists as string[]
28+
}
29+
30+
describe('isolated-vm worker hardening', () => {
31+
it('undefines every ivm.Reference bridge it installs as a global', () => {
32+
const lists = hardeningLists()
33+
expect(lists.length).toBeGreaterThanOrEqual(2)
34+
35+
for (const bridge of REFERENCE_BRIDGES) {
36+
const installed = WORKER_SOURCE.includes(`jail.set('${bridge}'`)
37+
if (!installed) continue
38+
const undefinedSomewhere = lists.some((list) => list.includes(`'${bridge}'`))
39+
expect(undefinedSomewhere, `${bridge} is set as an isolate global but never undefined`).toBe(
40+
true
41+
)
42+
}
43+
})
44+
45+
it('keeps the isolated-vm escape globals in every hardening list', () => {
46+
for (const list of hardeningLists()) {
47+
for (const name of ['Isolate', 'Context', 'Script', 'Reference', 'ExternalCopy']) {
48+
expect(list).toContain(`'${name}'`)
49+
}
50+
}
51+
})
52+
})

apps/sim/lib/execution/isolated-vm-worker.cjs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,12 @@ async function executeCode(request, executionId) {
310310
info: (...args) => __log(...args),
311311
};
312312
313-
// Set up fetch function that uses the host's secure fetch
314-
async function fetch(url, options) {
313+
// Set up fetch function that uses the host's secure fetch. The raw
314+
// host bridge is captured in this closure so the hardening step below
315+
// can undefine the global without breaking fetch().
316+
(() => {
317+
const __fetch = globalThis.__fetchRef;
318+
globalThis.fetch = async function fetch(url, options) {
315319
let optionsJson;
316320
if (options) {
317321
try {
@@ -323,7 +327,7 @@ async function executeCode(request, executionId) {
323327
throw new Error('fetch options exceed maximum payload size');
324328
}
325329
}
326-
const resultJson = await __fetchRef.apply(undefined, [url, optionsJson], { result: { promise: true } });
330+
const resultJson = await __fetch.apply(undefined, [url, optionsJson], { result: { promise: true } });
327331
let result;
328332
try {
329333
result = JSON.parse(resultJson);
@@ -355,7 +359,8 @@ async function executeCode(request, executionId) {
355359
blob: async () => { throw new Error('blob() not supported in sandbox'); },
356360
arrayBuffer: async () => { throw new Error('arrayBuffer() not supported in sandbox'); },
357361
};
358-
}
362+
};
363+
})();
359364
360365
const sim = (() => {
361366
const broker = __brokerRef;
@@ -408,7 +413,7 @@ async function executeCode(request, executionId) {
408413
const undefined_globals = [
409414
'Isolate', 'Context', 'Script', 'Module', 'Callback', 'Reference',
410415
'ExternalCopy', 'process', 'require', 'module', 'exports', '__dirname', '__filename',
411-
'__brokerRef', '__broker', '__callSimBroker'
416+
'__fetchRef', '__brokerRef', '__broker', '__callSimBroker'
412417
];
413418
for (const name of undefined_globals) {
414419
try {

0 commit comments

Comments
 (0)