Agentic DataLong read

Safe Agent-to-Data Access Patterns for Postgres

Postgres permissions alone won't stop an agent from exfiltrating data.

Staff Writer · · 11 min read
Cover illustration for “Safe Agent-to-Data Access Patterns for Postgres”
Agentic Data · October 6, 2026 · 11 min read · 2,508 words

A production Postgres database connected directly to an AI agent is a bet that nothing the agent does will exceed what its credentials allow. That bet is lost more often than the architecture suggests, because the danger scales with whatever access the agent holds, not with whether the agent means harm.

Giving AI agents raw Postgres access is a bet against your own data

An agent does not have to go rogue to cause damage. It only has to try to be useful in a way nobody planned for: a query that returns every row in a users table because the agent judged that the most complete answer to a question, a DELETE statement built while trying to satisfy a cleanup request, a full scan across a table that production traffic depends on every second. None of this requires malice or a jailbreak. It requires an agent doing what it was asked, with more reach than the task called for.

A controlled experiment run by Datapace on a large database put numbers to this. A SELECT-only role dumped every customer email address to a local file in roughly half a second. No grant was exceeded. Nothing was misconfigured. The role did precisely what SELECT permits, and SELECT permits reading everything within its scope. Read-only stops writes; it does nothing to stop a read from walking out the door with the whole table. The same experiment ran a DROP TABLE to completion in roughly one millisecond, fast enough that a Slack alert announcing the action would still be rendering after the table was already gone. An approval step an agent can finish its action before a human even sees the request is a formality that is still running when the damage is already done. Only a control that sits in the data path itself, enforced without waiting on a person, can stop something that moves at that speed.

Human users get tired, hesitate, second-guess an instruction that feels off. Agents do none of that. They act on whatever instructions reach them, including text buried in a support ticket, a webpage they were asked to summarize, or a row inside the very database they're querying. A 2026 survey of agentic AI trustworthiness names tool-use as one of the central new attack surfaces agents introduce, alongside memory, planning, and multi-agent coordination, and it specifically calls out prompt injection, tool misuse, and data exfiltration as risks that standard LLM safety evaluations never catch, because those evaluations don't account for agents that act across many steps with only occasional human review in between.

An agent can only do as much damage as its permissions allow. Scope those permissions tightly and the worst case is bounded and known in advance. Leave them broad, and everything the database holds becomes the worst case.

Diagram: Two Incidents, Two Millisecond Consequences. Visualizes: Show the extreme speed of two real database incidents from the Datapace experiment, contrasted as a before/after or two-stat callout: (1) a SELECT-only role dumped every customer…

What Postgres already gives you to constrain agent access (and where it stops)

Postgres ships with real, well-built access controls, and any team putting an agent near production data should configure them as a baseline, not as an afterthought. These controls are necessary. They are not sufficient, and the space between "necessary" and "sufficient" is where most of the risk described above still lives.

Start with identity. Every agent needs its own dedicated, least-privilege role; it should never share an application account, and never borrow an admin credential for convenience. Postgres's built-in pg_read_all_data role looks like an obvious shortcut for a read-only agent, but it was built for full-cluster dumps and backups, not for scoping a single agent to the handful of tables it actually needs. Per-schema SELECT grants or purpose-built application roles get much closer to what an agent deployment actually requires. Pair that with routing: analytics and exploratory agents belong on a read replica, so their queries never contend with production writes or hold locks on tables the rest of the application depends on.

Row-level security goes further than role scoping alone. In the Datapace experiment, one CREATE POLICY statement took what had been a total cross-tenant data leak and turned it into clean, per-tenant isolation on the exact same query. The policy enforces automatically, inside the engine, without asking the agent's permission or waiting for a human to notice something went wrong. statement_timeout works the same way: set a limit and a runaway query generating an unbounded join gets aborted by the server itself. In the same experiment, a timeout cut off a runaway query at exactly two seconds, with no operator intervention required once the setting was in place.

All of this is real protection, and none of it closes the full gap. Read-only privileges stop writes, but they do nothing to stop a SELECT on unrestricted columns from returning every value an agent asks for, as the half-second email dump in the Datapace experiment demonstrated. Roles and row-level security enforce what's permitted, not what's correct: an agent holding SELECT on exactly the right tables can still construct a query that is fully valid and still violates a regulatory, privacy, or business rule nobody encoded into a grant. A 2026 paper on Data Flow Control makes the distinction precise: a query being semantically valid says nothing about whether it's safe, because safety depends on constraints over how data gets combined and released, constraints the engine's permission system was never built to express. That same research, through a query-rewriting layer called Passant, found that enforcing those policies across five different database engines, including PostgreSQL, added roughly 0% overhead. The cost of doing this right is not the reason teams skip it.

The engine also has no notion of which tables an agent should see, as opposed to which tables a role's grants technically permit it to touch. Without a separate restriction layer, billing tables, internal configuration, and audit logs sit exposed to any role with broad SELECT access. And grants are static: whatever an agent was authorized to do the moment its session opened stays fixed for the life of that session, even if the context that justified that access has since changed. Postgres does not re-check authorization mid-query. These are the edge of what a database engine was ever designed to do, and the reason the next layer of the architecture has to sit above it.

The four-level safety layer that closes the gap the engine leaves open

Diagram: The Four-Level Agent Safety Stack. Visualizes: Illustrate a four-level vertical stack (or stepped funnel) showing how agent SQL requests are filtered before reaching the database.

Closing that gap takes a runtime control plane that sits in front of the database, and it operates independently of whatever privileges the engine's own roles grant. Its job is narrow: make sure the distance between what Postgres permits and what a given agent should actually be allowed to do, in this moment, for this task, collapses to zero. Four levels do this work together, each one catching what the level before it misses.

The first level is an operation allowlist. Only specific, permitted SQL operations ever reach the database; everything else gets rejected before it executes. An agent gets SELECT and nothing more, unless a team has explicitly granted it additional operations. This lives in code, not in a prompt, which matters because a prompt is a suggestion an agent can be talked out of, while code-level enforcement holds regardless of what the agent was told or convinced to do mid-conversation.

The second level is schema restriction. An agent can query only the tables a team has explicitly listed as visible to it. Billing tables, internal configuration, audit logs simply don't appear in the agent's view at all, which removes the entire category of harm that depends on an agent even knowing a table exists.

The third level is query validation, run before execution. Every query gets parsed and checked before execution: unbounded queries are rejected, every SELECT must carry a LIMIT, and cross-table JOINs require explicit permission to run. This is the level that catches the harm that looks like a correct query, the full table scan that returns valid rows from a valid table but still takes down a production system because nothing bounded its size. Dynamic identifiers deserve their own attention here: parameterizing a query's values is standard practice against SQL injection, but it does nothing to make an untrusted table name or column name safe. Allow-listing which identifiers, sort fields, and operators an agent can reference is a distinct step, separate from parameter binding, and skipping it leaves a hole that parameterization was never built to cover.

The fourth level is the one most safety checklists leave out entirely: current documentation of what the data actually means. A query can pass every allowlist, schema restriction, and validation check and still be wrong, because the agent picked the wrong column, misunderstanding what it represented. A permitted query built on a misunderstood schema is still a wrong answer delivered with full confidence. The control plane needs semantic grounding, not just syntactic rules, and this is the distinction Datapace's own findings underscore directly: the database engine supplies the static controls, but the control plane, armed with real context about what the data means, is where actual incidents get stopped.

One architectural pattern makes all four levels easier to enforce at once: instead of handing an agent generic SQL access, expose named, scoped tools built around specific intents. A tool like get_customer_order_summary(customer_id, start_date, end_date) or list_overdue_invoices(account_id, limit) carries its own strict input schema, its own authorization check, a built-in row limit, a timeout, and documented output. The agent never writes SQL. It calls a function that was already scoped, validated, and bounded before the agent ever saw it.

How MCP becomes the governed interface, where it falls short on its own

The Model Context Protocol is the right transport layer for connecting an agent to a database, and it's the standard most teams are reaching for right now. Treating it as a complete safety solution confuses the steering wheel with the driving lessons: MCP determines how instructions reach the database, not whether those instructions are safe to execute.

Used well, MCP replaces improvised, freeform SQL with the intent-specific tool pattern described above: each tool carries a defined schema, and the agent simply cannot reach columns or tables that the tool wasn't built to expose. The protocol itself has also matured on the infrastructure side. The stateless protocol core introduced in the MCP 2026-07-28 specification revision removed transport-level session management, so an MCP server can scale across ordinary, load-balanced HTTP infrastructure without needing a database read and write on every single call just to track session state.

None of that touches correctness or safety on its own. MCP moves data and actions between an agent and a database; it has no mechanism for making that data correct. An agent connected through MCP to tables nobody has reviewed or certified still produces confidently wrong answers, just over a cleaner wire protocol. MCP's own security guidance flags a specific structural risk in proxy servers: a proxy can pass a caller's credentials straight through to a downstream system. The specification forbids token passthrough for exactly this reason: a proxy that forwards credentials creates a new principal acting on the database, one the database itself has no way to audit independently of the proxy. A stateless MCP server that drops accountability along with its session state is just a thin pipe. Every request still needs its own identity check, its own policy check, and its own log entry, regardless of whether the protocol layer underneath is holding state or not.

Research out of the University of Washington on agent permission systems identifies a further gap: a divide between product-level policies, fixed rules that apply to every user alike, and user-level policies, which depend on context that shifts from one request to the next. MCP doesn't resolve this gap. Nothing at the protocol level can, because the protocol's job is message passing, not policy reasoning. Governance has to sit above MCP to handle it.

The practical shape of this is a narrow policy-enforcement service sitting at the MCP layer, not a transparent pass-through to SQL. The MCP server validates every tool call, argument, identity, and limit; unknown tools and unexpected parameters fail closed by default. The Postgres role sitting behind that server holds no ownership and no administrative privileges, regardless of what the agent in front of it claims to need. For systems carrying more risk, splitting read and write into separate execution paths adds another layer of containment: a read server running against a read replica, scoped to a role limited to reporting views, and a write server exposing only a small, reviewed set of procedures behind explicit approval and policy checks.

Why Supabase teams face this problem faster than they expect

Supabase was built to let teams ship fast, and that speed is why Supabase teams run into agent-data safety problems earlier than most. An agent reaches production data on a Supabase project well before most teams have built the governed layer described above, simply because getting from zero to a connected agent takes so little setup.

Supabase has documented a recurring set of failure modes across its own projects, and they map closely onto the gaps described earlier. Missing indexes on foreign keys let an agent generate a join that behaves fine in development and then saturates production once real data volume hits it. Queries that bypass row-level security by accident are a particularly sharp case: an agent that builds SQL without awareness of the RLS policies in place can produce a query that is syntactically valid and fully compliant with the role's grants, and still returns data the requester was never supposed to see. Migrations that lock tables mid-deploy can block all writes during peak traffic if an agent runs a schema change without understanding the lock it's about to hold. Connection pools get exhausted when agents open connections and never close them cleanly, starving every other process competing for the same pool. Full table scans hide behind ORMs: the query looks reasonable at the ORM layer while generating an unindexed scan underneath that appears as a latency spike once someone checks the graph.

Supabase's answer to this is the supabase/agent-skills repository, an open-source set of 30 rules spread across 8 categories: query performance, connection management, row-level security, schema design, and concurrency and locking among them. The goal is to give AI coding agents guardrails before they generate harmful Postgres code. The pattern Supabase recommends pairs this with MCP directly: MCP handles execution access, while the skill-based rules shape what the agent proposes in the first place, warning before an index creation locks a table, suggesting an RLS policy before insecure code ships, and steering query structure away from known performance traps.

For analytics specifically, Supabase handles application-scale workloads and large row counts well, provided the right indexes are in place. What matters just as much as the database's own capacity is the path an agent takes to reach it. A reporting agent querying Supabase for analytics should be going through the governed layer described throughout this piece, the scoped roles, the schema restrictions, the validated queries, the intent-specific tools, rather than holding a direct connection to the production database itself.

Sources

  1. Towards trustworthy agentic AI: a comprehensive survey of safety, robustness, privacy, and system security
  2. How Agents Ask for Permission: User Permissions for AI Agents, from Interfaces to Enforcement
  3. Data Flow Control: Data Safety Policies for AI Agents
  4. PostgreSQL: Documentation: 18: 5.9. Row Security Policies
  5. Architecture overview - Model Context Protocol
Filed underAgentic Data

More in Agentic Data