Page MenuHomePhabricator

Migrate the QuickJS interpreter off second-state/wasmedge-quickjs to a maintained QuickJS-NG WASI build
Closed, ResolvedPublic

Description

The JavaScript interpreter binary is built from second-state/wasmedge-quickjs (pinned to the abandoned v0.6.1-alpha, T364564), a low-activity side-project wrapping Bellard's QuickJS. The wider community has moved to QuickJS-NG, which receives regular releases. We should migrate our build source to a maintained QuickJS-NG WASI build (e.g. vercel-labs/quickjs-wasi), or QuickJS-NG built from source ourselves, so that the JS engine keeps receiving security and language-feature updates.

Technical notes

This is a build-source change, not a run-time change. We already run the QuickJS .wasm as a generic wasm32-wasip1 module under wasmtime and inject all sandbox controls ourselves in Rust — fuel/memory caps, a single read-only preopen, no host-env inheritance, in-process stdio channels, single-use executors (ADR-0001). The wasmedge runtime is not involved. The only coupling to second-state/wasmedge-quickjs is executors/wasm-utilities/build-quickjs-for-wasmtime, which clones, cargo builds to wasm32-wasip1, and hand-patches the resulting .wat (interpreters/quickjs.diff, a sock_accept arity fix).

Expected work:

  • (1) replace the build recipe to source QuickJS-NG;
  • (2) adapt the JS glue if the builtin module surface changed — QuickJS-NG renamed the modules to the qjs: prefix, so import * as std from 'std' in main.js and the getline/print/flush calls in utils.js need re-checking;
  • (3) the quickjs.diff patch likely becomes unnecessary against a current wasi-sdk build and should be dropped deliberately;
  • (4) re-run the security evaluation for the new engine against the ADR-0001 constraints (__code__ replacement, Symbol.species, frozen-globalThis delete), plus a fuzz pass; and
  • (5) opportunistically check whether the newer engine affects the known issues with concurrent-wave resulting in Z507 failures (suspected host/wasmtime interaction rather than engine bug).

Sizing note: building QuickJS-NG from source ourselves (wasi-sdk + CMake) is roughly equivalent in effort to the existing RustPython build stack and yet avoids the rustup-on-Debian std-grafting pain that build carries (T430145); adopting vercel-labs/quickjs-wasi is lighter still. Recommend a short spike first to confirm the module-surface delta and whether the .wat patch drops out, before committing to the full migration. We are explicitly not vendoring second-state/wasmedge-quickjs as an interim step. Worth recording the outcome as an ADR (following ADR-0002 for RustPython).

Acceptance criteria

  • The JavaScript interpreter .wasm is built from a maintained QuickJS-NG source, with no remaining dependency on second-state/wasmedge-quickjs, and the full test suite (test-rust, test-js-direct) passes.
  • The security evaluation is re-run against the new engine and the result is documented (ADR + any ADR-0001 updates).

Related Objects

Mentioned In
rMSFEe7415e9b8225: test: Add JavaScript waves to the executor pool load tests
rMSFE480455694ab2: test: Resolve the committed interpreters by default in the test harnesses
rMSFEa4a8f30ffc79: build: Migrate the JavaScript interpreter to quickjs-ng v0.15.1
rMSFE1a83c1ff99cd: docs: Correct ADR-0002's stale note on the QuickJS interpreter
rMSFE77bd196a1bcb: build: Pin (and commit) quickjs-ng to the tagged release v0.15.1
rMSFEacef7e459cef: build: Track baking wasi-sdk into the base as a follow-up, not a blocker
rMSFEa4ff5b1ffd87: ci: [SPIKE T432522] Install wasi-sdk under mktemp, not /opt (non-root builder)
rMSFE59a035e728e0: ci: [SPIKE T432522] Build quickjs-ng from source; add JS load-test waves
rMSFE6455520e78f7: build: Remove orphaned install-rust after the quickjs-ng migration
rMSFEa3a1584f6bc7: ci: [SPIKE T432522] Run test-rust against quickjs-ng instead of wasmedge-quickjs
rMSFEae59acb9da76: docs: Correct ADR-0002's stale note on the QuickJS interpreter
rMSFE54b467565c02: build: Track baking wasi-sdk into the base as a follow-up, not a blocker
rMSFE7eb355c81f23: build: Pin (and commit) quickjs-ng to the tagged release v0.15.1
rMSFE72e3f60f114c: build: Remove orphaned install-rust after the quickjs-ng migration
rMSFE62d1772968d8: ci: [SPIKE T432522] Install wasi-sdk under mktemp, not /opt (non-root builder)
rMSFEe76315805a4a: ci: [SPIKE T432522] Build quickjs-ng from source; add JS load-test waves
rMSFE59fff605737d: ci: [SPIKE T432522] Run test-rust against quickjs-ng instead of wasmedge-quickjs
rMSFE53987460eaa8: docs: Re-home T412768's inline TODOs onto its successor tasks
rMSFE8c14351223e9: docs: Re-home T412768's inline TODOs onto its successor tasks
rMSFE1b799abf542a: docs: Re-home T412768's inline TODOs onto its successor tasks
rMSFE3e8c500a06a9: build: Build RustPython on the base's baked wasm std, not rustup
rMSFEe2933307f1e6: docs: Correct ADR-0002's stale note on the QuickJS interpreter
rMSFEf6798fe24ae6: build: Track baking wasi-sdk into the base as a follow-up, not a blocker
rMSFE2b98af65ad54: build: Pin (and commit) quickjs-ng to the tagged release v0.15.1
rMSFEd5a26c6e49a3: build: Remove orphaned install-rust after the quickjs-ng migration
rMSFEf20cb5b843b2: ci: [SPIKE T432522] Install wasi-sdk under mktemp, not /opt (non-root builder)
rMSFE35e019121e55: ci: [SPIKE T432522] Build quickjs-ng from source; add JS load-test waves
rMSFEe411748fab49: build: Build RustPython on the base's baked wasm std, not rustup
rMSFEfa09d3923386: ci: [SPIKE T432522] Run test-rust against quickjs-ng instead of wasmedge-quickjs
rMSFEf05b14a2e0b0: docs: Correct ADR-0002's stale note on the QuickJS interpreter
rMSFEa44fe42c8f79: build: Pin (and commit) quickjs-ng to the tagged release v0.15.1
rMSFEc39a6ea1e00a: build: Track baking wasi-sdk into the base as a follow-up, not a blocker
rMSFEc201a1c8883d: build: Remove orphaned install-rust after the quickjs-ng migration
rMSFE0abfb3a5c151: build: Build RustPython on the base's baked wasm std, not rustup
rMSFEddbb735108db: tests: Assert user JavaScript code has no reachable filesystem capability
rMSFE218e2412d7d5: build: Build RustPython on the base's baked wasm std, not rustup
rMSFE037243f5bc59: build: Build RustPython on the base's baked wasm std, not rustup
rMSFE32c1f60be71e: tests: Assert user JavaScript code has no reachable filesystem capability
rMSFE4fa6cf5606e3: tests: Assert the RustPython sandbox boundary holds for user code
rMSFE8ebe3ba4c480: tests: Assert user JavaScript code has no reachable filesystem capability
rMSFE1e6414af6c04: ci: [SPIKE T432522] Build quickjs-ng from source; add JS load-test waves
rMSFE346fa84d2ceb: ci: [SPIKE T432522] Install wasi-sdk under mktemp, not /opt (non-root builder)
rMSFE3ae08d790679: ci: [SPIKE T432522] Build quickjs-ng from source; add JS load-test waves
rMSFEbdd0a7763285: ci: [SPIKE T432522] Run test-rust against quickjs-ng instead of wasmedge-quickjs
Mentioned Here
T430690: Restore load testing capabilities in Rust evaluator
T364564: Pin the wasmedge-quickjs Dependency to a Specific Commit
T430145: Ship the wasm32-wasip1 Rust standard library in the abstractwiki-rust base, built by the image's own Debian-packaged rustc

Event Timeline

✅ Spike: quickjs-ng validated as the JS interpreter replacement.** All mechanical axes green on x86 CI; only security review remains.

✅ Build (build-wasmtime-quickjs-only): quickjs-ng built from source; qjs_exe (command-model) target, wasm32-wasi via wasi-sdk 25.0 + cmake, pinned to the validated commit. No need for our old quickjs.diff / wasm2wat splice: quickjs-ng imports no sock_accept, so wasmtime accepts it unpatched.

✅ Runtime (test-rust): single-request test_javascript_* all pass with quickjs-ng + our existing glue.

✅ Concurrency (test-load): extended with JavaScript waves (pool-refresh + timeout). Both pass, which seems to resolve my long-standing Z507-under-concurrency problem: it was wasmedge-quickjs-side, and quickjs-ng clears it. JS load tests can now be enabled permanently (previously Python-only, T430690).

Migration surface is small:

  • main.js: import … from 'std''qjs:std' (builtins moved to the qjs: prefix; getline/print/flush intact).
  • Delete quickjs.diff + the splice build step.
  • Binary is ~1.5 MiB vs the shipped ~3.6 MiB: leaner, not lacking: the gap is wasmedge-quickjs's Rust runtime + node-like shims (fetch/process/http), which a sandbox shouldn't expose anyway.

I've pushed a draft MR that switches us. If we're going ahead with this, we'll need to re-check our security controls all work, but from my checking it seems good.

Jdforrester-WMF changed the task status from Open to In Progress.Thu, Jul 23, 3:09 PM
Jdforrester-WMF claimed this task.
Jdforrester-WMF triaged this task as High priority.

Change #1324726 had a related patch set uploaded (by Jforrester; author: Jforrester):

[operations/deployment-charts@master] wikifunctions: Upgrade evaluators from 2026-08-04-215640 to 2026-08-11-201322

https://gerrit.wikimedia.org/r/1324726

Change #1324726 merged by jenkins-bot:

[operations/deployment-charts@master] wikifunctions: Upgrade evaluators from 2026-08-04-215640 to 2026-08-11-201322

https://gerrit.wikimedia.org/r/1324726