AbsoluteJS

isolated-jsc 0.11.0 Proof Pack

This is still not broad launch mode. The 0.8.x line moved isolated-jsc from raw isolate primitives into a product API for the Bun isolation wedge: policy recipes, one-shot execution, pooled keyed runners, precompiled callables, capability manifests, redacted bounded audit events, execution receipts, and output limits for tenant scripts, AI-generated code, and plugin execution. The 0.9.0 through 0.11.0 releases add the multi-tenant economics layer on top: hibernating keyed pools, operator-shaped pool metrics, and OpenTelemetry tracing.

#Match the execution boundary to the threat model

The fastest backend is not automatically the right trust boundary; choose from workload provenance and deployment controls.

Best for: High-volume code you control or tightly broker through explicit host capabilities.

Tradeoffs: Lowest overhead, but it is a same-process boundary and must not be described as an OS sandbox.

Requirements
  • Pin and verify the supported JavaScriptCore runtime
  • Apply capability, output, console, timeout, and memory policy

#What shipped

As of 0.11.0 the package keeps the proof pack but adds the API shape services actually want: choose a policy, run one-off source with runIsolated(), create a pooled createIsolatedRunner(), warm hot functions with precompile(), hibernate idle tenants with createHibernatingIsolatePool(), review declared host powers with capability manifests, redact and bound audit events, cap host capability outputs, and inspect every run with receipts.

Headline numbers from the repeatable bench:proof run (100 warm / 10 cold iterations):

Warm callable p50
0.23ms
FFI backend, 100 warm iterations of bench:proof
Cold isolate p50
1.15ms
FFI backend, includes backend startup
Cold heap
~300KB
FFI backend; the Worker fallback sits at ~46 MB
Process-spawn p50
25.8ms
Bun subprocess per execution, the baseline it replaces
FeatureWhy it matters
Benchmark proof packReproducible local measurements for FFI, Worker, Bun process-spawn, and optional Node isolated-vm baselines.
Migration guideA concrete path for teams moving a Node isolated-vm workload to Bun and JavaScriptCore.
Security modelExplicit FFI vs Worker guarantees, Worker residuals, resource limits, and deployment hardening guidance.
TypeScript helpersBun-native transpilation helpers for string source and real .ts/.tsx/.js/.jsx files before isolate execution.
Capability brokerNamed host tools with validation hooks, timeout, concurrency, output byte caps, tenant context, manifests, redacted audit events, bounded audit buffers, typed tool definitions, and typed host-side direct calls.
Execution receiptsPer-run receipts for scripts, callables, one-shot execution, and pooled runners, including backend, policy, timing, metrics, output size, capability summaries, console bounds, and dropped-audit counts.
Output boundariesRun-level result byte limits, per-capability output byte limits, and isolate-level console entry/byte limits so successful outputs, tool outputs, and captured logs stay bounded.
Doctor CLIA package bin that reports Bun/platform details, FFI availability, checked JSC paths, and install hints.
Agent-tool exampleA runnable demo combining TypeScript callables, brokered tools, tenant context, metrics, and audit events.
Policy presets + runnersProduct-ready recipes for AI tools, tenant scripts, plugins, and trusted code, including recommended result, console, audit, broker, and pool defaults.
Hibernating isolate poolA keyed isolate + context pool that checkpoints idle tenants down to bytes and wakes them transparently on the next call, with a pluggable hibernation store (0.9.0).
Pool operator surfacemetrics(), drain(), and warm(key) on both pools for cost metering, graceful shard shutdown, and predictive pre-warm (0.10.0).
OpenTelemetry tracingOptional tracerProvider on the hibernating pool emits an isolated_jsc.run span per pool.run with tenant and wake attributes (0.11.0).

#Receipts + limits

The new launch framing is not just "run code in a sandbox." The host gets reviewable capability manifests, redacted audit events, bounded audit buffers, and execution receipts that survive success and failure paths. Receipts carry the backend, policy, tenant and purpose labels, timing, metrics, output size, capability call summaries, console overflow flags, and capability audit truncation counts.

maxResultBytes rejects oversized successful outputs with ResultSizeError. maxConsoleEntries and maxConsoleBytes bound captured console output. createCapabilityAuditBuffer()bounds retained capability events and records how many were dropped. Capability tools can set maxOutputBytes so oversized host-tool results reject with a CapabilityErrorbefore sandbox code receives them, and receipts retain machine-readable error codes such as CAPABILITY_OUTPUT_SIZE_LIMIT.

Every release since 0.8.11 tightened this surface. The line runs from error-metadata parity through checkpoints and receipts to the hibernating pool and tracing:

0.8.11
FFI error metadata parity
Preserves host Reference error metadata — capability code, tool, and output-size fields — when errors cross the FFI backend.
0.8.12
Stable audit schema
Adds schemaVersion: 1 to capability manifest entries and execution receipts so apps can persist and parse audit records against an explicit stable schema.
0.8.13
Schema contract tests
Contract tests lock the schema-v1 manifest and receipt key sets so future audit-surface changes are intentional.
0.8.14
Broker redaction examples
Expands default and per-tool audit redactors: masked emails, opaque card tokens, and redacted processor trace identifiers.
0.8.15
Packaged policy recipes
Resolved policies carry recommended result, console, audit buffer, broker, and runner pool settings; policy isolates inherit the recipe result-size limit as a default run option.
0.8.16
Policy helper builders
policyAuditOptions(), policyBrokerOptions(), policyConsoleOptions(), policyRunOptions(), and policyRunnerOptions() return copy-safe option objects for wiring recipes into surrounding APIs.
0.8.17
Checkpoint boundary documented
SNAPSHOT_RESEARCH.md documents why the public JSC C API supports data checkpoints but not a V8-style heap pause/resume snapshot.
0.8.18
File-backed sources
Scripts and callables can live in real .ts/.tsx/.js/.jsx files via compileTypeScriptFile(), runIsolatedFile(), and runner file methods instead of string literals.
0.8.19
Explicit context checkpoints
context.checkpoint(options) with schema version, byte length, skip reasons, and maxBytes / include / exclude controls, plus createContext({ checkpoint }) restore.
0.8.20
Restore validation
validateContextCheckpoint() and runtime restore validation fail malformed persisted checkpoints before seed code runs; backend parity tests cover Worker and FFI.
0.8.21
Checkpoint receipts
checkpointWithReceipt() and createContextWithReceipt() return schema-v1 CheckpointReceipt envelopes; the error path rethrows with the receipt attached.
0.9.0
Hibernating isolate pool
createHibernatingIsolatePool checkpoints idle tenant contexts to bytes and wakes them transparently on the next call; createInMemoryHibernationStore ships as the default pluggable store.
0.10.0
Pool operator surface
metrics(), drain(), and warm(key) on both pools — cumulative cost counters for metering, graceful shard shutdown, and predictive pre-warm without invoking user code.
0.11.0
OpenTelemetry tracing
HibernatingIsolatePoolOptions.tracerProvider accepts any @opentelemetry/api-compatible provider and emits an isolated_jsc.run span per pool.run(key, fn); zero-cost when omitted.
TS
import {
  createCapabilityAuditBuffer,
  runIsolated
} from "@absolutejs/isolated-jsc";

const audit = createCapabilityAuditBuffer({ maxEvents: 32 });

const { result, receipt } = await runIsolated("await tools('now'); 42", {
  policy: "tenant-script",
  globals: { tools: broker.reference },
  maxConsoleEntries: 4,
  maxConsoleBytes: 512,
  run: {
    ...audit.receiptOptions(),
    executionId: "exec_123",
    maxResultBytes: 16_384,
    purpose: "tenant-plugin",
    tenant: "tenant_acme",
  },
  withReceipt: true
});

receipt.capabilityCallsDropped;    // number of audit events not retained
receipt.capabilityCallsTruncated;  // true when the audit buffer overflowed
receipt.console.truncated;         // true when console capture overflowed
receipt.error?.code;               // machine-readable failure, when present
receipt.schemaVersion;             // 1stable receipt schema marker
receipt.outputBytes;               // estimated successful-result bytes

#The Bun wedge

The positioning is narrow on purpose: Bun already makes TypeScript execution fast. @absolutejs/isolated-jsc makes untrusted TypeScript and JavaScript execution embeddable inside Bun.

For the deeper market and objection-handling frame, see isolated-jsc for Bun.

Node migrationteams using isolated-vm get familiar nouns: Isolate, Context, Script, Reference, and ExternalCopy.
Bun-native runtimethe FFI backend talks to JavaScriptCore directly when libJSC is available, with a Worker fallback for portability.
Operational proofisolated-jsc doctor shows the backend, checked JSC paths, and platform-specific install guidance before teams hit runtime errors.
BASH
bun add @absolutejs/isolated-jsc
bunx @absolutejs/isolated-jsc

#Agent tool example

The example is the intended adoption shape: define typed host tools, call them directly from the host when useful, pass the brokered dispatcher as a Reference to untrusted code, set a timeout, and capture per-call metrics.

TS
import {
  createCapabilityAuditBuffer,
  createCapabilityBroker,
  createIsolatedRunner,
  defineCapabilityTool
} from "@absolutejs/isolated-jsc";

type TenantContext = { id: string; plan: "free" | "pro" };
type OrderLookup = { id: string };
type Order = { id: string; status: string; totalUsd: number };

const audit = createCapabilityAuditBuffer<TenantContext>({ maxEvents: 100 });

const broker = createCapabilityBroker(
  {
    lookupOrder: defineCapabilityTool<
      OrderLookup,
      Order | null,
      TenantContext
    >({
      description: "Read one order by id for the current tenant",
      input: "OrderLookup",
      output: "Order | null",
      maxOutputBytes: 16_384,
      risk: "read-only",
      timeoutMs: 100,
      redactAuditInput: (input) => ({ id: (input as { id?: unknown }).id }),
      redactAuditOutput: (output) => {
        if (output === null) return null;
        const order = output as Order;
        return { id: order.id, status: order.status };
      },
      validateInput: (input) => {
        if (input === null || typeof input !== "object") {
          throw new Error("lookupOrder input must be an object");
        }
        const id = (input as { id?: unknown }).id;
        if (typeof id !== "string") {
          throw new Error("lookupOrder input requires a string id");
        }
        return { id };
      },
      handler: async ({ id }, tenant) => lookupOrderForTenant(tenant.id, id)
    })
  },
  {
    context: { id: "tenant_acme", plan: "pro" },
    onAudit: audit.onAudit
  }
);

const manifest = broker.manifest();

// Direct host calls infer Order | null from the tool map.
const order = await broker.call("lookupOrder", { id: "ord_123" });

const runner = createIsolatedRunner({
  policy: "ai-tool",
  pool: { maxSize: 64, idleMs: 60_000 },
});

await runner.precompile(
  "agentLookup",
  'async (tools, orderId) => await tools("lookupOrder", { id: orderId })',
  { key: "tenant_acme" }
);

const { result, metrics } = await runner.call(
  "agentLookup",
  'async (tools, orderId) => await tools("lookupOrder", { id: orderId })',
  [broker.reference, "ord_123"],
  {
    key: "tenant_acme",
    run: {
      ...audit.receiptOptions(),
      maxResultBytes: 16_384,
      purpose: "agent-tool-call",
      tenant: "tenant_acme"
    },
    withMetrics: true
  }
);

const stats = runner.stats();
await runner.dispose();

#Bun sandboxing decision guide

Use backend: 'ffi' for hostile-code production paths on macOS or Linux where JavaScriptCore is available. Use backend: 'auto' for local development, demos, CI smoke tests, and portable defaults. When the Worker fallback is the only option, keep it behind a process or container boundary if hostile workloads could reach meaningful host secrets.

FFIlowest cold heap, interrupt-driven timeouts, isolate survives timeouts, and eval / Function-constructor residuals are closed.
Worker fallbackportable heap isolation and resource caps, but deploy it with OS-level blast-radius controls for arbitrary third-party code.
Host toolsexpose powers through Reference or the typed capability broker, then validate, timeout, and audit every call.

#Security posture

The release keeps the security language explicit. FFI closes the indirect eval and Function-constructor residuals by disabling eval per context. The Worker backend remains a portable fallback, but hostile-code workloads that care about those residuals should requirebackend: 'ffi' and compose with process, container, uid, or network boundaries when host secrets are high value.

#Start here

  • Read the package README for install, backend behavior, and API examples.
  • Read SECURITY.md before running untrusted code.
  • Run bun run example:agent-tool from the repo to see the proof-pack path end to end.

Current package surface

What ships today

@absolutejs/isolated-jscv0.12.4 · betaPlatform & InfranpmSource
1entry points80symbols

Import surface · click to copy

80 symbols
createIsolateexportPermalinkSource
TS
createIsolate
Exported from @absolutejs/isolated-jsc

Outcomes

What you can build

Overview

JavaScriptCore-native sandbox for Bun. Heap-isolated execution for untrusted code, with an isolated-vm-shaped API.

Why this exists

Bun has no equivalent to Node's isolated-vm. The Node library is V8-specific — it links against V8's HasCustomHostObject ABI symbol — and Bun uses JavaScriptCore, not V8. So bun install isolated-vm succeeds, then import fails with undefined symbol: HasCustomHostObject.

Quick answers

Why not Node isolated-vm? It is the right shape for Node/V8, but Bun is JavaScriptCore. isolated-jsc ports the isolate-shaped API to Bun/JSC instead of trying to load a V8 addon.

Hardening checklist

Production guidance

DoctorThe doctor prints Bun/platform details, FFI backend availability, JavaScriptCore flavor/path when found, checked library paths when missing, and the install hint for the current platform. Pass --json for machine-readable CI or deployment checks.

Follow in order

Troubleshooting path

1
API
Errors