# How Instatic Permission-Gates Plugins for Network and Filesystem Access

> Learn how Instatic permission-gates plugins for network and filesystem access using a QuickJS-WASM sandbox and explicit user consent. Enhance your application's security.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-07-28

---

**Instatic enforces a fine-grained, manifest-driven permission model that isolates plugins in a QuickJS-WASM sandbox and only permits network and filesystem operations when a user explicitly grants the corresponding capabilities.**

Instatic runs third-party plugins inside a QuickJS-WASM sandbox and controls what they can access through a strict, manifest-driven permission system. Each plugin ships a [`plugin.json`](https://github.com/CoreBunch/Instatic/blob/main/plugin.json) manifest that lists requested permissions—such as `network.outbound` or `filesystem.read`—and the runtime only injects user-approved capabilities into the sandbox. As implemented in CoreBunch/Instatic, this multi-layered approach combines manifest validation, runtime context injection, and VM-level API gating to prevent unauthorized host access.

## Manifest Declaration and Entrypoint Validation

Every Instatic plugin must declare its required permissions in its manifest. Before a plugin loads, [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts) validates that declared entrypoints have the matching permissions. For example, a plugin that registers an editor entrypoint must request `editor.code`:

```ts
// src/core/plugins/manifest.ts
if (data.entrypoints?.editor && !data.permissions.includes('editor.code')) {
  throw new Error('Add "editor.code" to `permissions`.');
}

```

Similar manifest checks exist for `modules.register`, `admin.navigation`, and `frontend.assets`. This validation happens before the plugin ever reaches the sandbox, ensuring that permission requirements are explicit and well-formed.

## Runtime Permission Context Injection

Once a plugin is installed, the approved permission set is stored in the manifest under `grantedPermissions`. The runtime context factory in [`src/core/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/runtime.ts) creates a `PluginContext` that includes only these granted permissions:

```ts
// src/core/plugins/runtime.ts
const context = {
  …,
  permissions: [...(manifest.grantedPermissions ?? [])],
};

```

This `PluginContext` is passed to every plugin lifecycle hook, so the permission list travels with the plugin throughout its execution. [`src/core/plugins/moduleAdapter.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/moduleAdapter.ts) bridges this host permission set into the sandboxed QuickJS environment, ensuring the VM receives the exact authority the user approved and nothing more.

## Gating Network Access in the Sandbox

Outbound network access is controlled by the `network.outbound` permission. When this permission is absent from the granted set, the QuickJS VM blocks any attempt to fetch external URLs. If the permission is present, the VM configures an `allowedHosts` list derived from the plugin manifest, restricting requests to explicitly approved domains.

The canonical permission constants are defined in [`src/core/plugin-sdk/builders/permissions.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/builders/permissions.ts), which exports identifiers such as `networkOutbound`, `filesystemRead`, and `filesystemWrite`.

The following plugin requests outbound network access and uses the `api.http.fetch` method gated by this permission:

```ts
// src/plugins/example/network-plugin.ts
import { definePlugin, permissions } from '@core/plugin-sdk';

export default definePlugin({
  id: 'example.network',
  version: '1.0.0',
  permissions: [permissions.networkOutbound],
  async init({ api }) {
    // This fetch will succeed only if the user granted `network.outbound`
    const res = await api.http.fetch('https://api.example.com/data');
    const json = await res.json();
    console.log('Fetched data:', json);
  },
});

```

Because the sandbox only exposes `api.http.fetch` when `network.outbound` is granted, unauthorized network calls are impossible.

## Gating Filesystem Access in the Sandbox

Filesystem operations are gated by `filesystem.read` and `filesystem.write`. The QuickJS sandbox exposes a virtual filesystem rather than the host OS directly, and any host-side filesystem calls are wrapped in permission checks. If the plugin has not been granted the relevant capability, the virtual filesystem API returns an error.

Here is a plugin that requests both read and write access:

```ts
import { definePlugin, permissions } from '@core/plugin-sdk';

export default definePlugin({
  id: 'example.fs',
  version: '1.0.0',
  // Request read and write access to the sandboxed filesystem
  permissions: [permissions.filesystemRead, permissions.filesystemWrite],
  async init({ api }) {
    // Read a file from the sandboxed FS
    const data = await api.fs.readFile('data.json');
    // Write a new file
    await api.fs.writeFile('output.txt', 'Hello, Instatic!');
  },
});

```

Plugins can also inspect their granted permissions at runtime through `api.plugin.permissions`, which returns only the user-approved subset:

```ts
export default definePlugin({
  id: 'example.inspect',
  version: '1.0.0',
  permissions: [],
  async init({ api }) {
    console.log('Granted permissions:', api.plugin.permissions);
    // Will only contain the permissions the user approved
  },
});

```

## Permission Enforcement Testing

Instatic’s test suite verifies that the sandbox cannot bypass the granted permission set. The file [`__tests__/server/pluginVmPermissions.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/__tests__/server/pluginVmPermissions.test.ts) asserts that the API exposed to plugins reflects `grantedPermissions`, not merely the requested list. These tests validate that [`src/core/plugins/moduleAdapter.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/moduleAdapter.ts) correctly withholds network and filesystem bindings when the corresponding permissions are absent, making the permission model enforceable by code rather than convention.

## Summary

- **Manifest validation** in [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts) ensures every entrypoint declares its required permissions before load.
- **Runtime injection** in [`src/core/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/runtime.ts) creates a `PluginContext` that carries only `manifest.grantedPermissions` into the sandbox.
- **Network gating** relies on the `network.outbound` permission and an `allowedHosts` list configured inside the QuickJS VM.
- **Filesystem gating** uses a virtual FS and permission checks around `filesystem.read` and `filesystem.write`.
- **Automated tests** in [`__tests__/server/pluginVmPermissions.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/__tests__/server/pluginVmPermissions.test.ts) confirm that ungranted permissions are never exposed to plugin code.

## Frequently Asked Questions

### What permission does an Instatic plugin need to make HTTP requests?

An Instatic plugin must declare and be granted the `network.outbound` permission. The QuickJS sandbox checks this permission before exposing `api.http.fetch`, and it further restricts requests to hosts listed in the plugin manifest’s `allowedHosts` configuration.

### Can a plugin access the host filesystem directly?

No. Instatic exposes only a virtual filesystem inside the QuickJS sandbox. Host filesystem access is impossible unless the plugin has been explicitly granted `filesystem.read` or `filesystem.write`, and even then the operations are sandboxed and mediated by the runtime.

### Where are granted permissions stored in Instatic?

Approved permissions are stored in the plugin manifest under the `grantedPermissions` array. [`src/core/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/runtime.ts) reads this field to build the `PluginContext`, and [`src/core/plugins/moduleAdapter.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/moduleAdapter.ts) forwards only those permissions into the QuickJS VM.

### What happens if a plugin requests an entrypoint without the required permission?

[`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts) throws a validation error during plugin load. For example, declaring an `editor` entrypoint without including `editor.code` in the manifest permissions causes the loader to reject the plugin before it enters the sandbox.