Skip to content

Security model

← Documentation home · See also SECURITY.md

Authorization libraries sit on your security boundary. Sentinel defaults and invariants are documented here so you can threat-model without reading the implementation.


Deny by default

If no rule matches a request, the effect is deny (default-deny). You can set defaultEffect: "allow" in engine options, but the default is deny — the common secure choice for SaaS.

Unmatched requests produce a clear reason: "No matching rule — default deny".


Fail closed on condition errors

When a condition function throws, the engine treats it as false — access is not granted because a check failed loudly.

Optionally register onConditionError to log ruleId, conditionIndex, and the error without changing the deny outcome.

Why: A bug in a condition must not become accidental allow. Silent success on thrown errors would be privilege escalation.


Immutable rules after insertion

addRule() freezes each rule with Object.freeze(). Mutating a rule object after registration does not change engine behavior.

Why: Shared rule arrays passed from config loaders cannot be tampered with at runtime by unrelated code.


Cache safety

The optional LRU cache (cacheSize) only stores results for evaluations where no condition ran on the matching path — context-independent allows/denies.

Conditional evaluations are never cached, because the same subject/action/resource can allow or deny depending on resourceContext.

Adding or removing rules clears the cache.


Strict tenancy

With strictTenancy: true, evaluating a subject that has tenant-scoped role assignments without passing tenantId throws instead of guessing.

Why: Omitting tenant context should fail in development, not leak the wrong role set into production.


JSON import validation

importRulesFromJson() validates structure and rejects invalid effect values. Conditions resolve only through a ConditionRegistry you control — arbitrary code is not deserialized from JSON.


Server mode hardening

createAuthServer() supports:

  • authenticate(req) — reject unauthenticated callers before evaluation
  • maxBodyBytes — bound request body size (default 1 MB)

Without authenticate, every client that can reach the port can call /evaluate. Use only on trusted networks or behind mutual TLS.


What Sentinel does not provide

  • Authentication — you must establish identity before calling evaluate()
  • Rate limiting — protect the auth server and HTTP routes separately
  • Encryption — transport security is your TLS layer
  • Policy signing — if you load rules from a database, protect integrity at the storage layer

Responsible disclosure

Report security issues per SECURITY.md.

Released under the MIT License.