Give your coding agent a database it can break
Sandboxes isolate the machine, not the connection string. The safe setup for an AI coding agent on Postgres is a database that is disposable, populated, and synthetic, so a leaked credential costs you nothing.
The AI coding agent horror stories share the same shape. A scoped identity, a microVM, a careful prompt, and then one infrastructure call takes production down for hours. The write-ups are almost always about the machine: filesystem access, network egress, secrets in the environment.
Nobody writes about the connection string, which is the part that actually matters for a database application. Your agent has to run the app to write it. The app needs Postgres. And the honest menu of options is three bad ones:
- Production credentials in the environment. This is the failure isolation was supposed to prevent, and an agent reads environment variables the same way it reads any other string.
- An empty schema. The agent writes code that never touches a populated table. Its own tests pass because a query against zero rows is always correct.
- A production dump in the sandbox. Now your isolation boundary contains customer data, which is the exact thing the boundary was built to exclude.
The fix is not better isolation. Isolation tries to stop the agent from reaching the wrong database. The better move is to make the database it does reach worth nothing.
An agent's blast radius is the set of credentials it can read
This is the sentence worth internalizing before any tool choice. You cannot audit what an agent will not do. You can only decide which of its actions would not matter.
So the safe agent database needs three properties at once, and the common setups each miss at least one:
| Setup | Disposable | Populated | Synthetic | Fails how |
|---|---|---|---|---|
| Production with restricted grants | No | Yes | No | One bad migration or DELETE is an incident, and grants are wider than anyone intends |
| Staging copy | Partly | Yes | No | Real customer rows in an environment designed to keep them out |
| Empty local database | Yes | No | Empty | The agent verifies nothing and reports success |
| Prod dump restored locally | Yes | Yes | No | PII leaves production and lands wherever a laptop does |
| Generated from the schema, locally | Yes | Yes | Yes | Nothing, which is the point |
Disposable means you can destroy it on purpose and rebuild it in seconds. Populated means the agent meets foreign keys, constraints, and a distribution that behaves like the real thing. Synthetic means there are no customer rows to leak, so a disclosed credential reveals an inventory of generated rows nobody recognizes.
The last row is the one that changed how I think about sandboxing: the safest database for an agent is one whose entire contents can be recreated with one command.
The setup
Four steps: the schema as text, the DSN that draws the boundary, the rebuild command, and the reset that makes it boring.
One: get the schema out of the database, once
This step is done by a human or by CI, not by the agent. It is the only place production is touched, and it is read-only:
pg_dump --schema-only -d production_db > schema.sqlCommit schema.sql. A text file with table definitions carries no credentials, no customer rows, and no authority to do anything. Your agent can read it as freely as it reads its own source code, and that is the whole trick: the thing the agent most needs to know, which is your schema, is the one thing you can hand it safely.
Two: point the MCP server at the scratch database only
If the agent works through MCP tools, the DSN in that config is the boundary. Put the disposable database there, and never put anything else there:
{
"mcpServers": {
"weavori": {
"command": "npx",
"args": ["-y", "@weavori/cli", "mcp"],
"env": {
"WEAVORI_DATABASE_URL": "postgres://weavori:weavori@localhost:5432/agent_sandbox",
"WEAVORI_API_KEY": "your-api-key"
}
}
}
}introspect, estimate, and doctor are read-only. generate and sync write, and they write only to the target they are given. In that config, the only target reachable is the sandbox.
Full copy-paste configs for Claude Desktop, Cursor, VS Code Continue, and Qoder are in the MCP setup guide.
Three: rebuild the data from text, with no connection string to anything real
This is where a CLI-only capability does the heavy lifting, because the MCP server needs a live database to introspect. When the sandbox is empty, wiped, or half-migrated, the agent runs one command and reads nothing but the committed schema file:
--create-tables is on by default, so the tables are created in the target and then filled. Foreign keys resolve in dependency order: parents before children, every child pointing at a row that exists. --yes skips the interactive preview, which matters here: an agent blocked on a prompt it cannot answer is an agent that starts improvising. The result is a populated, referentially intact database, and no secret in the agent's environment had anything to do with it.
Four: make the reset boring
Once destroying the database is cheap, the agent can be aggressive on your behalf:
dropdb --if-exists agent_sandbox && createdb agent_sandbox
npx --yes @weavori/cli generate --input schema.sql --input-format ddl \
--target "postgres://weavori:weavori@localhost:5432/agent_sandbox" --rows 5000 --yesTwo seconds of downtime, and the failure the agent caused is now a test case rather than an incident. That is the practical difference between guarding an agent and being able to let it run unattended.
What the agent can do with it
Ask for a database and the assistant can take the whole loop itself: introspect to read the schema, estimate to see row counts and time before anything is written, generate to fill it. That is the sequence, and it is worth requiring the middle step as a habit, because estimate is the difference between a two-second reset and an unexpected afternoon.
The part that matters for correctness, not just convenience: an agent testing against real-shaped data writes different code. A database with foreign keys forces it to respect them. A database where a status column is 82 percent one value exposes queries that an even distribution would have hidden. A database with 5,000 rows instead of three shows the missing index on a foreign key column, which Postgres does not create for you.
Give the agent an empty schema and you get code that is syntactically correct and semantically unverified. Give it a realistic one and the agent's own checks start meaning something.
Where this stops being the answer
Honest boundaries
Synthetic data cannot reproduce the specific bad row that is only in production. If a bug depends on one customer's malformed address or a duplicate created by a legacy import, no amount of realistic generation finds it; a query against the real table does. Generation also needs the schema to be readable, and DDL paste mode parses CREATE TABLE statements only, so indexes, sequences, and ALTER statements are not part of the picture. The MCP server connects to a live database, so the paste-mode rebuild above is a CLI command rather than a tool call. Authentication is required: an API key or a one-time weavori login, and generation runs locally with no network round trip once you are authenticated. And this is PostgreSQL only.
When you need production's literal values inside the sandbox, that is a different job with a different tool. weavori sync copies real rows between databases in foreign-key order, which is Pro, and it copies them unmasked: it moves data, it does not de-identify it. If the requirement is real values with the PII removed, that is anonymization, and it is a separate category from generation. The two get sold together and confused constantly, and picking the wrong one is how customer data ends up in a sandbox.
The checklist
- No production DSN anywhere the agent can read. Not in
.env, not in MCP config, not in a comment. - One read-only schema dump, committed as text. A human or CI does this, on a schedule you control.
- A scratch database the agent may destroy. Local, containerized, disposable.
- Rebuild from the text file with one command. If the reset is annoying, the agent gets the production database instead.
estimatebeforegenerate. Make the agent check size before it writes.- Real data only when the bug is genuinely data-specific, copied with
syncor restored from a masked dump, and never silently.
Summary
- Sandboxing solves the machine. It does not solve the connection string, and the connection string is the part with your customers in it.
- An agent's blast radius is exactly the credentials it can read, so the design goal is a database you can afford to lose.
- Disposable, populated, and synthetic together: reset in seconds, constraints the agent has to respect, and nothing to disclose if the environment leaks.
- The schema is the one thing worth giving an agent, and a text file carries it with no authority attached.
- Realistic data makes the agent's own verification useful. An empty database produces confident, untested code.
Start with the reset loop on a project you are already building: