Skip to content
Agentic Control Plane

Why Not Just Use Your Database's Permissions?

David Crowe · · 7 min read
architecture identity

There’s a question I keep getting from senior people — the ones who have built real infrastructure and are rightly suspicious of new categories:

“Why do I need a control plane for my agents? The database already has permissions. Postgres has row-level security. Salesforce has profiles. Just enforce policy in the data plane — it’s already there.”

It’s the strongest version of the objection, stronger than “why not OPA and a service mesh”, because it points at enforcement that already runs in production, guarding the actual data, with decades of hardening behind it. Nobody has to build anything. So let me take it as seriously as it deserves.

Keep your database permissions. They’re the floor.

First, the concession, because it’s not a small one: database grants and row-level security are necessary. An agent credential that can bypass RLS is a standing exfiltration path, and shipping one is negligence, not a trade-off. Everything below assumes you keep native permissions exactly where they are.

The argument is not permissions or a control plane. It’s that permissions answer one question — may this principal touch this data — and agents fail on questions the database structurally cannot see.

The part of the objection that’s already answered

The first failure is identity, and if you’ve read the three-party identity gap you know the shape: the agent arrives at your database as a service account. RLS evaluates the service’s identity, not the user behind it. Was it the CFO or the intern? The database can’t tell, so it grants the union of everything anyone might need, and your audit log attributes both of their queries to agent-service.

This isn’t hypothetical. There’s a public Microsoft support thread where a Fabric Data Agent returned all rows to every user — RLS was configured correctly, but the query ran under the agent’s identity because nobody propagated the user’s context through the AI layer. The permissions worked. The plumbing that would have made them mean something didn’t exist.

The determined skeptic answers: fine, build the plumbing. Propagate user identity, mint per-agent database roles, thread delegation through. And they’re right that it’s buildable — that’s the identity leg, and we’ve covered what it takes. But suppose you build all of it. Suppose every tool call arrives at Postgres with the right user, the right agent role, perfectly scoped.

You’re still blind, because of what a permission is.

Permissions are predicates over the wrong unit

A database permission is a static predicate over (principal, object): may this identity run this class of statement against this table. That’s the right unit for protecting data at rest inside one system.

Agent risk lives in properties of the action — and the database can’t see any of them:

Content. An agent asks your CRM for customer records: crm.query("select email, ssn …"). The user has read access. RLS evaluates the predicate and — correctly — allows it. Then 1,240 rows of PII flow into a model’s context window, and from there into a completion, a log, a third-party API, wherever the loop goes next. The risk was never the access. It was the destination. No grant in any database expresses “allowed to read, not allowed to pipe into a prompt.” That check can only run at the boundary where the data crosses into the AI layer — which is a place the database has never heard of.

Cost. Our own metering shows the same agent task completing for 0.9¢ on the median run and $501 in the tail — same goal, same tools, different loop. There is no GRANT $10 PER RUN in SQL. Spend is a property of the sequence, visible only from a vantage point that sees every model and tool call in the run. A per-statement permission check sees one statement, priced at zero.

Aggregates. Each call an agent makes can be individually innocuous while the swarm is the incident: a user spawns five subagents, each under its own rate limit, collectively 10× over yours. We’ve watched five scheduled agents drain an API credit pool in eleven minutes without a single unauthorized call among them. Per-resource enforcement evaluates calls one at a time. Runaway is a property of the count.

The chain. Agent A delegates to agent B, which calls a tool. What should B inherit — A’s authority? The user’s? The intersection, narrowed to the task? Delegation provenance and scope inheritance have no representation in a grant. A database sees whoever showed up with credentials; the question of how authority flowed to them is decided — or not decided — upstream.

Your database is the best-case surface. The agent doesn’t stay there.

Here’s the part of the objection that quietly generalizes from its strongest example. “The database already has permissions” is true — of the database. It is the most governable thing your agent touches.

Now look at the rest of the tool surface. We counted the tools exposed by a stock Claude Code install: 76. Bash is one of them, and Bash has no permission model at all — grep and rm -rf are the same permission to the operating system. Most SaaS APIs offer OAuth scopes so coarse they amount to admin-or-nothing; row-level anything doesn’t exist there. Email sends, browser sessions, webhook posts: no rows, no grants, no RLS.

So “enforce policy in the data plane” really means “enforce policy in the one data plane that has a policy vocabulary, and hope the loop stays inside it.” Agents don’t. The same run that reads your database sends the email. If your enforcement lives in Postgres, the blast radius is everything that isn’t Postgres.

Even when the database says no, you learn nothing

One more property of terminal enforcement worth naming: a deny at the resource is nearly as bad as an allow, because of when it happens and what it leaves behind.

By the time the database rejects a statement, the model has already composed the query — table names guessed, relationships probed. At agent speed, a permission error is a schema oracle that answers thousands of times before a human reads one log line. And the record of all this is scattered: each backend logs its own fragment, in its own format, attributed to its own principal. “What did this agent do across every system, on whose behalf, and what did it cost” is unanswerable by construction from N resource logs — it requires the one chokepoint that saw every call. That chokepoint is also the only place a kill switch can live that stops the run rather than one system’s slice of it.

We’ve run this movie three times

None of this is a new pattern. Every host had a firewall; we still built network control planes when actors outgrew per-host rules. Every cloud resource had ACLs; we still built IAM, org policies, and CloudTrail — not because ACLs were wrong, but because “org-wide policy” and “unified audit” aren’t expressible one bucket at a time. Kubernetes nodes had permissions; we still built admission controllers.

The pattern: when actors × actions × surfaces outgrows what per-resource grants can express, a control plane emerges — and the grants stay, as the floor. Agents are the steepest version of the curve yet: probabilistic actors, created weekly, acting across every system you own, at machine speed.

What native permissions can and can’t express

We maintain a 48-scenario benchmark of agent-control failures, in eight categories. Read the categories against what a perfectly-configured data plane can express:

Failure class Can database permissions express it?
Identity propagation (who’s behind the agent) Only with plumbing the objection assumes exists
Per-user policy through an agent Partially — same plumbing required
Delegation provenance (A → B → tool) No representation
Scope inheritance (child narrower than parent) No representation
Rate-limit cascade (N subagents) No — per-call enforcement can’t see the count
Audit completeness across systems No — each resource logs its own fragment
Fail-mode discipline (fail-closed on outage) The grants are the floor that makes this survivable
Cross-tenant isolation Yes — this one is genuinely the database’s job

Frameworks’ own native controls, no control plane, pass 13 of 48. That’s frameworks, not Postgres — but the table is the honest version of the answer: of the eight ways agents fail, native permissions fully own one, and it’s the one nobody was worried about.

So when is the objection right?

If you have one agent, it only touches one database, and you’ve already propagated user identity to it — extend what you have. Genuinely. RLS plus real identity plumbing covers that case, and a control plane would be ceremony.

The break-even arrives with the second agent and the second surface, and it compounds: every new agent × system pair is new grant work, for principals that should often live for one task, guarding against risks — content egress, spend, cascades, chains — that no grant can express at any price. That’s when the question stops being “why does this category need to exist” and becomes the same question it became for networks, cloud, and Kubernetes: why would you rebuild it per resource?

Keep the floor. Add the layer that sees the loop.

What is an Agentic Control Plane? → · The benchmark → · Get started →

Get the next data drop
What agents actually cost, new tool-surface captures, and the occasional incident post-mortem — sent when we publish something worth your inbox, not on a schedule. Unsubscribe anytime.
Share: Twitter LinkedIn
Related posts

← back to blog