stanchion Guides GitHub
MIT or Apache-2.0

stanchion

Securely typed plugin infrastructure for applications and library. A stanchion both bears load and marks a boundary — which is what this does: typed contracts and dependency wiring on one side, sandboxing, capabilities and signatures on the other.

Read the guides View on GitHub

Describe the contract in Rust

Write a contract. In Rust the #[lua_class] macro generates the class handle, instance handle, and conversions. The contract itself is Rust-only — no foreign language supplies one — but Python, Ruby and Kotlin applications can host plugins through the stanchion-ffi bindings, resolving methods by name. Either way, a plugin's methods are checked when it loads, not when one is finally called at 3am.

#[lua_class]
pub trait Greeter {
    fn new(greeting: String) -> Result<Self>;
    fn greet(&self, who: String) -> Result<String>;

    #[lua(field)]
    fn greeting(&self) -> Result<String>;

    #[lua(optional)]
    fn on_unload(&self) -> Result<Option<()>>;

    async fn fetch(&self, url: String) -> Result<String>;
}

Implement it in Lua

An ordinary Lua class. Every method the contract expects is verified at load time — a missing field surfaces before the first call. A native implementation in Rust lives alongside Lua in the same Vec, and the dispatch layer resolves to whichever one you gave it. An experimental WASM backend loads .wasm plugins too, though today it marshals only numbers across the boundary.

local Greeter = {}
Greeter.__index = Greeter

function Greeter.new(greeting)
  return setmetatable({ greeting = greeting }, Greeter)
end

function Greeter:greet(who)
  return self.greeting .. ", " .. who
end

return Greeter

Seven layers, each optional

Every layer is a feature you turn on. Depend on the facade, take what you need, and pay for nothing else — the sigstore verifier alone brings a large dependency graph, so it lives in its own crate.

Typed contracts

A Rust trait becomes a Lua class binding. Tables or userdata; sync or async.

A plugin registry

Manifests, discovery, dependency order, hot reload, and per-plugin failure isolation.

Isolation

One shared state, one per dependency group, or one per plugin — with VM-enforced memory and per-call instruction limits.

Declared capabilities

A plugin asks; your policy grants, narrows, or refuses. Deny by default.

Signatures

Sigstore-backed provenance over every file — and provenance can tier what a plugin may reach.

Distribution

Packages, an index anyone can host, lockfile pinning, and an upgrade review before you trust it.

Out of process

Run plugins in a child over JSON-RPC, so a crash inside the interpreter is survivable.

What a crash costs you

In-process limits stop a runaway plugin, and a panic unwinds, so it can be caught and reported. A segfault is a signal and os.exit is an exit — neither unwinds, and no in-process trick survives them.

The plugin doesIn processOut of process
while true do endinstruction limitinstruction limit
allocation stormmemory limitmemory limit
os.exit, or a C-rock segfaultyour application diesHostGone, naming the signal
a panic in a host callbackreported as Panickedthe child dies; you keep serving

Built for

Semi-trusted authors: your team, your customers under contract, a curated community. The capability and signature guides cover what changes when the authors are strangers.

Editors and developer tools

Plugins depending on a shared utilities plugin, reloaded on save without a restart.

Games with mod support

Load-ordered mods, one broken mod reported rather than fatal, and a per-mod instruction budget.

Programmable gateways

Per-route transforms authored outside the binary, dispatched asynchronously.

Rules and alerting

Customer-authored detection logic, where a first-party signature earns network access.

Multi-tenant business rules

Pricing and eligibility per tenant, in their own state, without a deploy.

Data pipelines

Analysts write transforms and get real libraries from LuaRocks, not a bare interpreter.

The crates

A facade plus the layers behind it, so heavy dependencies stay optional.

CrateHolds
stanchionthe facade, and the plugin-host binary
stanchion-luathe #[lua_class] contract, LuaRocks trees and version constraints
stanchion-macrosthe attribute macro
stanchion-ffia dynamic facade for building foreign-language bindings (Python, Kotlin, …)
stanchion-wasma WASM runtime backend for stanchion-ffi
stanchion-registrymanifests, sandboxing, capabilities, signatures, reload
stanchion-sigstorekeyless verification, quarantining a large graph
stanchion-remoteout-of-process hosting over JSON-RPC 2.0
stanchion-distpackages, the index protocol, pinned installs
stanchion-indexserving an index, framework-neutral
stanchion-index-poema Poem adapter for serving a plugin index

Guides

Rendered from the repository's own documentation, so they cannot drift from it.