Skip to content

Conversation

@simonw
Copy link
Owner

@simonw simonw commented Dec 23, 2025

Investigate mquickjs as a safe JavaScript sandbox with:

  • Memory limits (user-provided buffer)
  • Time limits (interrupt handler)
  • No file/network access

Implementations:

  • FFI bindings (ctypes) - fastest startup, good performance
  • Python C extension - best performance
  • Subprocess wrapper - simplest, uses unmodified mqjs binary
  • WebAssembly - runs in Node.js, Deno, and browsers
  • Pyodide integration for Python-in-browser

Security analysis:

  • ReDoS: Backtracking regex engine, but interrupt handler mitigates
  • Sandboxing: Strong isolation via memory bounds and minimal stdlib
  • JS subset: ES5-like, no dangerous features

Benchmarks show:

  • C Extension: ~0.002-0.085ms per operation
  • FFI: ~0.007-0.086ms per operation
  • Subprocess: ~4-5ms per operation (500x slower)

mquickjs commit: 17ce6fe54c1ea4f500f26636bd22058fce2ce61a

Claude Code for web transcript: https://gistpreview.github.io/?6e07c54db7bb8ed8aa0eccfe4a384679

Original prompt:

Clone https://github.com/bellard/mquickjs to /tmp

Investigate this code as the basis for a safe sandboxing environment for running untrusted code
such that it cannot exhaust memory or CPU or access files or the network

First try building python bindings for this using FFI - write a script that builds these by
checking out the code to /tmp and building against that, to avoid copying the C code in this
repo permanently. Write and execute tests with pytest to exercise it as a sandbox

Then build a "real" Python extension not using FFI and experiment with that

Then try compiling the C to WebAssembly and exercising it via both node.js and Deno, with a
similar suite of tests

Finally Rey executing that WASM build in Python itself using first wasmer and then wasmtime

The goal is a Python library that can take an arbitrary string of the mquickjs subset of
JavaScript, plus optional custom memory and time limits (set these to sensible defaults) and
return the result. Design an API for that early and use that same design for all of the other
experiments.

In your final report include sandboxing pros and cons (including security problems you could not
fix if you found any) and comparative timing benchmarks for all of your solutions

Investigate mquickjs as a safe JavaScript sandbox with:
- Memory limits (user-provided buffer)
- Time limits (interrupt handler)
- No file/network access

Implementations:
- FFI bindings (ctypes) - fastest startup, good performance
- Python C extension - best performance
- Subprocess wrapper - simplest, uses unmodified mqjs binary
- WebAssembly - runs in Node.js, Deno, and browsers
- Pyodide integration for Python-in-browser

Security analysis:
- ReDoS: Backtracking regex engine, but interrupt handler mitigates
- Sandboxing: Strong isolation via memory bounds and minimal stdlib
- JS subset: ES5-like, no dangerous features

Benchmarks show:
- C Extension: ~0.002-0.085ms per operation
- FFI: ~0.007-0.086ms per operation
- Subprocess: ~4-5ms per operation (500x slower)

mquickjs commit: 17ce6fe54c1ea4f500f26636bd22058fce2ce61a
After deeper investigation, successfully implemented wasmtime support by
properly implementing the emscripten invoke_* trampolines:

Key insights:
- invoke_* must call through __indirect_function_table
- Must catch WASM traps and Python exceptions for longjmp
- Must use setThrew/stackRestore to manage emscripten state

Performance (vs FFI):
- Startup: ~58ms (due to WASM compilation)
- Execution: ~300x slower (due to Python/WASM boundary crossing)
- Still viable for security-critical scenarios

Files added/modified:
- mquickjs_wasmtime.py - Working wasmtime implementation
- build_wasm_wasmtime.py - Alternative build script
- mquickjs_wasmtime.wasm - WASM built with SUPPORT_LONGJMP=wasm
- benchmark.py - Added wasmtime to benchmarks
- README.md - Added wasmtime appendix with deep dive
- notes.md - Updated with wasmtime success
- Create benchmark_results.md with comprehensive performance data
- Update README.md to include Wasmtime in main benchmark table
- Document wasmer CLI limitations (requires same invoke_* imports)
- Link to detailed benchmark results from README
@simonw simonw merged commit 75b5f6b into main Dec 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants