What 13 documented cases teach about giving AI agents database access

Coding agents, MCP servers and text-to-SQL tools, sorted by what actually went wrong and by which controls would have stopped them.

9 min read
AI AGENTS RESEARCH

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; BLOCKED
VERICTO-001
Yes
2. drizzle-kit push --force DROP TABLE "trades" CASCADE; · ALTER TABLE … DROP COLUMN BLOCKED
VERICTO-010 VERICTO-015
Yes
3. Prisma reset DROP SCHEMA "public" CASCADE; BLOCKED
VERICTO-012
Likely. We have not confirmed the exact SQL Prisma sends on a reset
4. Reference Postgres MCP COMMIT; DROP SCHEMA public CASCADE; BLOCKED
VERICTO-012
Yes. Every statement in the string is checked
5. AWS Postgres MCP COPY (…) TO PROGRAM '…'; BLOCKED
VERICTO-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; BLOCKED
VERICTO-010
Yes
8. LlamaIndex DROP TABLE …; BLOCKED
VERICTO-010
Yes, for the destructive variant
9. P2SQL DELETE … WHERE 1=1 · … OR 1=1 · UPDATE with no WHERE BLOCKED
VERICTO-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 …; BLOCKED
VERICTO-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; FLAGGED
VERICTO-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

  1. Keep production credentials out of anything the agent can read. Not in .env files shared with tests, not in tokens left in unrelated files. Give agents their own credentials and keep production ones separate (cases 1, 3, 13).
  2. Make read-only a property of the database role. Grant SELECT and nothing else. Never superuser, never pg_execute_server_program. Don't rely on the tool wrapping queries (4, 5, 6).
  3. Assume the agent will accept every --force and every reset prompt. Disable what you can in the agent's tooling, but don't make that the only line (2, 3).
  4. Keep backups and point-in-time recovery where the agent's credentials can't delete them (2, 13).
  5. 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=1 gets the same verdict as DELETE FROM users. It has to sit where the agent can't skip it or switch it off (1, 2, 4, 5, 7, 8).
  6. Log every decision, including the refusals. What an agent tries after it is denied is exactly what you want on record.
  7. 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.