AUDIT / src /workers /jsRunner.worker.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 4)
641b62c verified
Raw
History Blame Contribute Delete
3.33 kB
/**
* 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);
}
}
};