Pricing Logic in Your Process: Limitr's Security Model
Limitr puts pricing inside your app. Every allow() is checked in your process, and a policy can carry logic of its own: a cost function per model, a rule for who gets a feature, a capability your agent can call. That's what makes it fast and flexible, and it raises the questions any security review should ask: what can that logic touch, who can change it, and what happens when something goes wrong?
Here are the answers, as of engine 0.8.3. The Security model page keeps them current.
The Short Version
- The engine is open source and runs in your process. One npm package, one dependency (Stof, which has none), no install scripts, and no network call on the hot path.
- Policy code runs in a sandbox. No files, no environment variables, and no network unless you allow it. Your private network stays off limits unless you list hosts on it.
- Every call has time and memory limits. A policy function that goes wrong fails on its own, and the engine keeps working.
- The engine is sealed. A policy builds on it but can't change how it meters, limits, or prices.
- Changes go through people. Your repo and review on your own, or drafts, review, and publish in Limitr Cloud, where the Analyst proposes and someone on your team applies.
1. What Runs in Your Process
@formata/limitr is the engine: the Limitr spec compiled to WebAssembly on Stof, with a TypeScript SDK around it. It depends on one package, @formata/stof, which has no dependencies of its own, and neither runs install scripts. Both are Apache 2.0, so everything that decides what your customers can do is code you can read.
allow, check, reserve, and every other engine call run locally. Run the engine on its own and nothing leaves your process at all. With Limitr Cloud, usage and customer updates sync in the background over a WebSocket, and policy updates come down the same way.
2. What Policy Code Can Do
A policy is a document with logic in it, and that logic runs in Stof's sandbox:
| By default | To allow it | |
|---|---|---|
| The policy and its customers | Yes | |
| Stof's libraries (math, strings, time, ...) | Yes | |
| Your app's functions | Only the ones you register | limitr.doc.lib('App', 'notify', fn) |
The network, Limitr.new | No | limitr.doc.allowHttp(['api.example.com']) |
The network, Limitr.cloud | Any public host | httpHosts: ['api.example.com'], or false for none |
| Your machine and private network | No | List the hosts |
| Files and environment variables | No | Not available |
Even with network access on, localhost, private ranges like 10.x and 192.168.x, and the cloud metadata address that serves your server's credentials are refused unless you list them. When a policy needs something from your app, the best way is a function you register, with your checks inside it, rather than network access.
3. Limits on Every Call
Every policy call has a time limit (5 seconds by default) and a memory limit (256 MB of growth). Engine calls use a sliver of either. The limits are there for a policy function that goes wrong, like a cost function stuck in a loop:
limitr.setMaxExecutionTime(2000); // ms, or null for no limit
limitr.doc.setMaxMemory(64 * 1024 * 1024); // bytes, or null
A call that hits a limit fails with an error, and the next call runs as normal.
4. A Sealed Engine
The engine is part of the same document as your policy, which is what lets a policy extend it. As of 0.8.3, the SDK seals the engine as it loads it, with Stof's sealing. Anything parsed into the document afterward, whether your policy, a Cloud update, or a policy function, can call the engine's functions but can't change, remove, or replace them:
fn shortcut() {
<Limitr>.allow = (id: str, entitlement: str): bool => true;
}
sealed: 'allow' is sealed and can't be replaced
Customizing works the same as before. A policy overrides behavior in its own types, and can still call the engine's version:
#[type]
Limitr GuardedLimitr: {
list blocked: ['beta'];
fn allow(id: str!, entitlement: str!, value: float | str = 0, event: bool | str | obj = true, cap: Cap | map = null, overhead?: float, force: bool = false) -> bool {
if (self.blocked.contains(entitlement)) return false;
self.allow<Limitr>(id, entitlement, value, event, cap, overhead, force)
}
}
So a policy decides what's different about your pricing, and the engine every policy runs on stays the one you installed. See Extend Limitr for overrides and hooks.
5. Who Can Change Your Pricing
On your own, the policy lives in your repo. It changes the way your code does: a pull request, a review, a deploy.
With Limitr Cloud, your team edits policies in drafts, validates and tests them, and publishes a version to every running copy of your app, with every version kept and one click to roll back. Limitr Analyst writes and tests in a draft too, and proposes the rest: nothing is published or applied until someone on your team does it.
A published policy reaches your engines with the sandbox and the sealed engine still in place. If your policies don't call outside services, set httpHosts: false; if they call a few, list them.
6. When Something Goes Wrong
The engine is built to fail safely:
- A cost function that throws falls back to the credit's
overhead_cost, and the error is logged. - A feature rule that throws counts as off.
- An unknown customer or entitlement is denied, not an exception.
- A call that hits its time or memory limit fails on its own, and the engine keeps working.
- If the connection to Limitr Cloud drops, you choose what happens: by default,
allowdenies until it's back (denyUnconnected), so shared limits can't be overspent, and usage syncs when it reconnects.
A Checklist for Your Review
- Pin
@formata/limitrwith your lockfile, and upgrade on purpose. - With Limitr Cloud, set
httpHoststo the hosts your policies call, orfalse. - Give policies functions, not access, and keep secrets in your app.
- Set the time limit to what your policy functions need.
- Override engine behavior in your own types; the engine stays sealed.
- Review policy changes like code: in your repo, or as drafts in Limitr Cloud.
The reference version of this post is the Security model page, with details in Runtime and sandbox. Stof's side of it, including sealing, digests, and signatures, is in Running Logic You Didn't Write. If your security team wants to walk through the architecture with us, book a call.
