Skip to main content

Security model

Limitr runs your pricing inside your app: every check happens in your process, and a policy can carry logic of its own. This page is what that logic can reach, who can change it, what leaves your process, and how the engine behaves when something goes wrong. For a walkthrough with examples, see Pricing Logic in Your Process.

What runs in your process​

  • One package, one dependency. @formata/limitr depends only on @formata/stof, which has no dependencies. Neither runs install scripts.
  • Open source. Both are Apache 2.0: the engine that decides what your customers can do is code you can read.
  • No network on the hot path. allow, check, reserve, and every other engine call run locally. Run the engine on its own and nothing leaves your process.

What policy code can reach​

Functions in a policy (cost functions, feature rules, capabilities, notifications) run in Stof's sandbox:

By defaultTo allow it
The policy and its customersYes
Stof's libraries (math, strings, time, ...)Yes
Your app's functionsOnly the ones you registerlimitr.doc.lib('App', 'notify', fn)
The network, Limitr.newNolimitr.doc.allowHttp(['api.example.com'])
The network, Limitr.cloudAny public hosthttpHosts: ['api.example.com'], or false for none
Your machine and private networkNoList the hosts
Files and environment variablesNoNot available

The private network is localhost, 10.x, 172.16.x to 172.31.x, 192.168.x, their IPv6 equivalents, names like *.internal, and 169.254.x (where cloud providers serve instance credentials). See Runtime and sandbox.

A sealed engine​

The engine and your policy share one document, which is how a policy extends the engine. The SDK seals the engine as it loads it (engine 0.8.3 and later): anything parsed into the document afterward, including your policy, a Cloud update, or a policy function, can call the engine's functions but can't change, remove, or replace them, and can't declare a type with an engine type's name. A policy customizes behavior with functions on its own objects, its own types, and subtypes that override engine functions (Extend Limitr).

Limits on every call​

LimitDefaultChange it
Time5 secondslimitr.setMaxExecutionTime(ms), or maxExecutionTime for Limitr.cloud
Memory256 MB of growthlimitr.doc.setMaxMemory(bytes)

A call that hits a limit fails with an error, and the next call runs as normal. See Runtime and sandbox.

Who can change your pricing​

On your own, the policy lives in your repo and changes through your review and deploy process.

With Limitr Cloud, policies change in drafts and reach your app only when someone publishes them:

  • Permissions. Publishing to live and publishing to test are separate permissions, given per teammate (Team).
  • Versions. Every published version is kept, with what changed, and you can roll back to any of them.
  • Limitr Analyst works in a draft and proposes changes; nothing is published or applied until someone on your team does it.

A published policy runs with the same sandbox and the same sealed engine as one in your repo.

Keys​

A Limitr Cloud key connects your app to one workspace, in Test or Live mode, and can expire. Store it as a secret on your server. For an SDK where end users could read the key (a browser, say), make it usage only: it can meter usage but can't create or change customers. See Connect your app.

What leaves your process​

Run the engine on its own and nothing does. With Limitr Cloud, over one WebSocket:

  • To your app: the published policy, and customer changes made in Cloud.
  • To Cloud: each call's usage (customer, entitlement, amount, cost, and time), the metadata you attach, and customer changes made in your app.

Your requests, prompts, and responses never pass through Limitr. See What syncs.

When something goes wrong​

  • 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 a limit fails on its own, and the engine keeps working.
  • A dropped connection to Cloud: by default (denyUnconnected: true) calls are denied until it's back, so shared limits can't be overspent, and usage kept while disconnected is sent when it reconnects.

Checklist​

  • Pin @formata/limitr with your lockfile, and upgrade on purpose.
  • With Limitr Cloud, set httpHosts to the hosts your policies call, or false.
  • Register narrow host functions with your checks inside, rather than opening network access.
  • Keep secrets in your app, not in the policy.
  • Set the time limit to what your policy functions need.
  • Override engine behavior in your own types.
  • Give publish permissions to the people who should have them, and use usage-only keys anywhere a key could be read.

Stof's side of all this, including sealing, digests, and signatures, is in Security and the Sandbox.