惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
博客园_首页
H
Help Net Security
博客园 - Franky
V
Visual Studio Blog
Jina AI
Jina AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
宝玉的分享
宝玉的分享
酷 壳 – CoolShell
酷 壳 – CoolShell
J
Java Code Geeks
L
LangChain Blog
腾讯CDC
Engineering at Meta
Engineering at Meta
D
DataBreaches.Net
爱范儿
爱范儿
Google DeepMind News
Google DeepMind News
C
Check Point Blog
博客园 - 聂微东
罗磊的独立博客
量子位
M
MIT News - Artificial intelligence
F
Fortinet All Blogs

Cerbos - All Posts

Authentik vs Keycloak: Self-hosted IdP comparison Mapping business requirements to authorization policy for automotive Fine-grained authorization for AI gateways EIC 2026: Stop counting agents, protect what they can touch Agent skill for writing authorization policies in Claude Desktop Identity security in 2026 EIC 2026 takeaways: the identity stack built for humans will not hold up for AI agents Already have authentication? Here's the authorization layer you still need. Tokens are authorization decisions: a guide to policy-driven token issuance What is a Runtime Authorization Platform It's a dimmer switch, not a kill switch. How CISOs are rethinking AI agent governance From maps to bitmaps (and from bitmaps to bitmaps) AuthZEN, Shared Signals, SCIM Events, IPSIE: Notes from the OpenID Enterprise Panel How do you update authorization policies without redeploying your application? IIW42 recap: Where agent authorization got real Cerbos PDP v0.52.0/v0.53.0: Engine performance, security hardening, and CEL path functions Authorization Management Platforms: what they do, how they work, and where they fit PocketOS AI coding agent deleted a production database in 9 seconds Non-Human Identity management still has a blind spot Supabase alternative in 2026: Best open source auth options Benefits of on-premise authorization: Why enterprises are moving toward self-hosted Authorization policies: How to write, test, and validate them (faster with AI) Agent skill for writing authorization policies How much does it cost to build authorization in-house? Why centralized authorization governance reduces incident response time OPA alternative Why AI agents make authorization a right now problem Modernizing legacy application authorization: why it’s your biggest security blind spot How to add authorization to legacy applications without code changes 5 authorization blind spots auditors find, and how to fix them
Query plan adapter for Drizzle ORM
Alex Olivier · 2026-02-20 · via Cerbos - All Posts

Externalized authorization separates access control logic from application code. Instead of scattering if statements and role checks across your codebase, you define policies centrally and query a decision engine at runtime. Cerbos is purpose-built for this: your policies live as code in version control, and applications call Cerbos to find out whether a given principal can perform a given action on a given resource.

This works well for individual access checks, but most applications also need to answer a different question: "which resources can this user see?" The naive approach -- fetch everything, then call checkResources on each row -- does not scale. You end up reading data the user will never be allowed to see, wasting database I/O and application memory.

Cerbos solves this with the PlanResources API. Instead of evaluating a specific resource instance, PlanResources uses partial evaluation to analyze your policies and return a query plan -- an abstract syntax tree (AST) that describes the conditions under which access is granted. The plan contains the same operators and attribute references your policies use, but structured as a tree that can be mechanically translated into any query language. The result is one of three outcomes:

  • Always allowed: the user can access every resource of this type, no filter needed.
  • Always denied: the user has no access at all, return an empty set.
  • Conditional: Cerbos returns an expression tree. Translate it into your database's filter syntax and let the database do the work.

The conditional case is where query plan adapters come in. They walk the AST, map Cerbos attribute paths to your schema's column names, and produce a native filter that your ORM or database client can execute directly. Authorization logic stays in your policies, and the database applies it at the query layer -- aligned with indexes and query optimization, not burning cycles in application code.

Today we are releasing @cerbos/orm-drizzle, a query plan adapter for Drizzle ORM.

Why Drizzle?

Drizzle has become one of the most popular TypeScript ORMs. It is lightweight, SQL-first, and gives developers direct control over the queries that hit the database. Its type-safe query builder maps closely to SQL, which makes it a natural target for translating Cerbos query plans into efficient database filters.

The adapter works with every SQL dialect Drizzle supports -- SQLite, PostgreSQL, MySQL, and PlanetScale.

How it works

queryPlanToDrizzle takes a Cerbos PlanResourcesResponse and a mapper that associates Cerbos attribute paths with Drizzle columns. It walks the expression tree and returns a Drizzle SQL fragment that slots straight into a .where() clause.

import { queryPlanToDrizzle, PlanKind } from "@cerbos/orm-drizzle";
import { resources } from "./schema";

const plan = await cerbos.planResources({
  principal: { id: "user1", roles: ["USER"] },
  resource: { kind: "document" },
  action: "view",
});

const result = queryPlanToDrizzle({
  queryPlan: plan,
  mapper: {
    "request.resource.attr.status": resources.status,
    "request.resource.attr.owner": resources.ownerId,
  },
});

switch (result.kind) {
  case PlanKind.ALWAYS_ALLOWED:
    return await db.select().from(resources);
  case PlanKind.ALWAYS_DENIED:
    return [];
  case PlanKind.CONDITIONAL:
    return await db.select().from(resources).where(result.filter);
}

Because the adapter produces a standard Drizzle SQL fragment, you can compose it with your own conditions using and() or or() like any other filter.

Relation support

Real-world authorization policies rarely check flat columns alone. A policy might grant access to documents owned by a specific department, where the department is a row in another table. The Drizzle adapter handles this with relation mappings that generate EXISTS subqueries, including support for nested relations and many-to-many joins.

const result = queryPlanToDrizzle({
  queryPlan: plan,
  mapper: {
    "request.resource.attr.tags": {
      relation: {
        type: "many",
        table: resourceTags,
        sourceColumn: resources.id,
        targetColumn: resourceTags.resourceId,
        fields: {
          name: {
            relation: {
              type: "one",
              table: tags,
              sourceColumn: resourceTags.tagId,
              targetColumn: tags.id,
              field: tags.name,
            },
          },
        },
      },
    },
  },
});

Full example

Consider a policy that allows users to view published documents, or any document they own:

# policies/document.yaml
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  resource: document
  version: default
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      roles: ["USER"]
      condition:
        match:
          any:
            of:
              - expr: request.resource.attr.status == "published"
              - expr: request.resource.attr.ownerId == request.principal.id

With Drizzle, the adapter turns this into a composable SQL filter:

import { GRPC as Cerbos } from "@cerbos/grpc";
import { queryPlanToDrizzle, PlanKind } from "@cerbos/orm-drizzle";
import { eq, and } from "drizzle-orm";
import { db } from "./db";
import { documents } from "./schema";

const cerbos = new Cerbos("localhost:3592", { tls: false });

async function listDocuments(userId: string) {
  const plan = await cerbos.planResources({
    principal: { id: userId, roles: ["USER"] },
    resource: { kind: "document" },
    action: "view",
  });

  const result = queryPlanToDrizzle({
    queryPlan: plan,
    mapper: {
      "request.resource.attr.status": documents.status,
      "request.resource.attr.ownerId": documents.ownerId,
    },
  });

  switch (result.kind) {
    case PlanKind.ALWAYS_ALLOWED:
      return await db.select().from(documents);
    case PlanKind.ALWAYS_DENIED:
      return [];
    case PlanKind.CONDITIONAL:
      return await db
        .select()
        .from(documents)
        .where(and(eq(documents.deleted, false), result.filter));
  }
}

The adapter produces a standard Drizzle SQL fragment, so you can compose it with your own conditions using and() or or() like any other filter.

Supported operators

The adapter covers the full range of operators that Cerbos can emit in a query plan:

  • Logical: and, or, not
  • Comparison: eq, ne, lt, gt, le, ge, in
  • String: contains, startsWith, endsWith
  • Existence: isSet
  • Collections: hasIntersection, exists, exists_one, all

Get started

npm install @cerbos/orm-drizzle

The full documentation and source are available on GitHub. If you have questions or run into issues, join the Cerbos community Slack.