stanchion
Lua plugins for Rust applications. 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 trait. #[lua_class] generates the class handle, the instance
handle, and the conversions — and validates the contract when a plugin
loads, not when a method 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. The trait stays dyn-compatible, so a native Rust
implementation drops into the same Vec<Box<dyn Greeter>>.
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. They cannot stop a segfault in a C rock, or rescue a state whose allocator already failed.
| 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 | RemoteError::HostGone |
| interpreter panic | unwinds into your stack | 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-core | the #[lua_class] contract |
stanchion-macros | the attribute macro |
stanchion-registry | manifests, sandboxing, capabilities, signatures, reload |
stanchion-rocks | LuaRocks trees and version constraints |
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 |
Guides
Rendered from the repository's own documentation, so they cannot drift from it.