diff --git a/packages/kernel/src/kernel/main.ts b/packages/kernel/src/kernel/main.ts index 950e679..052fb12 100644 --- a/packages/kernel/src/kernel/main.ts +++ b/packages/kernel/src/kernel/main.ts @@ -1300,7 +1300,8 @@ function startRuntime(proc: Proc, thread: Thread, init: KernelToRuntimeInit, tra if (child?.parent === thread) finishThread(proc, child, 1, true); } }; - thread.worker.onmessage = (rev: MessageEvent) => handleRuntimeMessage(rev.data); + // The runtime speaks only over the port handed over in init. The worker's own channel + // belongs to guest code, so nothing posted on it is read. // A worker that fails to load or dies outside guest code ends like a crash. thread.worker.onerror = () => { if (thread === proc.main) { diff --git a/packages/kernel/src/runtime/main.ts b/packages/kernel/src/runtime/main.ts index 74a51a2..086d8c5 100644 --- a/packages/kernel/src/runtime/main.ts +++ b/packages/kernel/src/runtime/main.ts @@ -143,6 +143,8 @@ const ctx: RequireContext = { self.onmessage = (ev: MessageEvent): void => { const msg = ev.data; if (msg.t !== "init") return; + // From here on the kernel speaks only over msg.port; the worker scope is the guest's. + self.onmessage = null; kernelPort = msg.port; sab = new SharedArrayBuffer(SAB_BYTES); kernelPort.postMessage({ t: "attach", mailbox: sab } satisfies RuntimeToKernel); diff --git a/packages/kernel/tests/process-runtime.spec.ts b/packages/kernel/tests/process-runtime.spec.ts index 17d93d0..e464f4f 100644 --- a/packages/kernel/tests/process-runtime.spec.ts +++ b/packages/kernel/tests/process-runtime.spec.ts @@ -109,3 +109,18 @@ test("node stays the default runtime", async ({ page }) => { }, "/app/main.mjs"); expect(out).toEqual({ code: 0, signal: null, stdout: "object function function object\n", stderr: "" }); }); + +test("the worker scope belongs to the guest: its postMessage cannot reach the kernel", async ({ page }) => { + const forged = "postMessage({ t: 'stdio', stream: 'stdout', bytes: new TextEncoder().encode('forged\\n') });"; + for (const [runtime, entry] of [["web", "/app/main.mjs"], ["node", "/app/main.mjs"]] as const) { + const out = await runGuest(page, { + "/app/main.mjs": [ + "let heard = 0;", + "self.onmessage = () => { heard++; };", + forged, + "setTimeout(() => console.log('still talking to the kernel', heard), 20);", + ].join("\n"), + }, entry, "", { runtime }); + expect(out, runtime).toEqual({ code: 0, signal: null, stdout: "still talking to the kernel 0\n", stderr: "" }); + } +});