Skip to content

feat: configurable JS runtime platform (CreateVmConfig.jsRuntime)#78

Merged
NathanFlurry merged 1 commit into
mainfrom
feat/js-runtime-platform
Jun 19, 2026
Merged

feat: configurable JS runtime platform (CreateVmConfig.jsRuntime)#78
NathanFlurry merged 1 commit into
mainfrom
feat/js-runtime-platform

Conversation

@NathanFlurry

@NathanFlurry NathanFlurry commented Jun 19, 2026

Copy link
Copy Markdown
Member

What

Adds CreateVmConfig.jsRuntime so callers can choose which JavaScript host environment guest code sees — modeled on esbuild's platform. Omitting it is byte-for-byte today's behavior (full Node.js emulation).

config: { jsRuntime: { platform: "browser", moduleResolution: "node" } }
  • platform: node | browser | neutral | bare (esbuild's three + a sanctioned language-only tier)
  • moduleResolution: node | relative | none (independent axis)
  • allowedBuiltins: tri-state Option — omitted = engine default, [] = deny all, [..] = exactly those (node platform only)
Capability node browser neutral bare
Node globals (process/Buffer/require…)
node:* builtins
Web platform (fetch/URL/WebCrypto…)
Universal (console/timers)
Language + WebAssembly

How

  • The guest JS surface is baked into a shared V8 snapshot, so non-node platforms are produced by subtractively scrubbing baked globals in the per-execution runtime shim (prepend_v8_runtime_shim) — not by gating the config-blind setupGlobals().
  • Builtin denial is enforced at the bridge's rejectRestrictedBuiltinRequest sink (the chokepoint for import/import()/require), via a one-shot __agentOsInitJsRuntime init fn that stores the allow-list in a bridge closure and is deleted by the shim — no reachable __agentOs* global remains.
  • moduleResolution is gated at the Rust resolver (MODULE_NOT_FOUND for denied bare/relative); builtin denial surfaces uniformly as ERR_ACCESS_DENIED.
  • browser narrows crypto to WebCrypto (no randomBytes) and keeps npm/ESM→CJS interop working (module machinery _requireFrom/_moduleModule is governed by moduleResolution, not stripped by platform).
  • Identity leak surfaces (_processConfig, __agentOsProcessConfigEnv) are scrubbed for non-node platforms.
  • Removes the legacy configure_vm.allowed_node_builtins field end-to-end (BARE schema → generated wire types → ~40 call sites).

Docs & spec

  • New docs page Features → Runtime & Platform (website/src/content/docs/docs/features/runtime-platform.mdx) with the full matrix + examples.
  • Design spec in specs/js-runtime-platform.md (peer-reviewed; the wiring reflects the corrected mechanism).
  • CLAUDE.md: esbuild-vocabulary rule, with bare documented as the one sanctioned extension.

Testing

  • vm-config: 41 unit tests (validation, tri-state, every platform × moduleResolution round-trip).
  • Pentest suite (crates/execution/tests/javascript_v8.rs, in javascript_v8_suite) proving the surface is actually disabled: under bare, process/Buffer/require/node:*/timers/console/identity channels are unreachable (incl. via the Function constructor) while language + WebAssembly remain; browser exposes web globals, denies node:fs with ERR_ACCESS_DENIED, loads a CJS npm package, and keeps fetch callable; moduleResolution: none/relative deny imports as specified; node allowedBuiltins:["path"] permits path, denies fs.
  • TS check-types, full sidecar lib+tests build, and pnpm --dir website build all green.

@railway-app

railway-app Bot commented Jun 19, 2026

Copy link
Copy Markdown

🚅 Environment secure-exec-pr-78 in rivet-frontend has no services deployed.

Add a jsRuntime config to choose which JavaScript host environment guest
code sees, modeled on esbuild's platform:

- platform: node | browser | neutral | bare
- moduleResolution: node | relative | none (independent axis)
- allowedBuiltins: tri-state Option (node platform only)

Non-node platforms are produced by subtractively scrubbing baked globals in
the per-execution runtime shim (the V8 snapshot is shared, so tiering can't
live in setupGlobals). Builtin denial is enforced at the bridge's
rejectRestrictedBuiltinRequest sink via a one-shot init fn (no reachable
__agentOs* global); moduleResolution is gated at the Rust resolver. browser
narrows crypto to WebCrypto and keeps npm/ESM->CJS interop working.

Removes the legacy configure_vm.allowed_node_builtins field end-to-end
(BARE schema + generated wire types + ~40 call sites). Adds docs page
(features/runtime-platform) + spec. vm-config validation + an execution-level
pentest suite prove the surface is actually disabled (process/Buffer/require/
node:* unreachable, identity channels scrubbed, imports denied per mode).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@NathanFlurry
NathanFlurry force-pushed the feat/js-runtime-platform branch from 3d60364 to 58f62ba Compare June 19, 2026 07:42
@NathanFlurry
NathanFlurry merged commit e118ec8 into main Jun 19, 2026
1 of 2 checks passed
@NathanFlurry
NathanFlurry deleted the feat/js-runtime-platform branch June 19, 2026 07:43
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.

1 participant