History

Enhance sandbox security and testing capabilities

- Updated tests in `runtime-core.test.ts` to improve checks against global constructor and eval escapes, ensuring that sensitive host globals are not exposed. - Modified `runCodeWithAdapter` in `runtime-core.ts` and `vercel-sandbox-runtime.ts` to utilize a sandboxed context with stricter code generation settings, enhancing security during code execution. - Added new tests to verify that prototype pollution does not leak to the host environment, further strengthening the sandbox's isolation features.

Rhys Sullivan committed 9 months ago

Commit fe78c871 parent 70e652d3 files · +84 −5Browse files

Unified · Split

3 files
  1. executor/apps/server/src/runtimes/runtime-core.test.ts +70 −3
  2. executor/apps/server/src/runtimes/runtime-core.ts +7 −1
  3. executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts +7 −1
executor/apps/server/src/runtimes/runtime-core.test.ts+70−3
@@ -135,17 +135,26 @@ test("sandboxed runner does not expose host globals", async () => { · 134 unmodified lines
135 135
136 const result = await runCodeWithAdapter(136 const result = await runCodeWithAdapter(
137 request(`137 request(`
138 let fsEscape = "unknown";
139 try {
140 fsEscape = [].constructor.constructor("return typeof process")();
141 } catch {
142 fsEscape = "blocked";
143 }
144
138 const checks = {145 const checks = {
139 process: typeof process,146 process: typeof process,
140 bun: typeof Bun,147 bun: typeof Bun,
141 fetch: typeof fetch,148 fetch: typeof fetch,
142 fsEscape: [].constructor.constructor("return typeof process")(),149 fsEscape,
143 };150 };
144 console.log(JSON.stringify(checks));151 console.log(JSON.stringify(checks));
145 if (checks.process !== "undefined") throw new Error("process leaked");152 if (checks.process !== "undefined") throw new Error("process leaked");
146 if (checks.bun !== "undefined") throw new Error("Bun leaked");153 if (checks.bun !== "undefined") throw new Error("Bun leaked");
147 if (checks.fetch !== "undefined") throw new Error("fetch leaked");154 if (checks.fetch !== "undefined") throw new Error("fetch leaked");
148 if (checks.fsEscape !== "undefined") throw new Error("constructor escape leaked");155 if (checks.fsEscape !== "undefined" && checks.fsEscape !== "blocked") {
156 throw new Error("constructor escape leaked");
157 }
149 return "ok";158 return "ok";
150 `),159 `),
151 adapter,160 adapter,
@@ -155,7 +164,65 @@ test("sandboxed runner does not expose host globals", async () => { · 3 unmodified lines
155 expect(result.stdout).toContain("\"process\":\"undefined\"");164 expect(result.stdout).toContain("\"process\":\"undefined\"");
156 expect(result.stdout).toContain("\"bun\":\"undefined\"");165 expect(result.stdout).toContain("\"bun\":\"undefined\"");
157 expect(result.stdout).toContain("\"fetch\":\"undefined\"");166 expect(result.stdout).toContain("\"fetch\":\"undefined\"");
158 expect(result.stdout).toContain("\"fsEscape\":\"undefined\"");167 expect(result.stdout).toMatch(/"fsEscape":"(undefined|blocked)"/);
168});
169
170test("sandbox blocks global constructor and eval escapes", async () => {
171 const adapter = createRuntimeAdapter(new Map(), []);
172
173 const result = await runCodeWithAdapter(
174 request(`
175 let ctorEscape = "unknown";
176 let evalEscape = "unknown";
177 let functionEscape = "unknown";
178
179 try {
180 ctorEscape = globalThis.constructor.constructor("return typeof process")();
181 } catch {
182 ctorEscape = "blocked";
183 }
184
185 try {
186 evalEscape = eval("typeof process");
187 } catch {
188 evalEscape = "blocked";
189 }
190
191 try {
192 functionEscape = Function("return typeof process")();
193 } catch {
194 functionEscape = "blocked";
195 }
196
197 console.log(JSON.stringify({ ctorEscape, evalEscape, functionEscape }));
198 if (ctorEscape !== "undefined" && ctorEscape !== "blocked") throw new Error("ctor escape");
199 if (evalEscape !== "undefined" && evalEscape !== "blocked") throw new Error("eval escape");
200 if (functionEscape !== "undefined" && functionEscape !== "blocked") throw new Error("Function escape");
201 `),
202 adapter,
203 );
204
205 expect(result.status).toBe("completed");
206 expect(result.stdout).toMatch(/"ctorEscape":"(undefined|blocked)"/);
207 expect(result.stdout).toMatch(/"evalEscape":"(undefined|blocked)"/);
208 expect(result.stdout).toMatch(/"functionEscape":"(undefined|blocked)"/);
209});
210
211test("sandbox prototype pollution does not leak to host", async () => {
212 const marker = `__pwned_${crypto.randomUUID().replace(/-/g, "")}`;
213 expect(({} as Record<string, unknown>)[marker]).toBeUndefined();
214
215 const adapter = createRuntimeAdapter(new Map(), []);
216 const result = await runCodeWithAdapter(
217 request(`
218 Object.prototype[${JSON.stringify(marker)}] = "yes";
219 return "done";
220 `),
221 adapter,
222 );
223
224 expect(result.status).toBe("completed");
225 expect(({} as Record<string, unknown>)[marker]).toBeUndefined();
159});226});
160 227
161test("runs openapi and graphql sourced tools through the runtime", async () => {228test("runs openapi and graphql sourced tools through the runtime", async () => {
executor/apps/server/src/runtimes/runtime-core.ts+7−1
@@ -104,12 +104,18 @@ export async function runCodeWithAdapter( · 103 unmodified lines
104 error: (...args: unknown[]) => appendStderr(formatArgs(args)),104 error: (...args: unknown[]) => appendStderr(formatArgs(args)),
105 };105 };
106 106
107 const context = createContext({107 const sandbox = Object.assign(Object.create(null), {
108 tools,108 tools,
109 console: consoleProxy,109 console: consoleProxy,
110 setTimeout,110 setTimeout,
111 clearTimeout,111 clearTimeout,
112 });112 });
113 const context = createContext(sandbox, {
114 codeGeneration: {
115 strings: false,
116 wasm: false,
117 },
118 });
113 119
114 const runnerScript = new Script(`(async () => {\n"use strict";\n${request.code}\n})()`);120 const runnerScript = new Script(`(async () => {\n"use strict";\n${request.code}\n})()`);
115 121
executor/apps/server/src/runtimes/vercel-sandbox-runtime.ts+7−1
@@ -146,12 +146,18 @@ const consoleProxy = { · 145 unmodified lines
146 error: (...args) => appendStderr(formatArgs(args)),146 error: (...args) => appendStderr(formatArgs(args)),
147};147};
148 148
149const context = vm.createContext({149const sandbox = Object.assign(Object.create(null), {
150 tools,150 tools,
151 console: consoleProxy,151 console: consoleProxy,
152 setTimeout,152 setTimeout,
153 clearTimeout,153 clearTimeout,
154});154});
155const context = vm.createContext(sandbox, {
156 codeGeneration: {
157 strings: false,
158 wasm: false,
159 },
160});
155const runnerScript = new vm.Script("(async () => {\\n\"use strict\";\\n" + userCode + "\\n})()");161const runnerScript = new vm.Script("(async () => {\\n\"use strict\";\\n" + userCode + "\\n})()");
156 162
157const timeoutPromise = new Promise((_, reject) => {163const timeoutPromise = new Promise((_, reject) => {