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.
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 does | In process | Out of process |
|---|---|---|
while true do end | instruction limit | instruction limit |
| allocation storm | memory limit | memory limit |
os.exit, or a C-rock segfault | your application dies | HostGone, naming the signal |
| a panic in a host callback | reported as Panicked | the 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.
| Crate | Holds |
|---|---|
stanchion | the facade, and the plugin-host binary |
stanchion-lua | the #[lua_class] contract, LuaRocks trees and version constraints |
stanchion-macros | the attribute macro |
stanchion-ffi | a dynamic facade for building foreign-language bindings (Python, Kotlin, …) |
stanchion-wasm | a WASM runtime backend for stanchion-ffi |
stanchion-registry | manifests, sandboxing, capabilities, signatures, reload |
stanchion-sigstore | keyless verification, quarantining a large graph |
stanchion-remote | out-of-process hosting over JSON-RPC 2.0 |
stanchion-dist | packages, the index protocol, pinned installs |
stanchion-index | serving an index, framework-neutral |
stanchion-index-poem | a Poem adapter for serving a plugin index |
Guides
Rendered from the repository's own documentation, so they cannot drift from it.