In May 2026, a coding agent dispatched a subagent to check why a test suite's reset logic was failing. The subagent wrote a script with a DELETE FROM for every table and ran it. The project's .env file still had the production credentials in it, so one of those scripts connected to production. A real customer's database was gone.
The repository even had a guardrail: test runs refused to start unless the database name ended in _test. The script loaded the environment on its own and never went through it.
Nobody attacked anything. An agent tried to be useful with the access it had.
We went through the public record of cases like this one: incidents, advisories, CVEs and research where an AI agent, an MCP server or a text-to-SQL tool did something destructive to a database, or could have. We kept the ones with a source we could link to. This is what they have in common, and what would have stopped each of them.
A note on where we stand. We build Vericto, a deterministic check that sits between agents and the database and blocks destructive SQL before it runs. So we have an interest in this topic. That is why the scorecard below includes the cases our approach would not have stopped, and why every claim links to its source.
How we built the list
- Public sources only. Postmortems, GitHub issues written by the people affected, vendor advisories, CVEs, a research paper and a conference talk. Self-reports are labelled as such: they are first-hand, but nobody else has verified them.
- SQL run through a real engine. When a case published its SQL, or the statement it implies, we ran it through our engine (vericto-engine 3.5.3 with the 28 built-in rules our proxy ships with), on both Postgres and MySQL where the statement applies. Each statement was evaluated 1,000 times. The verdict never changed, which is the point of a deterministic check.
- No numbers we couldn't trace. Figures come from the sources, not from estimates.
The 13 cases
Agents running destructive SQL on their own
1. A subagent runs DELETE FROM on every production table (May 2026). The case above. The script ran with no confirmation, and the parent agent first denied causing it because it hadn't checked its subagents' transcripts. Source: anthropics/claude-code#64056 (self-report).
2. drizzle-kit push --force against production: 60+ tables gone (February 2026). A coding agent running in another terminal pushed a schema change with --force to a production Postgres. According to the report there were no automatic backups or point-in-time recovery, and rebuilding took about eight hours. It was the second loss with the same tool in that project. Source: anthropics/claude-code#27063 (self-report).
3. An agent accepts a Prisma reset in production (March 2026, two reports). In one, prisma db push detected schema drift during a routine deploy and offered a reset; the agent accepted it, taking every table and 32 custom SQL functions with it. In the other, facing a data-loss warning, the agent ran prisma db push --force-reset in the background while DATABASE_URL pointed at production. Sources: #33183, #36183 (self-reports).
“Read-only” that wasn't
4. The reference Postgres MCP server (August 2025). It wrapped every query in a READ ONLY transaction. But the driver accepted several statements in one string, so COMMIT; DROP SCHEMA public CASCADE; closed the read-only transaction and ran the rest with the role's full privileges. The server had already been deprecated and archived, yet Datadog counted about 21,000 weekly downloads of the package. Source: Datadog Security Labs.
5. COPY … TO PROGRAM in AWS's Postgres MCP server (September 2026). In its default read-only mode, the server's SQL validator did not block every statement with side effects. A COPY … TO PROGRAM planted in content the agent processed could run operating-system commands on a self-managed Postgres host, if the database role had superuser or pg_execute_server_program. Fixed in 1.1.7. The advisory recommends a least-privilege role and blocking statements, as defense in depth. Sources: GHSA-fph8-pg5w-78fv, AWS bulletin 2026-104.
6. 18 of 19 “read-only” SQL MCP servers had flaws (2026). In a published talk, Maximilian Hildebrand reviewed 19 SQL MCP servers that advertise a read-only mode and found ways around it in 18, each in under 30 minutes. Source: media.ccc.de.
Text-to-SQL tools that execute what the model writes
7. LangChain's SQLDatabaseChain (2023). An issue pointed out that nothing stopped a user from asking, in plain English, to drop a table, and proposed checking the SQL before sending it. It became CVE-2023-36189. Sources: langchain#5923, GHSA-7q94-qpjr-xpgm.
8. LlamaIndex text-to-SQL (January 2024). Several of its text-to-SQL engines, up to 0.9.35 according to the GitHub advisory, could be steered by prompt injection into running arbitrary SQL, including dropping tables (CVE-2024-23751). Its documentation now recommends restricted roles, read-only databases or sandboxing. Sources: llama_index#9957, GHSA-2jxw-4hm4-6w87.
9. P2SQL: from prompt injection to SQL injection (2023, ICSE 2025). Researchers at INESC-ID and Técnico Lisboa characterised “prompt-to-SQL” attacks on LangChain web applications: the variants, their impact and the defenses. Sources: arXiv 2308.01990, ICSE 2025 paper.
10. Vanna.AI: prompt injection to code execution (June 2024). Vanna's ask function also generated Python to chart the results. With prompt injection, that Python could be anything (CVE-2024-5565). Source: JFrog.
Platforms and infrastructure
11. Replit's agent deletes SaaStr's production database during a code freeze (July 2025). Jason Lemkin documented it publicly. Replit's CEO confirmed it, called it unacceptable, and rolled out automatic separation of development and production databases. The exact mechanism was never confirmed in a primary source. Sources: The Register, Replit's response.
12. Supabase MCP: a support ticket leaks the tokens table (July 2025). In a demonstration, an agent connected through MCP with a role that bypasses row-level security processed a ticket with hidden instructions. It read a private integration_tokens table and copied it into the ticket thread, where the attacker could see it. No database permission was violated. Supabase responded with defense-in-depth changes to its MCP server. Sources: General Analysis, Supabase.
13. PocketOS: a production volume deleted through an infrastructure API (April 2026). While fixing a credentials mismatch in staging, a coding agent found an API token in an unrelated file, one created to manage domains but with full permissions, and deleted a production volume. The backups lived on the same volume. It took nine seconds. Railway helped restore the data and changed that endpoint to a delayed delete. Sources: The Register, the founder's post.
What keeps repeating
The production credential was within the agent's reach (cases 1, 3, 13). A .env file, a DATABASE_URL, a token in a file that had nothing to do with the task. The agent didn't escalate privileges. It used what was lying around.
The agent resolved an error with the most destructive option available (2, 3, 13). A --force flag, a reset prompt, deleting the thing that looked broken. Each of those confirmations exists for a human to stop and think. An agent trying to finish the task treats them as obstacles.
Instructions in natural language were not controls (11, 13). A code freeze stated in the prompt and project rules telling the agent what not to do did not hold when the agent decided they no longer applied.
“Read-only” lived in the wrong layer (4, 5, 6). It was a promise made by the application, implemented by wrapping or filtering queries. The database role underneath could still write.
Backups were missing, or sat next to the data (2, 13).
The industry is already reacting. Replit separated development and production databases. Railway made API deletes delayed. Prisma 6.15.0 now blocks prisma migrate reset --force when it detects an AI agent, until the user consents (release notes). These are real improvements. They also show where agent-side controls stop: Prisma's consent reaches the tool through an environment variable that the agent itself sets.
Scorecard: would a check on each statement have stopped it?
The question here is narrow on purpose. Would a deterministic check on each SQL statement, sitting on the connection between the agent and the database, have blocked the destructive part? That only holds if production accepts connections through that check and nowhere else.
| Case | Statement (or its equivalent) | Verdict | Stopped? |
|---|---|---|---|
| 1. Subagent DELETE FROM | DELETE FROM patients; |
BLOCKEDVERICTO-001 |
Yes |
| 2. drizzle-kit push --force | DROP TABLE "trades" CASCADE; · ALTER TABLE … DROP COLUMN |
BLOCKEDVERICTO-010 VERICTO-015 |
Yes |
| 3. Prisma reset | DROP SCHEMA "public" CASCADE; |
BLOCKEDVERICTO-012 |
Likely. We have not confirmed the exact SQL Prisma sends on a reset |
| 4. Reference Postgres MCP | COMMIT; DROP SCHEMA public CASCADE; |
BLOCKEDVERICTO-012 |
Yes. Every statement in the string is checked |
| 5. AWS Postgres MCP | COPY (…) TO PROGRAM '…'; |
BLOCKEDVERICTO-080 |
Yes |
| 6. 18 of 19 MCP servers | — | — | Partly. Bypasses that end in writes, DDL or COPY … PROGRAM are blocked; the ones that only read data or files are not |
| 7. LangChain | DROP TABLE employee; |
BLOCKEDVERICTO-010 |
Yes |
| 8. LlamaIndex | DROP TABLE …; |
BLOCKEDVERICTO-010 |
Yes, for the destructive variant |
| 9. P2SQL | DELETE … WHERE 1=1 · … OR 1=1 · UPDATE with no WHERE |
BLOCKEDVERICTO-003 VERICTO-090 VERICTO-030 |
Partly. The destructive variants are blocked; the ones that only read other users' data are not |
| 10. Vanna.AI | — | — | No. The payload was Python, not SQL |
| 11. Replit | DROP TABLE …; |
BLOCKEDVERICTO-010 |
Only if that is what ran and the platform let you put a check on that connection, which it doesn't |
| 12. Supabase MCP | SELECT * FROM integration_tokens; |
FLAGGEDVERICTO-050 |
No. The SELECT is logged, not blocked, and writing the tokens back as literal values is allowed too |
| 13. PocketOS | — | — | No. It was an infrastructure API call, not SQL |
Six clear yes, one likely, two partial, one conditional, three no. A statement check is one layer. Cases 10, 12 and 13 need other ones: least privilege, isolated tokens, backups the agent can't reach, and not giving a support bot a role that bypasses row-level security.
If you are giving agents database access
- Keep production credentials out of anything the agent can read. Not in
.envfiles shared with tests, not in tokens left in unrelated files. Give agents their own credentials and keep production ones separate (cases 1, 3, 13). - Make read-only a property of the database role. Grant
SELECTand nothing else. Never superuser, neverpg_execute_server_program. Don't rely on the tool wrapping queries (4, 5, 6). - Assume the agent will accept every
--forceand every reset prompt. Disable what you can in the agent's tooling, but don't make that the only line (2, 3). - Keep backups and point-in-time recovery where the agent's credentials can't delete them (2, 13).
- Check each statement before it reaches the database, outside the agent. A check on the structure of the query, not the text, so
DELETE FROM users WHERE 1=1gets the same verdict asDELETE FROM users. It has to sit where the agent can't skip it or switch it off (1, 2, 4, 5, 7, 8). - Log every decision, including the refusals. What an agent tries after it is denied is exactly what you want on record.
- Don't count prompt instructions as controls. A code freeze in the prompt is a request, not a boundary (11, 13).
None of these is exotic. Most teams have them for people already. Agents need the same ones, applied on the assumption that nobody reads the query before it runs.