# How QuickJS-WASM Plugin Sandbox Isolation Works in Instatic

> Discover how Instatic uses QuickJS-WASM plugin sandbox isolation to secure your server. Learn about complete isolation of plugins from the host runtime, file system, and network.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-30

---

**Instatic enforces complete isolation between server-side plugins by running each plugin inside its own QuickJS-WASM sandbox, ensuring no access to the host Node/Bun runtime, file system, or network without explicit permission grants.**

The Instatic platform (CoreBunch/Instatic) implements a defense-in-depth architecture where untrusted JavaScript plugin code executes inside a WebAssembly-based sandbox. This QuickJS-WASM plugin sandbox isolation guarantees that malicious or buggy code cannot compromise the host server or interfere with other plugins, while still permitting controlled access to specific capabilities through a strictly permissioned SDK.

## Bun Worker Per Plugin

Each plugin spawns in its own `Bun.Worker` thread, completely separating its execution context from the main server process and other plugins. The worker never uses `import()` to load the plugin source directly; instead, it receives the pre-compiled bundle and evaluates it inside the QuickJS virtual machine.

This design ensures that a crash or infinite loop in one plugin terminates only that specific worker without affecting the host runtime or neighboring sandboxes. The worker implementation lives in [`server/plugins/pluginWorker.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/pluginWorker.ts), which handles the lifecycle from boot to disposal.

## QuickJS-WASM VM Factory

The sandbox relies on a fresh QuickJS context created for every plugin entry-point via the factory function `createQuickJsVm()` in [`server/plugins/quickjs/vm.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/quickjs/vm.ts). This instantiation produces an isolated JavaScript environment with its own global object, heap, and execution stack, explicitly preventing any leakage of Node.js or Bun globals like `process`, `require`, or `fs`.

During creation, the VM is hardened with concrete resource boundaries:

- **Heap limit**: 64 MiB via `setMemoryLimit`
- **Stack limit**: 1 MiB via `setMaxStackSize`
- **Execution deadline**: 5 seconds (5000 ms) wall-clock time

These constraints prevent runaway memory allocations and deep recursion from exhausting server resources.

## Strict Permission Model

Privileged operations are gated through a declarative permission system enforced in [`server/plugins/quickjs/limits.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/quickjs/limits.ts). The plugin manifest declares requested capabilities (e.g., `network.outbound`, `content.read`), while the host validates granted permissions before exposing any SDK surface to the VM.

The host constructs a sandboxed API object using `buildSandboxedApi()` based solely on the `grantedPermissions` array, then injects it into the VM as a global named `api`. When plugin code invokes `api.fetch()` or similar methods, the call marshals across the worker boundary, where `assertPermission()` validates the action against the granted set before execution.

```json
{
  "id": "secure-plugin",
  "manifestVersion": "1.0",
  "entrypoints": {
    "server": "dist/server.js"
  },
  "permissions": ["content.read", "network.outbound"],
  "grantedPermissions": ["content.read"]
}

```

## Execution Flow and RPC Isolation

The complete lifecycle from activation to runtime follows a hardened path that maintains QuickJS-WASM plugin sandbox isolation at every stage:

1. **Activation**: [`server/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/runtime.ts) reads the plugin manifest and spawns a dedicated `Bun.Worker`.
2. **Worker Boot**: [`server/plugins/pluginWorker.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/pluginWorker.ts) imports the VM factory and calls `createQuickJsVm()` with the 64 MiB heap, 1 MiB stack, and 5-second timeout configuration.
3. **SDK Injection**: The host builds the restricted API object and calls `vm.setGlobal('api', sandboxApi)`, exposing only permitted capabilities.
4. **Evaluation**: The compiled entry point executes inside the VM under the enforced limits.
5. **RPC Bridging**: Plugin interactions with the host occur through the injected `api` object, with all calls crossing the worker boundary and undergoing permission validation in [`limits.ts`](https://github.com/CoreBunch/Instatic/blob/main/limits.ts).
6. **Cleanup**: On disable or crash, the VM is destroyed and its memory freed, preventing leaks in the Emscripten-backed runtime.

## Resource Exhaustion Protection

Beyond memory isolation, the sandbox enforces temporal limits to prevent denial-of-service via long-running computations. The `DEFAULT_EVAL_TIMEOUT_MS` constant sets a hard 5-second wall-clock deadline on plugin execution. When exceeded, QuickJS throws an `interrupted` exception, which the host records as a timeout status and handles gracefully without crashing the worker.

```typescript
try {
  await pluginVm.runTask(task);
} catch (err) {
  if (err.message === 'interrupted') {
    recordPluginTimeout(pluginId);
    throw new Error('Plugin execution timed out');
  }
  throw err;
}

```

## Summary

- **Worker Isolation**: Each plugin runs in a dedicated `Bun.Worker` that hosts a separate QuickJS-WASM VM, preventing cross-plugin contamination.
- **Resource Constraints**: Hard limits of 64 MiB heap, 1 MiB stack, and 5-second execution timeouts enforce deterministic resource usage.
- **Permission Gates**: All host capabilities require explicit grants in the plugin manifest, enforced at runtime by `assertPermission()` in [`server/plugins/quickjs/limits.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/quickjs/limits.ts).
- **Zero Host Exposure**: The VM instantiation in [`server/plugins/quickjs/vm.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/quickjs/vm.ts) excludes native modules and globals, presenting only the injected `api` surface.
- **Clean Termination**: Explicit disposal of the VM on plugin shutdown prevents memory leaks in the WebAssembly runtime.

## Frequently Asked Questions

### How does the QuickJS-WASM sandbox prevent plugins from accessing the file system?

The sandbox removes all native host APIs from the JavaScript environment. The VM created in [`server/plugins/quickjs/vm.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/quickjs/vm.ts) has no access to `require`, `process`, or Bun-specific globals. File system access is only possible if the plugin manifest includes `grantedPermissions` containing the specific file-system flag, which causes the host to expose a restricted `api.files` method that validates paths against an allowlist.

### What happens when a plugin exceeds the 5-second execution limit?

When plugin execution exceeds the `DEFAULT_EVAL_TIMEOUT_MS` of 5000 milliseconds, the QuickJS engine throws an `interrupted` exception. This error propagates to the host in [`server/plugins/pluginWorker.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/pluginWorker.ts), which records a timeout status for that plugin and terminates the current task without crashing the worker or affecting other sandboxes.

### How are permissions granted to an Instatic plugin?

Permissions are granted through the `grantedPermissions` array in the plugin manifest JSON. During initialization, [`server/plugins/pluginWorker.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/pluginWorker.ts) passes these grants to `buildSandboxedApi()`, which constructs a tailored SDK containing only the approved capabilities. The `assertPermission()` function in [`server/plugins/quickjs/limits.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/plugins/quickjs/limits.ts) validates every host-bound RPC call against this set before execution.

### Can a plugin crash affect other plugins or the host server?

No. Because each plugin executes inside its own `Bun.Worker` with a dedicated QuickJS-WASM VM instance, segmentation faults, infinite loops, or unhandled exceptions remain confined to that specific worker. The host process and other plugin workers continue operating normally, as the sandbox design guarantees complete process isolation at both the worker and VM levels.