Skip to content

vegtelenseg/sentinel

Repository files navigation

@siremzam/sentinel

npm version CI documentation Deploy docs zero dependencies license

All your permission logic in one place. Type-safe. Multi-tenant. Explainable.

Stable 1.0 — semver guarantees apply from this release. See API stability policy.

Most auth libraries give you create, read, update, delete and call it a day. Your app has invoice:approve, users are admin in one tenant and viewer in another, and when access breaks nobody can tell you why without grepping the codebase.

Sentinel replaces scattered role checks with a single policy engine — domain actions instead of CRUD, tenant-scoped roles by default, and every decision tells you exactly which rule matched and why.

Zero dependencies. ~1,800 lines. 1:1 test-to-code ratio.


Documentation

Documentation: vegtelenseg.github.io/sentinel · edit on GitHub

For AI agents: AGENTS.md — compact reference (read this instead of the full docs tree). Index: llms.txt · llms.txt on docs site

Start here
New to Sentinel What is Sentinel?
Five-minute setup Quickstart
How decisions are made How evaluation works
Try in the browser Interactive playground

Install

npm install @siremzam/sentinel

Quick example

Without Sentinel — scattered, fragile, no tenant awareness:

app.post("/invoices/:id/approve", async (req, res) => {
  if (
    user.role === "admin" ||
    (user.role === "manager" && invoice.ownerId === user.id)
  ) {
    // which tenant? who knows
  }
});

With Sentinel — centralized, type-safe, explainable:

import { AccessEngine, createPolicyFactory } from "@siremzam/sentinel";
import type { SchemaDefinition, Subject } from "@siremzam/sentinel";

interface AppSchema extends SchemaDefinition {
  roles: "admin" | "member";
  resources: "invoice";
  actions: "invoice:approve" | "invoice:read";
}

const { allow } = createPolicyFactory<AppSchema>();
const engine = new AccessEngine<AppSchema>({ schema: {} as AppSchema });

engine.addRule(
  allow().roles("admin").actions("invoice:approve").on("invoice").build(),
);

const user: Subject<AppSchema> = {
  id: "u1",
  roles: [{ role: "admin", tenantId: "acme" }],
};

engine.evaluate(user, "invoice:approve", "invoice", {}, "acme"); // allowed

Protect a route:

import { guard } from "@siremzam/sentinel/middleware/express";

app.post(
  "/invoices/:id/approve",
  guard(engine, "invoice:approve", "invoice", {
    getSubject: (req) => req.user,
    getTenantId: (req) => req.headers["x-tenant-id"],
  }),
  handler,
);

→ Continue in the Quickstart for schema design, ABAC conditions, multitenancy, and explain().


Why teams use Sentinel

  • Type-safe schema — typos in actions fail at compile time, not in production
  • Domain actionsinvoice:approve, not generic CRUD
  • Built-in multitenancy — per-tenant roles; optional strictTenancy
  • explain() — per-rule traces when debugging access
  • Audit hooksonDecision + toAuditEntry() for structured logs
  • Zero dependencies — small surface, easy to review

Why Sentinel? · Feature comparison


Examples

Example Description
standalone Engine only — evaluate, permit, explain
express-multi-tenant HTTP API with tenant header

Security

Deny by default. Fail closed on condition errors. Frozen rules. See Security model and SECURITY.md.


Contributing & license

About

TypeScript-first, domain-driven authorization engine for modern SaaS apps.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors