Skip to content

fix(v8-runtime): stop guest JavaScript whose WebAssembly memory passes the heap limit - #2080

Merged
eersnington merged 1 commit into
fix/v8-array-buffer-budgetfrom
fix/v8-external-memory-limit
Oct 27, 2026
Merged

eersnington merged 1 commit into
fix/v8-array-buffer-budgetfrom
fix/v8-external-memory-limit

Conversation

@eersnington

@eersnington eersnington commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Refs #2078. Stacked on #2079.

#2079 caps ArrayBuffer memory through V8's ArrayBuffer allocator. V8 allocates WebAssembly memory through its page allocator instead, so a guest could still hold gigabytes in WebAssembly.Memory and OOM the shared sidecar. Host-provided buffers are also outside that budget on purpose.

  • V8 counts both as external memory and runs GC as it grows. install_external_memory_guard (crates/v8-runtime/src/isolate.rs) adds a GC prologue check. When external memory outside the ArrayBuffer budget passes the heap limit, it terminates the script and sets the existing heap-limit flag.
  • The caller gets the existing execution_heap_limit_exceeded error, which names limits.jsRuntime.v8HeapLimitMb. The sidecar emits a v8_external_memory_bytes limit warning.
  • The check runs for guest JavaScript only. Python (Pyodide) and the WASM runner also run in V8, but they keep their own program memory in WebAssembly under their own limits. javascript_execution_limits in native-sidecar sets JavascriptExecutionLimits::limit_external_memory, and RuntimeCommand::CreateSession carries it to the session. The session installs the guard when it first runs on an isolate, so warm-pool keys do not change.
  • create_guest_isolate replaces the two copies of isolate setup (fresh and snapshot) and keeps the budget on the isolate for the check.
flowchart LR
    A[launch.rs: JS execution] -->|limit_external_memory: true| B[CreateSession]
    P[Python / WASM runner] -->|false| B
    B --> C[session thread]
    C -->|true| D[GC prologue check]
    D -->|external memory minus budget > limit| E[terminate: execution_heap_limit_exceeded]
Loading
Guest JavaScript past the limit #2079 This PR
WebAssembly.Memory, grow, memory declared in a module Allocation succeeds execution_heap_limit_exceeded
Shared WASM memory, resizable ArrayBuffer, growable SharedArrayBuffer Allocation succeeds Unchanged. V8 does not count them as external memory.

Security

  • Closes the WebAssembly path of the same guest-to-sidecar denial of service.

This is part 2 of 2 in a stack:

@github-actions

Copy link
Copy Markdown
Contributor

📟 Pip-Boy · b80c307

📦 Packages · fresh npm install resolve, no lockfile · this PR vs base

No notable install size, dependency, or entrypoint changes across 6 packages.

All 6 packages · linux-x64-gnu
package (linux-x64-gnu) @latest base this PR Δ vs base packages
@rivet-dev/agentos-core 326.0 MB 326.0 MB 326.0 MB 0 10 (0)
@rivet-dev/agentos-node-pty 7.6 KB 18.0 KB 18.0 KB 0 1 (0)
@rivet-dev/agentos-posix 253.8 MB 253.8 MB 253.8 MB 0 7 (0)
@rivet-dev/agentos-runtime-core 253.8 MB 253.8 MB 253.8 MB 0 6 (0)
@rivet-dev/agentos-sandbox 363.7 MB 363.7 MB 363.7 MB 0 85 (0)
@rivet-dev/agentos-toolchain 130.8 KB 141.2 KB 141.2 KB 0 2 (0)

🧹 Rust dependencies · cargo deny + cargo shear

check new in this PR fixed existing on base
advisories ✅ 0 0 28
licenses ✅ 0 0 0
sources ✅ 0 0 0
bans ✅ 0 0 0
unused ✅ 0 0 16

🦀 Binaries · publish Dockerfiles, --release

Base: main@ae5e5cd, the newest main commit with native builds before the merge base. Size changes merged to main since then show up here too.

artifact npm @latest base this PR Δ vs base
agentos-native-sidecar linux-x64-gnu 143.8 MB 143.8 MB 143.8 MB +4.8 KB (+0.0%)

⏱ Timing

job wall time slowest step
changes 28s checkout · 25s
zizmor 10s Set up job · 2s
footprint 2m54s Pack the base · 49s
hygiene 1m00s checkout · 28s
native · linux-x64-gnu 10m11s Build with the publish Dockerfile · 9m55s
⏭ self-test — skipped
report 22s Write the comment and run summary · 6s
🔭 Full CI trace for this commit

One OpenTelemetry trace of CI and Pip-Boy for b80c307: every job and step, with the sizes above attached. Run after CI finishes.

pnpm install --frozen-lockfile --filter 'pipboy...'
pnpm --filter pipboy pipboy trace --sha b80c3076a3384e3d1f55d1f6d010b6641b9b4a49

Add --otlp <url> to send it to any OTLP/HTTP collector.

Agent instruction: From a rivet-dev/agentos checkout, run the commands above, then read the trace file the last command prints. List the slowest jobs and steps and explain where CI time went.

@eersnington
eersnington merged commit b83845e into fix/v8-array-buffer-budget Oct 27, 2026
16 checks passed
@eersnington
eersnington deleted the fix/v8-external-memory-limit branch October 27, 2026 22:15
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