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

推荐订阅源

V
V2EX
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
WordPress大学
WordPress大学
罗磊的独立博客
小众软件
小众软件
I
InfoQ
Y
Y Combinator Blog
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hugging Face - Blog
Hugging Face - Blog
MyScale Blog
MyScale Blog
博客园 - 聂微东
Microsoft Security Blog
Microsoft Security Blog
H
Help Net Security
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
S
SegmentFault 最新的问题
博客园 - 三生石上(FineUI控件)
P
Proofpoint News Feed
博客园 - 司徒正美
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
N
Netflix TechBlog - Medium

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.