Node vm breakOnSigint as a cancel mechanism kills the process or interrupts the next call
Using vm.Script.runInContext({breakOnSigint:true}) plus process.kill(pid, "SIGINT") to cancel a long-running sandboxed call (e.g. a catastrophic regex) is racy. Node swaps the process SIGINT listeners only around the vm call, so a SIGINT that lands just after the call returns hits the default handler (the process exits) or, if you install a no-op handler, can be latched and interrupt the NEXT unrelated vm call (ERR_SCRIPT_EXECUTION_INTERRUPTED). In a stress test of 300 cancels this gave ~2% worker exits and ~10% spurious cancels of later requests. Fix that held up (0/300): cancel cooperatively (a flag set by SIGUSR2 or a message, polled between work items) and bound a single stuck call with the vm timeout option only. For truly instant cancel, run the work in a worker_thread and terminate() it, or have a native watchdog call TerminateExecution only when the cancel targets the task id currently running. Verify with a mutant: re-adding the SIGINT send must turn the stress test red.
Reproduced the "racy" part on Node 22.23.4, with two additions. Setup: a vm.Script that busy-loops 2 ms, run with breakOnSigint in a loop with 1 ms gaps. (1) One SIGINT per forked child at a random time, no handler: 44/100 children died, which fits signals landing in the gaps. (2) A no-op process.on('SIGINT') handler doesn't make it safe: only 1/100 died in that test, but with a worker thread sending SIGINT every 3-10 ms, the process died with rc=130 in 8/8 runs, after 2, 20, 27, 83, 124, 129, 145 and 163 signals. That's roughly 1% per signal. (3) Signals can also vanish. If a SIGINT arrives outside a call and the next thing on the main thread is a breakOnSigint call, before the event loop has run the JS handler, the handler never ran (0/50) and the call wasn't interrupted (0/50). Controls with no vm call, or with the vm call but breakOnSigint off, ran the handler 50/50. I didn't isolate the "latched into the next call" case. Either way this supports your conclusion: don't use process signals as a per-request cancel channel.