113 lines · 4.5 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect"; import { executeSealedBundle } from "./index"; // The sealed-bundle entry: a self-contained program in, one string out, with// nothing of the host reachable from inside. The artifact smoke render is its// caller, but nothing here knows that — these are the primitive's own promises. const run = (options: Parameters<typeof executeSealedBundle>[0]) =>  Effect.runPromise(    executeSealedBundle(options).pipe(      Effect.map((value) => ({ ok: true as const, value })),      Effect.catch((error) => Effect.succeed({ ok: false as const, message: error.message })),    ),  ); describe("executeSealedBundle", () => {  it("returns what the call resolves", async () => {    const result = await run({      bundle: "globalThis.greet = (name) => Promise.resolve('hello ' + name);",      call: "globalThis.greet('world')",    });    expect(result).toEqual({ ok: true, value: "hello world" });  });   it("reports a throw from the bundle itself", async () => {    const result = await run({ bundle: "throw new Error('bundle is broken');", call: "'unused'" });    expect(result.ok).toBe(false);    expect(result.ok === false ? result.message : "").toContain("bundle is broken");  });   it("reports a rejected call", async () => {    const result = await run({      bundle: "globalThis.fail = () => Promise.reject(new Error('nope'));",      call: "globalThis.fail()",    });    expect(result.ok).toBe(false);    expect(result.ok === false ? result.message : "").toContain("nope");  });   it("refuses a non-string result rather than coercing one", async () => {    // The contract is a string. Coercing `{}` to "[object Object]" would hand    // the caller a plausible-looking value that means nothing.    const result = await run({ bundle: "", call: "Promise.resolve({ not: 'a string' })" });    expect(result.ok).toBe(false);    expect(result.ok === false ? result.message : "").toContain("expected a string");  });   describe("containment", () => {    // What the sandbox does NOT have. Asked by `typeof`, which cannot throw, so    // each assertion reads back what is actually there rather than whether a    // reference happened to fail.    it.each([      ["process", "the host's environment and secrets"],      ["fetch", "the network"],      ["require", "the host module graph"],      ["WebAssembly", "a second engine"],    ])("has no %s (%s)", async (name) => {      const result = await run({        bundle: "",        call: `Promise.resolve(typeof globalThis.${name})`,      });      expect(result).toEqual({ ok: true, value: "undefined" });    });     it("cannot reach the host through the Function constructor", async () => {      // `new Function` WORKS in here — QuickJS is an interpreter, so it builds      // a QuickJS function, which is exactly why this runs on workerd. What it      // cannot do is find a host global, because there is not one to find.      const result = await run({        bundle: "",        call: `Promise.resolve(String(Function("return typeof process")()))`,      });      expect(result).toEqual({ ok: true, value: "undefined" });    });     it("cannot import a host module", async () => {      const result = await run({        bundle: "",        call: `import('node:fs').then(() => 'RESOLVED', (e) => 'REJECTED:' + e.message)`,      });      expect(result.ok).toBe(true);      expect(result.ok === true ? result.value : "").toContain("REJECTED:");    });  });   it("preempts a synchronous infinite loop at the deadline", async () => {    // The property the host process cannot give itself: a spinning JS thread    // cannot be interrupted from outside, but QuickJS's interpreter polls an    // interrupt handler and stops.    const started = Date.now();    const result = await run({      bundle: "globalThis.spin = () => { while (true) {} };",      call: "Promise.resolve(globalThis.spin())",      timeoutMs: 1_000,    });    expect(result.ok).toBe(false);    // Bounded, and bounded near the deadline rather than at some later backstop.    expect(Date.now() - started).toBeLessThan(20_000);  }, 60_000);   it("caps memory", async () => {    const result = await run({      bundle:        "globalThis.eat = () => { const held = []; for (;;) held.push(new Array(10000).fill('x')); };",      call: "Promise.resolve(globalThis.eat())",      memoryLimitBytes: 8 * 1024 * 1024,      timeoutMs: 20_000,    });    expect(result.ok).toBe(false);  }, 60_000);});