Skip to content

Example: Plugin System

A full plugin system built on xript’s tier 2 features: namespaces to organize bindings, capabilities to gate destructive operations, and custom types to describe data structures. Five plugins run against the same host, each with a different permission profile.

The full source is in examples/plugin-system/.

The manifest declares a tasks namespace with five methods and a top-level log function:

{
"xript": "0.1",
"name": "task-manager",
"version": "1.0.0",
"bindings": {
"tasks": {
"description": "Read and manage tasks.",
"members": {
"list": { "description": "Returns all tasks.", "returns": { "array": "Task" } },
"get": { "description": "Returns a task by ID.", "params": [{ "name": "id", "type": "string" }] },
"add": { "description": "Creates a new task.", "params": [...], "capability": "manage-tasks" },
"complete": { "description": "Marks a task as done.", "params": [...], "capability": "manage-tasks" },
"remove": { "description": "Permanently removes a task.", "params": [...], "capability": "admin" }
}
},
"log": { "description": "Writes a message to the plugin console.", "params": [...] }
}
}

Key observations:

  • tasks.list and tasks.get have no capability gate: any plugin can read tasks
  • tasks.add and tasks.complete require the manage-tasks capability
  • tasks.remove requires the admin capability, a higher privilege tier
  • log is a top-level function with no gate
{
"capabilities": {
"manage-tasks": { "description": "Create and complete tasks.", "risk": "medium" },
"admin": { "description": "Delete tasks and admin operations.", "risk": "high" }
}
}
{
"types": {
"Task": {
"description": "A task in the task manager.",
"fields": {
"id": { "type": "string" },
"title": { "type": "string" },
"done": { "type": "boolean" },
"priority": { "type": "Priority" },
"createdAt": { "type": "string" }
}
},
"Priority": {
"description": "Task priority levels.",
"values": ["low", "medium", "high", "urgent"]
}
}
}

Types do not enforce runtime validation; they serve as documentation for extenders and feed into tools like xript typegen and xript docgen.

The host creates a shared task store and wires up the namespace methods:

const taskStore = [];
const hostBindings = {
tasks: {
list: () => [...taskStore],
get: (id) => taskStore.find((t) => t.id === id),
add: (title, priority) => {
const task = { id: String(nextId++), title, done: false, priority, createdAt: new Date().toISOString() };
taskStore.push(task);
return task;
},
complete: (id) => { /* mark done */ },
remove: (id) => { /* splice from array */ },
},
log: (msg) => console.log(`[plugin] ${msg}`),
};

Each plugin gets its own runtime instance but shares the same underlying taskStore, so plugins see each other’s changes; that shared visibility is deliberate here.

The factory is initialized once, then each plugin creates a runtime from it:

import { initXript } from "@xriptjs/runtime";
const xript = await initXript();
const runtime = xript.createRuntime(manifest, { hostBindings, capabilities: [] });
runtime.execute("tasks.list()");
runtime.execute('log("Found " + tasks.list().length + " tasks")');

Can read tasks and call log, but cannot create, complete, or remove tasks.

const runtime = xript.createRuntime(manifest, { hostBindings, capabilities: ["manage-tasks"] });
runtime.execute('tasks.add("Write documentation", "high")');
runtime.execute('tasks.complete("1")');

Can create and complete tasks, but cannot remove them (no admin capability).

const runtime = xript.createRuntime(manifest, { hostBindings, capabilities: [] });
runtime.execute('tasks.list().filter(t => !t.done).length'); // works
runtime.execute('tasks.add("Sneaky task", "low")'); // CapabilityDeniedError

Demonstrates that a plugin without capabilities still cannot modify tasks, even after other plugins have created them.

const runtime = xript.createRuntime(manifest, { hostBindings, capabilities: ["manage-tasks", "admin"] });
runtime.execute('tasks.remove("1")');

Full access. Can read, create, complete, and remove tasks.

5. Privilege Escalation Attempt (manage-tasks only)

Section titled “5. Privilege Escalation Attempt (manage-tasks only)”
const runtime = xript.createRuntime(manifest, { hostBindings, capabilities: ["manage-tasks"] });
runtime.execute('tasks.remove("2")'); // CapabilityDeniedError: requires "admin"

Having manage-tasks does not grant admin; each capability must be explicitly granted.

Every runtime accepts an optional audit callback. The host receives an AuditEvent ({ binding, capability, at }) each time a plugin invokes a gated binding, so a host can log, rate-limit, or alarm on sensitive operations without changing the bindings themselves:

const runtime = xript.createRuntime(manifest, {
hostBindings: createHostBindings(),
capabilities: ["manage-tasks", "admin"],
audit: ({ binding, capability, at }) => {
console.log(`[audit] ${binding} (cap: ${capability ?? "none"}) at ${at}`);
},
});
runtime.execute('tasks.remove("1")'); // emits an audit event for "tasks.remove" / "admin"

The audit channel is per-capability and host-side only; it observes grants the runtime already enforces and never relaxes them.

Terminal window
cd examples/plugin-system
node src/demo.js

The demo runs all five plugins sequentially, showing which operations succeed and which are denied.

You can also point the toolchain at this manifest to validate it, inspect its extension surface, and see how moddable it is:

Terminal window
npx xript validate examples/plugin-system/manifest.json # check it against the spec schema
npx xript describe examples/plugin-system/manifest.json # summarize bindings, capabilities, and types
npx xript score examples/plugin-system/manifest.json # rate the exposed moddability surface
npx xript lint examples/plugin-system/manifest.json # findings-based review of the manifest

xript score measures moddability capacity: how much of the host’s surface (bindings, slots, capabilities, events) is exposed to extenders, not how heavily any particular plugin exercises it.

ConceptWhere
Namespacestasks.list(), tasks.add()
Capability gatingmanage-tasks on add/complete, admin on remove
Tiered permissionsPlugin 5 has manage-tasks but not admin
Custom typesTask and Priority in the manifest
Default-denyPlugins 1 and 3 get read-only access
Shared stateAll plugins see the same task store

Tier 2 is the right choice when:

  • You need to organize bindings into logical groups (namespaces)
  • Some operations are destructive or sensitive and need permission gating
  • You want to document data structures for extenders
  • Different plugins need different permission levels