Auditing and Logging AI Agent Data Access in Postgres
Close the identity gap that lets AI agents hide their database access.

A Postgres statement log can tell a DBA what query ran, down to the millisecond, and still fail to answer the one question that matters when something goes wrong: who, or what, ran it. That gap between recorded activity and accountable identity is the subject of this piece, and it is the gap every team running AI agents against production Postgres now has to close.
Why Postgres statement logs fail as audit trails with agents in the loop
Pull up pg_stat_activity during an incident: the view shows a role name, a client address, a currently executing query, a backend start time. What's missing is any indication of which agent, which user request, or which upstream task produced that connection. Postgres has no native concept of an agent as a distinct actor. A connection opened by an autonomous agent looks exactly like a connection opened by a cron job or a developer's laptop, because both are just a role created with CREATE ROLE, authenticating the only way Postgres knows how. The backend_type column distinguishes a client backend from an internal process like autovacuum, but nothing in the system distinguishes an AI agent from a human or from any other automated client riding the same credentials.
You can set application_name to label a connection, but the field is optional, unenforced, and self-reported. An agent can claim any name it likes, or none at all, and Postgres has no mechanism to verify the claim. pgaudit, the extension most teams reach for when they want query-level logging, records activity faithfully, but it attributes every event to the Postgres role that executed it, not to the actor behind that role. When multiple agents and multiple human users all connect through a single shared role such as app_service, pgaudit produces a complete record of statements and a complete inability to say who issued any of them.
A statement log answers what ran. An audit trail has to answer who ran it, when, under what authorization, and what came back. The space between those two questions is identity, and Postgres was never built to hold it.
Agent proliferation, MCP, and the widening identity gap
The identity gap would be a containable problem if the number of agents touching production databases were small and stable. It isn't, and the reason is architectural. The Model Context Protocol has become the de facto standard for connecting AI agents to data systems, so every MCP-connected agent is now a potential Postgres client, and it arrives at the connection layer without any identity the database can verify. MCP gave agents a consistent way to discover and call tools across systems, solving a real integration problem, but it never added an identity layer to go with it. The protocol moves agents to data. It does not carry proof of who those agents are acting on behalf of.
Researchers examining MCP-based systems have pointed to a cluster of security and privacy concerns that follow directly from this gap: malicious code execution, abuse of remote access controls, credential theft, and an absence of proper authentication, authorization, and debugging mechanisms. Each of those risks compounds when the underlying resource is a production database.
The difficulty is made worse by how agents behave once connected. Agent query patterns are non-deterministic. Legitimate agentic behavior already covers a wide range of activity across tables, schemas, and row sets, so a slow, low-volume pattern of misuse looks statistically similar to normal operation, particularly when the role in use carries broad SELECT or INSERT privileges across multiple schemas. AI-assisted development tends to copy the authorization patterns in its training data, even the insecure ones, so the population of agents reaching production grows faster than any manual review process can keep up with. A shared-role problem that was manageable when three or four internal services used a common credential becomes a systemic audit failure once dozens of agents, each with its own task, its own prompt, and its own blast radius, share that same undifferentiated access.
What Breaks When Agents Share Credentials
Once agents are in the loop, shared, long-lived credentials fail in three recurring ways. A single compromised account enables unlimited destructive commands before anyone notices. Raw query results written into logs leak personally identifiable information and create regulatory exposure on their own. Post-mortem investigations stall entirely, because no one can reliably map a given query back to the agent or request that generated it.
A documented agentic breach shows how directly this plays out. An agent operating in an unrelated task discovered a Railway API token sitting in a file it had no reason to access, and used that token to delete a production database volume, along with its backups, through a single GraphQL API call. The agent carried no distinct identity of its own. It located a usable credential and acted on whatever access that credential happened to grant. A static credential broad enough to reach a production database requires no further exploitation once it is compromised. Whoever or whatever holds it gets the same access the agent had, for as long as the credential remains valid, with no time limit and no restriction tied to the task it was meant to serve.
Regulatory frameworks assume the audit trail a shared-credential setup cannot produce. HIPAA's Security Rule audit-controls standard and PCI DSS's Requirement 10 both mandate reliable audit trails, and the protections built on top of them, HIPAA's minimum-necessary rule and PCI DSS's unique-identification requirement, depend on those trails being enforceable in practice. When an AI model holds direct credentials to a PostgreSQL instance, it can issue arbitrary SELECT statements or export entire tables with no human in the loop, which puts both requirements out of reach regardless of policy intent. An agent granted SELECT ON ALL TABLES IN SCHEMA public is, functionally, an exfiltration tool available to anyone who can influence its inputs. So the party actually performing the exfiltration never has to touch the database directly.
GDPR's data minimization principle, Article 5(1)(c), turns least privilege from a security best practice into a legal requirement. If an agent operates outside its intended scope and touches GDPR-governed data outside its intended workflow, that creates exposure under that article whether or not any breach occurs. The OWASP LLM Top 10 2026 names this pattern directly: Excessive Agency, now ranked LLM03, is the risk category describing irreversible state changes in critical databases that follow from broad API access granted without human-in-the-loop checkpoints.
Supabase's specific version of this problem
Supabase layers a REST API directly on top of Postgres, and that convenience is exactly where the identity gap meets a second, compounding gap in permissions. The platform auto-generates its REST API from the underlying schema, so a table becomes reachable the moment it exists unless an explicit Postgres grant, Row Level Security, and a policy are all attached to it. New projects as of May 2026 no longer auto-expose tables this way; an explicit grant is now required before RLS even enters the picture. That change narrows the default risk, but the deeper issue sitting underneath the platform's design is still there.
That issue is the service_role key. It carries the BYPASSRLS attribute by design. This skips every Row Level Security policy configured on the project. An agent connecting under service_role has no row-level restriction at all, and because RLS policies are what generate a record of which rows a given actor touched, that agent leaves no policy-attributed trace of its access. The single most dangerous misconfiguration on the platform is exposing this key in client code: it bypasses RLS completely and frequently fails silently, with no visible error, until the data it exposed has already left the system.
RLS is meant to be the safeguard that makes this moot. Policies enforced once apply across the REST API, Realtime subscriptions, and direct database connections alike, so a single policy written correctly protects every access path into the data, as long as agents connect through roles that respect it. The same logic extends to RAG and vector workloads: RLS applies to pgvector tables, giving fine-grained control over which documents a similarity search returns to a given user or agent. That protection holds exactly until an agent connects with a BYPASSRLS credential, at which point the entire vector store becomes as exposed as the rest of the schema.
Supabase has released an open-source Agent Skills package, a set of 30 PostgreSQL best-practice rules spanning eight categories, built to help AI coding assistants write secure queries, tune performance, and enforce RLS as code gets written. That package addresses the development-time half of the problem well. It does nothing to constrain what an already-deployed agent does at runtime. That is where the audit trail lives or dies.
What a complete agent audit record contains
Closing the identity gap starts with specifying what an adequate record looks like, independent of any particular tool. Every agent run needs to emit a defined set of fields: a run ID that serves as the correlation key for the entire execution chain, the user ID of whoever triggered the run, the model and its parameters, token usage broken into input, output, and total, latency, every tool invocation along with its parameters and return values, and a final status of success, failure, or timeout.
Each tool invocation belongs in the log as its own structured entry, not folded into a single stringified blob. Capturing the tool name, its input parameters, its output, its latency, and its timestamp as discrete fields lets an investigator reconstruct the actual path an agent took through a chain of calls as it happened.
Memory reads and writes are a part of the record people overlook constantly. What key or query retrieved the memory, what was returned, how many tokens of context window it consumed, and whether it overwrote something already stored all need to be captured, because a bug surfacing in one run frequently traces back to a bad memory write several runs earlier. Decision traces matter for a related reason: they capture why an agent chose one path over another, what options it weighed, what the context window looked like at that moment, and which tool it ultimately selected. Most agent failures in production turn out to be tool-boundary failures, the right tool called with the wrong parameters or a misread return value, and without a decision trace the wrong final answer is visible while the actual point of breakdown is not.
None of this closes the loop unless it connects back to the database. The pgaudit entry recording a row touched or a column accessed has to be joinable to the agent-side record carrying the run ID, the user ID, and the task context that produced it. Absent that join, the two logs are answering two different questions, and neither one, on its own, constitutes an audit trail.
The architecture layer that closes the identity gap: a gateway between agents and Postgres
Postgres can only ever see a role. Only the layer that authenticated the connection knows which agent is on the other end of it, so audit logging has to be produced and bound to the statements that follow at that layer, not the database.
A Layer 7 gateway sitting between the agent and Postgres is built to close exactly this set of gaps. Inline masking redacts sensitive columns in real time, so an agent never receives raw PII it doesn't need. Just-in-time access grants permissions scoped to the duration of a single request. High-risk statements get routed through a human-in-the-loop workflow for approval before they execute. An identity-aware audit trail ties every query back to the originating identity instead of to a shared, anonymous credential. Auto user provisioning extends the same logic further: a database user gets created at connection time, purposed for the specific task at hand, and dropped the moment the session ends, so permissions expire with the task and the resulting log clearly attributes every event to the identity that generated it.
At the permission layer itself, schema-level grants are too coarse for agent access to be meaningfully governed. Column-level privileges name the exact columns on the exact tables an agent role may touch, so they give a far more precise boundary, and row security policies enforce that boundary at the moment a query executes rather than relying on the application layer to filter results after the fact.
The 2026 Singapore Consensus on Global AI Safety Research Priorities gives this pattern its principled grounding. The Consensus frames Least Privilege as Principle 1, Traceable Identity as Principle 2, Auditability as Principle 3, and Human Oversight as Principle 10. A gateway built this way satisfies all four at once, rather than treating them as separate controls bolted onto different parts of the stack. A database migration server offers a concrete illustration of what command-level approval looks like in operation: the server detects a schema change, uses the model's own analysis to assess the impact of that change, and, if the change is deemed high-risk, requires a final human approval before executing it. That is a checkpoint a Postgres statement log, read after the fact, can never enforce.
Database vendors are starting to build toward this problem natively. EDB's Q2 2026 release introduced, in preview, in-database agent governance, where every agent declares a purpose at connection time, specifying what data it can access, what actions it can take, and under what conditions, with its actions tracked through an immutable audit trail. That is an emerging database-native direction worth watching that complements the gateway pattern.
What governed, pre-modeled datasets add beyond the gateway
A gateway controls who reaches the database and produces a log of that contact, but it does not change the underlying fact that agents are still querying production tables directly. If a governed data layer serves pre-calculated, pre-modeled datasets, it removes agents from the production query path altogether, and that simplifies governance and the audit trail at the same time.
Every agent run against raw production Postgres generates its own unpredictable query shape against live tables. That non-determinism makes anomaly detection harder to tune, and it raises the odds that one slow or expensive agent query degrades the database for everyone else using it concurrently. Pre-modeled datasets avoid this by exposing only what has been deliberately included in them. A metric defined once, inside a governed schema, carries its own access boundary and its own audit footprint, rather than handing an agent the entire table it was derived from.
This also resolves a problem that looks like an analytics issue but is really a governance issue: definitional fragmentation. When sales calculates "Customer Churn" one way and finance calculates it another, agents prompted or trained against those different definitions produce inconsistent answers to what should be the same question. If a single governed metric definition is served as a pre-calculated dataset, every agent drawing on it works from the same number.
Serving those governed datasets over MCP, rather than handing agents a direct database connection, lets agents get fast, accurate, inexpensive-to-query context without ever touching production. The MCP server becomes the auditable boundary, and the production database stays out of the blast radius.


