Spaces:
Sleeping
Sleeping
| /** | |
| * 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<string, { cancelled: boolean }>(); | |
| 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<JSRunnerMsg>) => { | |
| 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); | |
| } | |
| } | |
| }; | |