PermDock

PermDock

Typed permissions for TypeScript apps, APIs, databases, and AI agents.

PermDock lets you define permissions once as typed references over the Zod, Valibot or ArkType schemas you already have, grant them to roles with portable conditions, and check them in React, React Native, Next.js, Hono, tRPC and MCP servers. The same conditions compile to SQL where clauses and Postgres Row Level Security policies, and the same decision object drives tool approvals in the Vercel AI SDK, the Claude Agent SDK, Eve and the OpenAI Agents SDK.

pnpm add permdock
import {
  definePermissions,
  defineRoles,
  resource,
  crud,
  definePolicy,
  role,
  allow,
  principal,
  createPermDock,
} from "permdock";

export const permissions = definePermissions({
  post: resource(Post, crud({ id: "id" })),
});

export const roles = defineRoles({ member: {} });

export const policy = definePolicy(
  { permissions, roles },
  {
    roles: [
      role(roles.member, [
        allow(permissions.post.read),
        allow(permissions.post.create),
        allow(permissions.post.update, { where: { authorId: principal.id } }),
      ]),
    ],
    principal: (user: User | null) =>
      user && { id: user.id, roles: user.roles },
  },
);

const permdock = await createPermDock(policy, user);
permdock.can(permissions.post.update, post); // boolean
permdock.decide(permissions.post.delete, post); // { outcome: 'granted' | 'denied' | 'approval-required', ... }

What makes it different

  • Reference-based. permissions.post.update is a typed object with a key, a scope and a schema. Go-to-definition works, renames are safe, and there are no template-literal unions for TypeScript 7 to chew through.
  • Standard-Schema-native. Resources are defined from any Standard Schema validator; instance types are inferred; untrusted inputs are validated at trust boundaries.
  • Policy as data. Roles are arrays of allow / deny grants with portable conditions. One condition evaluates in the browser, filters arrays, compiles to Drizzle / Prisma / Kysely where, and generates RLS.
  • Decisions, not booleans. decide() returns granted, denied or approval-required with reasons and alternatives; adapters turn that into Problem Details, MCP refusals and AI SDK approval states. explain() adds a trace that names the deny that won.
  • Non-blocking UI. Snapshots carry conditions to the client so usePermission answers ownership checks offline and <Protected> never blocks a Next.js 16.3 instant navigation.
  • Agent-native. Two-principal subject (principal + actor + delegation), policy delegations that cap what an agent may do for a user, MCP, AI SDK, Claude Agent SDK, Eve and OpenAI Agents adapters, human approvals with quorum and stages, an AuthZEN decision endpoint, skills and llms.txt.
  • Authentication stays upstream. PermDock consumes verified material only: sessions, JWKS-verified JWTs via permdock/jwt (with a FAPI 2.0 profile), Supabase, Clerk and Better Auth claims, MCP authInfo, workload identities. Core never verifies a token.

Where to go next

  • Installation and the quick start.
  • Larger apps: define per feature, merge centrally, generate from RLS or OpenAPI.
  • Existing apps: adopt PermDock beside the permission keys, SQL helpers, tokens and custom roles an app already has.
  • Adapters: one page per framework, runtime and data layer; includes your own CLI.
  • Standards, the standards watch list and security.
  • Comparison with CASL, Kilpi, permix, Cedar, OPA, the Zanzibar family and hosted PDPs, with a pick-by-use-case guide.
  • Research: the libraries and products PermDock learned from. Design rationale lives in the Why section of each concept and adapter page.
  • Roadmap: what 0.1.0 contains, what is planned, and how versions are numbered.

Last updated on

On this page