Skip to main content
Use Cases

Plugin Systems

Run user-authored plugins in isolation with per-plugin permissions and timeouts using Secure Exec.

Plugins, user scripts, formulas, and webhooks-as-code all share one problem: the author is not you. Secure Exec lets each one run with exactly the access you decide, and no way to affect the others.

Declare what a plugin gets

// Plugins are user-authored source you do not trust. Each one declares what it
// needs, and you decide what it gets.
interface Plugin {
	name: string;
	source: string;
	network: boolean;
}

const plugins: Plugin[] = [
	{
		name: "shout",
		source: `(() => inputs.text.toUpperCase())()`,
		network: false,
	},
	{
		name: "spin",
		source: `(() => { while (true) {} })()`,
		network: false,
	},
];

Run each plugin in its own VM

// Every plugin runs in its own VM, so plugins cannot see each other's state, and
// a plugin that hangs or crashes only fails its own call.
for (const plugin of plugins) {
	const result = await evaluate<string>(plugin.source, {
		inputs: { text: "hello" },
		permissions: { network: plugin.network ? "allow" : "deny" },
		timeoutMs: 1_000,
	});
	console.log(
		plugin.name,
		result.outcome === "succeeded" ? result.value : result.outcome,
	);
}
// shout HELLO
// spin timed_out
  • No shared state. Each call is a fresh VM, so one plugin cannot read or corrupt another’s data.
  • Failures stay contained. A plugin that hangs returns timed_out. A plugin that throws returns failed. Your process keeps running.
  • Per-plugin policy. permissions, limits, and mounts are set per call, so a trusted plugin can have the network while the rest cannot.

Giving plugins files and packages

Mount read-only data or a shared node_modules with mounts. For plugins that keep state across invocations, give each plugin its own VM.