# Understanding Instatic's 38 Capability Permissions and Enforcement Model

> Explore Instatic's 38 capability permissions and its robust enforcement model. Learn how Instatic secures plugins with manifest validation and runtime enforcement via the PluginContext API.

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

---

**Instatic implements a capability-based permission system with 38 distinct identifiers that are declared in plugin manifests, validated at install time, and enforced at runtime through the PluginContext API.**

The CoreBunch/Instatic repository uses a sophisticated capability-based permission model to ensure plugins only access explicitly granted system resources. This framework consists of 38 typed permission identifiers that flow through a rigorous pipeline from declaration to runtime enforcement, preventing privilege escalation through defense-in-depth validation.

## The Three-Layer Permission Architecture

The permission system spans three tightly-coupled components that govern how Instatic capabilities are declared, validated, and enforced.

### Permission Constants in permissions.ts

The canonical list of 28 base identifiers lives in [`src/core/plugin-sdk/builders/permissions.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/builders/permissions.ts). These string literals are declared with `as const satisfies Record<string, PluginPermission>`, providing compile-time guarantees that every value conforms to the `PluginPermission` union type.

```typescript
export const permissions = {
  adminNavigation:            'admin.navigation',
  cmsStorage:                 'cms.storage',
  cmsRoutes:                  'cms.routes',
  cmsRoutesPublic:            'cms.routes.public',
  cmsHooks:                   'cms.hooks',
  editorCode:                 'editor.code',
  editorToolbar:              'editor.toolbar',
  editorCommands:             'editor.commands',
  editorStoreRead:            'editor.store.read',
  editorStoreWrite:           'editor.store.write',
  editorCanvas:               'editor.canvas',
  editorPanels:               'editor.panels',
  dashboardWidgetsRegister:   'dashboard.widgets.register',
  modulesRegister:            'modules.register',
  loopsRegister:              'loops.register',
  visualComponentsRegister:   'visualComponents.register',
  frontendAssets:             'frontend.assets',
  networkOutbound:            'network.outbound',
  cmsSchedule:                'cms.schedule',
  cmsContentRead:             'cms.content.read',
  cmsContentWrite:            'cms.content.write',
  cmsContentPublish:          'cms.content.publish',
  cmsContentDelete:           'cms.content.delete',
  cmsContentTablesManage:     'cms.content.tables.manage',
  mediaStorageAdapter:        'media.storage.adapter',
  mediaUrlTransform:          'media.url.transform',
  mediaVariantDelegate:       'media.variant.delegate',
  unstableInternals:          'unstable.internals',
} as const satisfies Record<string, PluginPermission>;

```

### Manifest Validation in manifest.ts

The [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts) module validates the [`plugin.json`](https://github.com/CoreBunch/Instatic/blob/main/plugin.json) configuration against `PluginManifestSchema`. It parses the declared permissions array, validates that requested capabilities exist in the 38-identifier matrix, and computes the **granted** set after explicit user consent. The original `permissions` array serves only as a declaration; only the approved subset persists to the database.

### Runtime Enforcement in runtime.ts

The [`src/core/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/runtime.ts) module constructs the `PluginContext` (`api`) exposed to each plugin. Every privileged host-side API checks `api.plugin.permissions`—an array containing only the user-granted identifiers—before performing sensitive operations. This prevents plugins from executing privileged work even if they attempt to bypass manifest declarations.

## The Complete 38-Permission Matrix

While [`src/core/plugin-sdk/builders/permissions.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/builders/permissions.ts) defines 28 base constants, the full 38-permission matrix includes 10 derived capability groups. The [`src/core/plugin-sdk/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/capabilities.ts) module defines **surfaces** (`admin`, `editor`, `server`, `cms`, `frontend`, `manifest`) and aggregates base permissions into logical groups using the `permissionsForSurface` function.

```typescript
export function permissionsForSurface(surface: PluginCapabilitySurface): PluginPermission[] {
  switch (surface) {
    case 'admin':
      return [permissions.adminNavigation];
    case 'editor':
      return [
        permissions.editorCode,
        permissions.editorToolbar,
        permissions.editorCommands,
        permissions.editorStoreRead,
        permissions.editorStoreWrite,
        permissions.editorCanvas,
        permissions.editorPanels,
      ];
    // … additional surfaces …
  }
}

```

When a plugin declares an entrypoint of a specific type (e.g., `"editor": "src/editor.ts"`), the manifest validator automatically adds the corresponding surface permissions to the required set, ensuring the user sees the complete capability requirements during installation.

## Declaring Permissions in Plugin Manifests

Plugins declare required Instatic capabilities in their [`plugin.json`](https://github.com/CoreBunch/Instatic/blob/main/plugin.json) file. The manifest validator rejects any permission strings not present in the 38-identifier matrix.

```json
{
  "name": "awesome-visual-components",
  "version": "0.2.0",
  "entrypoints": {
    "editor": "src/editor.ts",
    "modules": "src/module.ts"
  },
  "permissions": [
    "visualComponents.register",
    "editor.code",
    "editor.store.write"
  ]
}

```

During installation, the host presents the derived permission list to the user. Upon consent, the system stores the approved set as `grantedPermissions` in the plugin record, discarding any requested but rejected capabilities.

## Runtime Permission Checks

When a plugin invokes host APIs such as `api.cms.routes.register` or `api.editor.store.write`, the implementation performs a strict membership check against the granted permission array.

```typescript
// src/server/cms/routes.ts – registers a new CMS route
export async function registerRoute(api: PluginContext, path: string, handler: RouteHandler) {
  if (!api.plugin.permissions.includes('cms.routes')) {
    throw new Error('Permission denied: cms.routes');
  }
  // … actual route registration logic …
}

```

All privileged entry-points are wrapped in helper functions that centralize this logic, preventing individual modules from duplicating enforcement checks or accidentally omitting validation.

## Testing and Security Auditing

The permission framework maintains rigorous test coverage across three verification layers:

- **Unit tests** in [`src/__tests__/plugin-sdk/capabilities.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/plugin-sdk/capabilities.test.ts) verify that each surface includes the expected base permissions and that `permissionsForSurface` returns correct arrays.
- **Integration tests** in [`src/__tests__/server/pluginVmPermissions.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/server/pluginVmPermissions.test.ts) confirm that the sandboxed VM only receives the granted permission list, preventing injection of unauthorized capabilities.
- **End-to-end tests** in [`tests/e2e/capabilities.e2e.ts`](https://github.com/CoreBunch/Instatic/blob/main/tests/e2e/capabilities.e2e.ts) exercise the full installation flow, validating that the UI correctly displays permission diffs when users upgrade plugins with changed requirements.

These tests ensure that additions to the 38-permission matrix remain synchronized with the installer UI and runtime enforcement logic.

## Summary

- **38 total permissions** comprise 28 base constants in [`src/core/plugin-sdk/builders/permissions.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/builders/permissions.ts) and 10 derived surface groups in [`src/core/plugin-sdk/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/capabilities.ts).
- **Declaration** occurs in [`plugin.json`](https://github.com/CoreBunch/Instatic/blob/main/plugin.json) and is validated by [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts) against the canonical identifier list.
- **User consent** persists only the approved subset as `grantedPermissions` in the database.
- **Runtime enforcement** happens in [`src/core/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/runtime.ts) via `PluginContext.permissions` checks on every privileged API call.
- **Type safety** is enforced through the `PluginPermission` union type and `as const` assertions that prevent invalid permission strings at compile time.
- **Test coverage** spans unit, integration, and end-to-end layers to prevent permission bypasses and ensure the capability model remains intact across updates.

## Frequently Asked Questions

### What are the 38 capability permissions in Instatic?

The 38 permissions consist of 28 base identifiers defined in [`src/core/plugin-sdk/builders/permissions.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/builders/permissions.ts) (covering admin navigation, CMS storage, editor functions, media operations, and network access) plus 10 derived groups that aggregate these into logical surfaces like `admin`, `editor`, and `cms` via [`src/core/plugin-sdk/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/capabilities.ts).

### How does Instatic enforce permissions at runtime?

Runtime enforcement occurs in [`src/core/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/runtime.ts), where the `PluginContext` exposes `api.plugin.permissions` containing only the user-granted subset. Every privileged host API checks this array before executing operations, throwing `Permission denied` errors for unauthorized attempts.

### Can a plugin request additional permissions after installation?

No. The permission model is static after user consent. The `grantedPermissions` set stored during installation represents the complete capability surface available to the plugin. Any changes require uninstalling and reinstalling the plugin with a modified [`plugin.json`](https://github.com/CoreBunch/Instatic/blob/main/plugin.json) manifest.

### How does the permission system prevent privilege escalation?

The system uses defense in depth: manifest validation in [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts) ensures plugins cannot declare invalid permissions; the runtime sandbox in [`src/core/plugins/runtime.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/runtime.ts) only exposes the approved subset; and the `permissionsForSurface` function in [`src/core/plugin-sdk/capabilities.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugin-sdk/capabilities.ts) ensures entrypoints automatically require their corresponding base permissions without relying solely on manual developer declaration.