Stream: wasmtime

Topic: Node.js compatibility layer for QuickJS on Wasmtime


view this post on Zulip Mahavishnu K (Oct 04 2026 at 22:49):

Hey everyone,
I’m working on a project called ore-node-std, and I thought this might be relevant to people here working with Wasmtime/WASI.

I’m trying to run QuickJS under Wasmtime + WASI Preview 1 while providing a useful subset of Node.js APIs (fs, crypto, http, stream, buffer, process, etc.) without relying on custom WASM host imports, such as C/C++ implemented host functions (_node:os, _node:crypto) or non-standard socket extensions.

One of the problems I ran into is that WASI P1 doesn't provide outbound TCP sockets. Instead of extending the host ABI, I’m currently experimenting with a VFS-based approach where the QuickJS guest writes requests to temporary files, and the Rust host handles things like native crypto/network I/O and writes the results back.

ore-node-std is actually one layer of a larger system I’m building called ORE. The core system is ore-kernel, which acts as the root sandbox/runtime layer. It manages the execution environment and launches isolated workloads through Wasmtime, with the goal of keeping the guest environment constrained to standard WASI interfaces.

The idea is to have the kernel provide the isolation and resource boundary, while ore-node-std provides the higher-level Node-compatible environment inside the sandbox.

Architecture-wise, I’m roughly working toward:
ORE Kernel → Wasmtime → WASI P1 → QuickJS → ore-node-std

with the kernel acting as the root sandbox rather than exposing arbitrary host capabilities directly to the JavaScript runtime.

The projects are here:

I’d especially appreciate feedback from anyone familiar with Wasmtime/WASI sandboxing, guest/host boundaries, or whether there’s a more idiomatic WASI-native way to approach this.

Not trying to solve everything with the current design, but I’m mainly looking for a sanity check from people who know the Wasmtime side much better than I do. :slight_smile:

view this post on Zulip Bailey Hayes (Oct 05 2026 at 16:45):

Have you seen https://github.com/bytecodealliance/jco and https://bytecodealliance.github.io/jco/interop/nodejs-builtins.html

view this post on Zulip Mahavishnu K (Oct 05 2026 at 18:45):

Thanks for sharing these links, @Bailey Hayes !

I’ve looked closely into jco and the preview2-shim / jco-std work mapping Node.js built-ins to WASI 0.2. The coverage on the compatibility matrix is really impressive.

The reason I’m experimenting with WASI P1 + QuickJS rather than the full WASI 0.2 Component Model via jco comes down to three specific design constraints for ORE:

  1. Strict Layer-7 Containment & Zero-RAM Streaming: Because ORE executes untrusted, autonomous AI agent scripts, I deliberately avoid giving the guest lower-level socket access (even via wasi:sockets). Outbound I/O is mediated at Layer 7 by the ore-kernel, streaming responses directly to an ephemeral VFS mount to protect the guest's linear memory from OOMs on large payloads.
  2. Minimal Engine Footprint & Cold Starts: Keeping the runtime embedded within lightweight QuickJS keeps instantiation latency sub-millisecond with minimal memory overhead, avoiding heavier engine payloads like SpiderMonkey.
  3. Zero-IDL / Zero-Codegen Developer Experience: I want developers and autonomous agents to write plain scripts or tools without requiring ahead-of-time .wit authoring, code generation, or component composition steps before execution.

That said, jco's pure JS implementations for modules like node:buffer, node:events, and node:stream are great references that I can definitely study for the non-I/O built-ins.

Given your work around sandboxing and guest/host boundaries in the Wasm / WASI ecosystem, I'd be curious to hear your perspective on using an ephemeral VFS as the primary I/O bridge for untrusted workloads versus exposing finer-grained host imports.

view this post on Zulip Chris Fallin (Oct 05 2026 at 19:29):

Minimal Engine Footprint & Cold Starts: Keeping the runtime embedded within lightweight QuickJS keeps instantiation latency sub-millisecond with minimal memory overhead, avoiding heavier engine payloads like SpiderMonkey.

FWIW, we worked pretty hard on instantiation times in Wasmtime; we use virtual memory tricks so even a full SpiderMonkey component can instantiate in ~a few microseconds.

Heap size and compiled code size are still likely smaller with QuickJS but I'd encourage you to measure (and we're curious if you find any overheads you don't expect).

view this post on Zulip Mahavishnu K (Oct 06 2026 at 10:36):

That makes a lot of sense regarding how the pooling allocator and CoW page-table remapping keep instantiation down to microseconds regardless of binary size.

Currently, ORE's execution pipeline is built around core Wasm modules and WASI P1 (wasmtime::Module / Instance), so testing a componentize-js artifact directly would require standing up a separate wasmtime::component harness outside our kernel.

For context on my side, my QuickJS module (system-js.wasm) is ~4.2MB, but Cranelift compiles it into a ~19MB .cwasm artifact. Given the code expansion factor during AOT compilation, a heavier engine embedding starting at 8MB–13MB+ raw Wasm creates substantially more cache pressure when scaling to dozens of distinct tool cartridges on constrained edge nodes.

Circling back to the guest-host I/O boundary and the other design constraints, I'd still love to hear your perspective on using an ephemeral VFS to pass dynamic payloads back and forth in Wasmtime P1.

Are there hidden performance or lifecycle pitfalls with treating the filesystem as a universal transport like that, compared to exposing dedicated host function imports?

view this post on Zulip Till Schneidereit (Oct 06 2026 at 11:03):

one note on the .cwasm size and duplicated artifacts: we have a plan for making it so that runtimes, such as JS engines, can be reused across multiple components. That'd mean you'd only have a single .wasm per version of the engine.

That doesn't help you in the short term, of course, and I can't give you a good estimate for when this will be available.

In the meantime, most of us are focused on component model use, so I'm not sure how much engagement you'll get on the VFS usage. Superficially it seems plausible to me, but this being a very security sensitive part to get right, I wouldn't rely on a quick reply like that all that much

view this post on Zulip Mahavishnu K (Oct 06 2026 at 14:57):

Thanks for the transparent context, @Till Schneidereit !

That context on sharing runtimes across components makes a lot of sense, good to know it's an active area of exploration on your end, even if that timeline is still further out.

Understood on the team's focus being squarely on the Component Model right now. And I appreciate the word of caution on the VFS bridge, definitely agree that filesystem-mediated IPC carries plenty of security sensitivity around capability leaks, symlink resolution, and traversal. I am relying heavily on cap-std handles and strict UUID-scoped ephemeral mounts precisely to keep those boundaries tight.

Really appreciate you, @Bailey Hayes , and @Chris Fallin taking the time to share your perspectives and context. It's been super helpful for grounding my architecture!


Last updated: Oct 11 2026 at 02:20 UTC