Skip to content
Security

Eleven all-caps warnings did not stop it: what agent access to production should look like

A coding agent wiped a production database during a code freeze. The real lesson is about credentials, and it holds whether or not an agent is involved.

12 min readKato Chojiro

In July 2025 a coding agent deleted a production database during an explicit code freeze. The person running the experiment had written the freeze instruction eleven times, in capitals, in the same conversation. The agent proceeded anyway, and roughly twelve hundred companies' worth of records went with it. The story was re-litigated on Reddit and Hacker News for the rest of the year, and the consensus that formed in the comments was more useful than the headline: the problem was not that an AI was involved. The problem was that a development session held credentials that could drop a production table.

We bring this up because it is the single most common architecture we find in AI-built apps, and it predates agents entirely. One database, one credential, one environment. The agent did not create that condition. It just found it faster than a human would have.

Instructions are not a control

The most striking detail of that incident is the eleven warnings. It is tempting to read them as evidence of a badly behaved model, and there is something to that. But from a security standpoint the warnings were never going to work, because a prompt is a request and a permission is a fact. You do not stop a process from deleting a table by asking it not to. You stop it by not granting DELETE on that table to the role it is connecting as.

This is not a new principle and it is not specific to AI. It is why production deploys require approval, why a junior engineer's laptop does not hold root credentials, and why the postgres superuser is not the account your application connects with. What changed in 2025 is the number of actors holding a shell in a repository, and how fast each of them moves. A control that was merely good practice when a human might do something careless once a quarter becomes load-bearing when something can do it forty times an hour.

There is now a count for that. Georgia Tech's Systems Software and Security Lab runs a tracker called Vibe Security Radar, which traces published CVEs back through git history to work out whether an AI coding tool wrote the vulnerable code. Between July 2025 and March 2026 it confirmed 74, of which 14 were critical and 25 high severity. The distribution is the part to read: roughly 18 cases across the last seven months of 2025, then 56 in the first three months of 2026, and 35 of those in March alone.

The four separations that would have prevented it

None of these are exotic. All of them are missing from the majority of AI-built apps we audit.

1. Separate databases, not separate schemas

Development, staging and production should be different database instances with different credentials, not different schemas in one instance and certainly not the same schema with a dev_ prefix on some tables. The reason is blast radius: a mistake in a development database costs you a reseed, and there is no sequence of typos that turns a reseed into an incident.

The objection is always that seeding a realistic development database is work. It is, and it is worth it once. Write a seed script that generates a few thousand plausible rows, commit it, and make npm run db:reset a thing anyone can run without asking permission.

2. A role that cannot drop things

Your application does not need DROP. It does not need TRUNCATE. In most cases it does not need to create tables either - migrations do that, and migrations run as a different role in a different step of the pipeline. Splitting the two takes about twenty minutes:

sql
-- Migration role: owns the schema, runs in CI only.
create role app_migrator login password :'migrator_password';
grant all on schema public to app_migrator;

-- Runtime role: reads and writes rows, cannot reshape the database.
create role app_runtime login password :'runtime_password';
grant usage on schema public to app_runtime;
grant select, insert, update, delete on all tables in schema public to app_runtime;
grant usage, select on all sequences in schema public to app_runtime;

-- And for tables created later by migrations:
alter default privileges in schema public
  grant select, insert, update, delete on tables to app_runtime;
alter default privileges in schema public
  grant usage, select on sequences to app_runtime;

-- Explicitly not granted: create, drop, truncate, alter.
revoke create on schema public from app_runtime;

Put app_runtime in your application's environment. Put app_migrator in your CI secrets and nowhere else. An agent with a shell in your repository now cannot drop a table, because the credential it can reach does not carry the privilege - regardless of what it decides to try.

3. Production credentials that a laptop cannot read

If your production connection string is in a .env file on a developer machine, then every process on that machine has it: your editor, your terminal, any agent you run, and anything either of them installs. Move production secrets into a managed store - AWS Secrets Manager, Doppler, 1Password, Vault, or your hosting platform's own - and inject them at deploy time into the running service only.

The practical test is whether a new engineer can be productive on day one without ever holding a production credential. If the answer is no, that is the thing to fix, and it usually turns out to be a missing seed script rather than a hard problem.

4. Recovery you have actually performed

Point-in-time recovery is the control that makes the other three survivable when they fail. Every managed Postgres provider offers it, most people enable it, and almost nobody tests it. An untested backup and no backup are the same thing on the day you need one, because the failure modes - wrong retention window, missing extension, a restore that takes six hours you did not budget for - only appear during a restore.

What to do about agents specifically

Everything above applies whether or not you use coding agents. There are three additional things worth doing if you do, and we use all three ourselves when taking a Claude Code app to production.

  1. Run agents against a development database with its own credential, seeded from a script. If an agent can reach production, treat that as a misconfiguration to fix today rather than a policy to write down.
  2. Require a pull request for anything that reaches production. Branch protection and a required status check are a control; a code freeze announced in chat is a request. Keep the velocity - route it through the same gate everything else goes through.
  3. Keep migrations reviewable and reversible. A destructive migration should be a separate, deliberate pull request that a human reads, not a line buried inside a feature branch.

None of this slows an agent down meaningfully. What it does is shrink the set of outcomes a mistake can produce, which is the only durable way to work quickly with anything - human or otherwise - that occasionally does something surprising.

The honest version of the lesson

The comfortable reading of that incident is that AI tools cannot be trusted with production. It is comfortable because it puts the fault somewhere external. The uncomfortable reading, and the correct one, is that a single credential with unlimited privilege on live customer data was one careless action away from destroying it, and had been for months. The agent was the careless action that happened to arrive. Something else would have eventually.

Fixing this is not a project. Separate environments, a runtime role without DROP, secrets out of laptops, and one rehearsed restore is roughly a week of work on a typical app, and most of that week is writing the seed script. It is the cheapest security work available to you, and unlike most security work you can tell it is done.

Where this fits

Environment separation and credential scoping are the first two items in our production readiness checklist, ahead of anything about performance, because they are the two that turn a bad afternoon into a company-ending one. If you want a second pair of eyes on how yours are set up, the Production Check is free and takes three to five business days.

KC

Written by Kato Chojiro

Takes AI-built MVPs to production: security, data, and the infrastructure underneath.

Connect on LinkedIn
Book a Free Call

Want a second pair of eyes on your app?

The Production Check is free, and the written report is yours to keep.

Book a Free Call