Skip to content
permdock 0.8.0 is out

Typed permissions for apps, APIs, databases and AI agents

Define a policy once in TypeScript. Check it in React, on the server and in agent tools; compile the same conditions to SQL and Postgres RLS.

Why PermDock

Authorisation as typed code, checked everywhere it runs

One policy object drives the UI, the HTTP layer, the database and the agent tools.

Typed references

Permissions are objects your editor completes. A renamed action fails the build instead of a request.
permdock.can(permissions.post.update, post)
readupdatedeletepublish

Three outcomes

decide() returns granted, denied or approval-required. There is no fourth.
grantedapproval-requireddenied

Conditions compile to SQL

One portable where runs in memory and compiles to Drizzle, Prisma, Kysely and Postgres RLS.
{ authorId: { eq: subject.id } }where author_id = $1

Agent tools behind the same policy

MCP, the AI SDK, the Claude Agent SDK and OpenAI Agents call decide() before a tool runs. An agent never approves its own call.
  • search_docsgranted
  • refund_invoiceapproval-required
  • delete_orgdenied

UI that matches the server

The client reads a snapshot of the same decisions, so a hidden button and a refused request never disagree.
usePermission(permissions.post.update, post){ allowed: false, decision: { outcome: 'denied' } }

Fail-closed

An unknown subject, an invalid body or a condition that throws is denied. can() never throws.
unknown subjectdeniedinvalid bodydeniedcondition threwdenied

Every surface

One decision, every surface

The same permission reference drives UI, HTTP, MCP tools, agent runtimes and generated RLS.

Interactive

decide() in the browser

A real PermDock policy. Switch role and action to see granted, denied and approval-required.

Portable-first

One where, three compilers

The same condition evaluates in memory, compiles to Drizzle, Prisma and Kysely, and generates Postgres RLS. Closures stay a branded escape hatch.

Agent-native

The same Decision gates the tool call

granted, denied and approval-required map onto MCP, the Vercel AI SDK, the Claude Agent SDK, Eve, OpenAI Agents, WebMCP and A2A. An agent never approves its own call.

Secure by default

Invariants, not a later pass

A PR that breaks one of these is wrong, whatever else it does.

Works with

Adapters you already ship

Each tile is a first-class entry. The public API is typed permission references, never string keys.

Get started

Install, define, grant, decide

  1. Install

    pnpm add permdock

  2. Define

    definePermissions with resource() over the schema you already have.

  3. Policy

    definePolicy with role(), allow() and portable conditions.

  4. Decide

    createPermDock, then can(), decide() or assert() on every surface.

Optional

The Cloud is optional

Every hosted capability has an in-process default. A store or sink never influences an outcome. Cloud outages do not change can() or decide().

FAQ

Before you adopt

Licensing, network, fail-closed, and where authentication lives.

The library is MIT. Every decision runs in-process. PermDock Cloud is optional and never sits on the decision path.

Ship the check with the feature

Install the package, define the resources you already have, and put decide() on the route, the tool and the query.