This is the list we work through before an AI agent is allowed near a production database, a payment API or customer data. It’s written for a small team: one or two engineers, an agent that already works in staging, and a launch date. Each item is a yes or no. If you can’t answer one, that’s the item to work on.
The public incidents in our incident catalogue share a pattern: the agent could reach far more than its task needed, and the only thing between it and a destructive action was an instruction in a chat. Most of this list is about closing that gap with things that don’t depend on the model behaving.
1. Know what it can reach
1. You have a written list of every credential the agent can read. Database URLs, API tokens, cloud keys, including the ones in .env files, MCP client configs and old scripts in the repo. An agent that can read the repository can read whatever is in it.
2. For each database credential, you know the role and what it can change. Not “it’s the app user”; the actual tables and verbs. The quickest way is to ask the database. Here is our scan against a demo store database, connected as the bootstrap superuser, which is what many agents get by default:
$ deedglass scan --postgres-dsn-env ACME_OWNER_URL
Blast radius: F · 6 write paths with no check · fix this first: The database role bypasses row-level security
A means a small blast radius, not a safe agent.
−35 superuser or BYPASSRLS postgres · Role postgres is superuser · BYPASSRLS
−8 medium finding The database role can create roles
−8 medium finding The database role can replicate
−8 medium finding The role can run SECURITY DEFINER functions
−4 unchecked write path public.customers · INSERT, UPDATE, DELETE, TRUNCATE · not shown to be routed
−4 unchecked write path public.orders · INSERT, UPDATE, DELETE, TRUNCATE · not shown to be routed
…
21 points
And the same database, connected as a role made for one support job (section 3 shows how):
$ deedglass scan --postgres-dsn-env AGENT_URL
Blast radius: A · 0 write paths with no check
A means a small blast radius, not a safe agent.
100 points
Postgres · acme · role agent_support · wal_level logical · read-only session · 5 tables read-only
Table Can change Est. rows Cascades to Triggers Undo-ready Proven-ready
public.orders UPDATE 500 none none ✓ ✓
The score starts at 100 and subtracts for a superuser or BYPASSRLS role, high and medium findings, and each write path not routed through a policy; A is 90 or more, F is under 50.1 The scan runs read-only and nothing leaves your machine.1 You can get the same facts by hand from pg_roles and information_schema.role_table_grants; the point is to look. Note the scan’s own caveat on the second line: a small blast radius is not the same as a safe agent.
3. You know which of those tables have triggers, cascades or functions that write elsewhere. A narrow UPDATE on a table with a trigger can change far more than one row. We wrote up a real run where 1 reported row was 301 committed rows.
2. Give it its own credentials
4. The agent has its own database role, not the app’s and not the owner’s. Separate roles mean separate grants, separate limits and a separate thing to switch off.
5. That role is not a superuser and doesn’t have BYPASSRLS, CREATEROLE or REPLICATION. On managed Postgres, check what your provider’s default user really is. On Amazon RDS for PostgreSQL, the first (master) user is a member of rds_superuser.7 On Neon, roles created in the Console, API or CLI are members of neon_superuser, which has CREATEROLE and CREATEDB.8 Create a plain role for the agent instead.
6. Every API token the agent can see is scoped to its job. One environment, one project, the fewest operations. A token made “just to manage domains” with account-wide rights is a production credential.
7. No credential reaches the agent’s context at all, if you can help it. Put tools between the agent and your systems that hold the secret and expose only operations. The agent asks; the tool decides and holds the key.
3. Let the database say no
8. Reads use a role that can only SELECT. A role that holds no write privilege can’t write, whatever the agent sends.3
9. Writes use a role with column grants for one job. Postgres can grant SELECT, INSERT, UPDATE and REFERENCES on single columns; TRUNCATE is a table-level privilege only.5 “Update status on orders” is a grant. “Work with orders” isn’t.
CREATE ROLE agent_support LOGIN PASSWORD '…' CONNECTION LIMIT 3;
ALTER ROLE agent_support SET statement_timeout = '2s';
GRANT USAGE ON SCHEMA public TO agent_support;
GRANT SELECT (id, customer_id, status, total_cents) ON public.orders TO agent_support;
GRANT UPDATE (status) ON public.orders TO agent_support;
10. Row-level security limits which rows it can touch, and the agent never connects as the table owner. Once row security is on for a table, access with no matching policy is denied, and table owners normally bypass row security unless FORCE ROW LEVEL SECURITY is set.4
11. There’s a statement timeout. statement_timeout aborts any statement that runs longer than its limit; the default, 0, disables it.2 Set it on the role.
12. You don’t rely on a tool’s “read-only mode” alone. default_transaction_read_only only sets the default for each new transaction.2 Datadog Security Labs showed that the archived reference Postgres MCP server’s read-only mode ran each query in a read-only transaction that a stacked COMMIT; ends, after which later statements run with the role’s full rights.6 The role’s privileges are the boundary; the mode is a second fence.
Our guide to Postgres write access for Claude Code and Cursor walks through items 8 to 12 with a tested SQL file.
4. Decide destructive verbs before they run
13. DROP, TRUNCATE, and UPDATE or DELETE without a WHERE never run from the agent. Not “the agent is told not to”; something outside the agent refuses them. In Deedglass that’s a rule, and a refused call looks like this:
✕ denied · postgres.execute
Blocked by policy "block-destructive-sql": DROP is never allowed for agents.
Without Deedglass, the role’s grants do most of this: a role without TRUNCATE and without ownership can’t truncate or drop.
14. Writes outside the agent’s normal job wait for a person. Pick the few that matter (refunds over a threshold, deletes, anything touching customer contact data) and require an approval, ideally on a phone, with the exact arguments shown.
15. A “freeze” is a permission change, not a sentence in the prompt. If nobody should change production this week, revoke the role’s write grants this week.
5. Keep production apart
16. Staging credentials can’t reach production resources, and production credentials don’t live anywhere the agent works in staging. Separate identifiers, separate secrets, ideally separate accounts or projects.
6. Make sure you can get back
17. Backups live somewhere the agent’s credentials can’t delete, and you’ve restored one in the last month. Know your point-in-time window in hours, not “we have backups”.
18. You know how you’d undo one bad action without restoring the whole database. A restore throws away everyone else’s writes since the backup. For a single agent action, you want the rows it changed, before and after, which is what Postgres’s change log carries for the tables you watch.
7. Keep evidence, not just logs
19. For every write, you record the request, the rule or person that allowed it, and what the database actually committed. The agent’s own trace records what it asked and what the tool replied, which isn’t the same thing. Why logs aren’t evidence goes into what to keep and how to make it hard to edit.
8. Be able to stop it
20. You can cut the agent off in under a minute, and you’ve tried it. For Postgres that’s two statements, run by an admin:
ALTER ROLE agent_support NOLOGIN;
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE usename = 'agent_support';
Do the same for API tokens: know where the revoke button is, and who can press it at 2 a.m.
Where to start
If you only do three things this week: give the agent its own non-superuser role (items 4 and 5), set a statement timeout on it (11), and make destructive verbs impossible for that role (13). Then run the free scan, npx deedglass scan, and see what’s left; the grade is explained here. To watch rules, a check and an undo work on a real Postgres without installing anything, try the playground.