/** * jsRunner.worker.ts — Sandboxed JavaScript execution in a Web Worker * * Isolates JS execution from the main thread. * Uses a restricted scope via Function constructor — no DOM, no window access. */ export type JSRunnerMsg = | { type: "run"; id: string; code: string; timeout?: number } | { type: "cancel"; id: string }; export type JSRunnerResult = | { type: "stdout"; id: string; text: string } | { type: "stderr"; id: string; text: string } | { type: "done"; id: string; exitCode: number; time: number } | { type: "error"; id: string; message: string }; const running = new Map(); const SAFE_GLOBALS = ` const console = { log: (...a) => self.postMessage({ type: "stdout", id: __RUN_ID__, text: a.map(String).join(" ") + "\\n" }), error: (...a) => self.postMessage({ type: "stderr", id: __RUN_ID__, text: a.map(String).join(" ") + "\\n" }), warn: (...a) => self.postMessage({ type: "stdout", id: __RUN_ID__, text: "[warn] " + a.map(String).join(" ") + "\\n" }), info: (...a) => self.postMessage({ type: "stdout", id: __RUN_ID__, text: a.map(String).join(" ") + "\\n" }), }; const print = (...a) => self.postMessage({ type: "stdout", id: __RUN_ID__, text: a.map(String).join(" ") + "\\n" }); // TOOL_CTX-1: blocca accesso rete dal sandbox — il Worker ha fetch() nativo ma non // deve permettere exfiltration (es. fetch verso attacker.com con token dal vault). // Si sovrascrivono le API rete con stub che lanciano errore immediatamente. const fetch = () => { throw new Error("[sandbox] fetch non disponibile in modalità sandbox"); }; const XMLHttpRequest = undefined; const WebSocket = undefined; const EventSource = undefined; `; self.onmessage = async (e: MessageEvent) => { const msg = e.data; if (msg.type === "cancel") { const ctx = running.get(msg.id); if (ctx) ctx.cancelled = true; return; } if (msg.type === "run") { const { id, code, timeout = 10_000 } = msg; const ctx = { cancelled: false }; running.set(id, ctx); const start = Date.now(); let timedOut = false; const timer = setTimeout(() => { timedOut = true; ctx.cancelled = true; self.postMessage({ type: "stderr", id, text: "[timeout] Execution exceeded " + timeout + "ms\n" } satisfies JSRunnerResult); self.postMessage({ type: "done", id, exitCode: 124, time: Date.now() - start } satisfies JSRunnerResult); }, timeout); try { const injected = SAFE_GLOBALS.replace(/__RUN_ID__/g, JSON.stringify(id)); // eslint-disable-next-line no-new-func const fn = new Function(injected + "\n" + code); const result = fn(); if (result instanceof Promise) await result; if (!timedOut && !ctx.cancelled) { self.postMessage({ type: "done", id, exitCode: 0, time: Date.now() - start } satisfies JSRunnerResult); } } catch (err) { if (!timedOut) { const msg2 = err instanceof Error ? err.message : String(err); self.postMessage({ type: "stderr", id, text: msg2 + "\n" } satisfies JSRunnerResult); self.postMessage({ type: "done", id, exitCode: 1, time: Date.now() - start } satisfies JSRunnerResult); } } finally { clearTimeout(timer); running.delete(id); } } };