Situation · Database deletes
Stop an AI agent from deleting a production database — refused before the statement runs
An agent with a database connection can delete a table the moment it decides to. Read-only credentials prevent that and also prevent the work you wanted. The alternative is to put destructive calls behind a decision made at the call, and keep a record of what was refused.
Answers the question: “How do I stop an AI agent from deleting my production database?”
- Also: “How do I require approval before an AI agent runs DROP TABLE?”
- Also: “How do I stop an AI agent running DELETE without a WHERE clause?”
- Also: “Can I prove an AI agent's destructive query was blocked?”
A connection that can write can drop
Database permissions decide which statements a credential may run. They do not decide whether this statement, against this table, should run now. An agent that is allowed to update rows is often allowed to delete them too.
A DELETE with no WHERE: one statement removes every row the credential can reach.
A schema change run as cleanup: DROP TABLE or a destructive migration issued in a working session.
Approval of a tool, not a statement: a person allows “database access” and the agent issues a statement nobody read. The offline boundary holds the call, but the approver still has to read the statement.
No record of what was checked: afterward there is a query log, not evidence a third party can verify.
Check it, scan for it, then pick the id
The first two steps need only a laptop. The third explains why the database path is offline today.
1 · Check the refusals yourself, offline — no account, no network
git clone https://github.com/Tarshann/solo-builder-core cd solo-builder-core && pip install -e . solo strix-wire verify docs/evidence/database-refused-v1/.strix/evidence/receipts.jsonl --state-dir docs/evidence/database-refused-v1/.strix
Signed local receipts for real SQL against a throwaway in-memory SQLite database seeded with synthetic rows: a DELETE with no WHERE held for approval, a DROP TABLE held for approval, the same DROP TABLE denied by a configured rule even with approval, and the same DELETErunning once approval is granted. After each run the database is read back, so “the rows were not touched” is observed, not inferred. The key is local to the machine that minted them: this shows the mechanism, not Strix-operated custody.
2 · Find the destructive calls in your own code
solo govern scan --format text
The scanner reports each call it recognizes and, where its pattern can tell, marks whether it is scoped (has a WHERE), to rank which call to wrap first. It finds calls that match its patterns; a statement built at runtime, or a call made outside the code it reads, is not covered.
3 · Which id to use depends on where the call is checked
| Id | Where it is used | Hosted API | Note |
|---|---|---|---|
| database.delete | Scanner and offline policy | Refused as unknown | What the scanner emits for a DELETE and the offline policy classifies. Not a hosted id. |
| database.migrate | Scanner and offline policy | Refused as unknown | What the scanner emits for DROP and schema changes. Not a hosted id. |
An id the hosted API refuses as unknown fails closed, but it is refused at the contract check: no decision exists and so no signed record does. Today the database path is the offline one.
What the offline boundary does, and what it does not
These describe the local boundary and its committed evidence. They are not hosted guarantees.
| Capability | How Strix delivers it |
|---|---|
| Refused before the statement runs | The wrapped operation is only called after the boundary admits it. In the evidence package the call counter stays at zero on every refusal, and the table and its rows are read back unchanged. |
| Held and denied are different records | A call held pending approval is recorded as pending, not as a denial. A configured deny wins over a granted approval. The default rules hold destructive calls for approval and deny nothing outright. |
| Signed and tamper-evident | Each refusal is signed and hash-chained. Editing a receipt to claim the statement ran fails verification. The key is local to the machine that minted the receipts. |
| Not a reading of the SQL | The offline policy decides on the action's risk tier. A DELETE scoped to one row and a DELETE of every row get the same decision. A test in solo-builder-core pins this; if the policy starts reading statements, that test fails. |
| Hosted kernel | database.delete is not a registered capability on the hosted API. The hosted kernel does register delete capabilities for the console's own records, among them the ids ending in _delete: contact_delete (HIGH, 2 approvers; production CRITICAL, 3); onboarding_workspace_delete (CRITICAL, 3 approvers; production CRITICAL, 3); organization_delete (HIGH, 2 approvers; production CRITICAL, 3); sales_pipeline_deal_delete (CRITICAL, 3 approvers; production CRITICAL, 3); sales_pipeline_shell_delete (CRITICAL, 3 approvers; production CRITICAL, 3); vault_item_delete (HIGH, 2 approvers; production CRITICAL, 3); vertical_delete (HIGH, 2 approvers; production CRITICAL, 3). Those govern Strix's console, not your database, and this page does not report whether any of them has executed in production. |
| Where it stands | A destructive SQL statement wrapped by the offline boundary was refused before it ran, the refusal was signed by a local key, and the rows were observed untouched. The offline policy decides on the action's tier, not on the SQL. The hosted kernel has no generic database capability, so this is not a hosted or production record, and it does not protect a database the boundary does not wrap. |
Proof, not promises
Every claim Strix makes is backed by an artifact you can independently verify.
Offline refusal package
Signed local receipts for a held DELETE, a held DROP TABLE, a denied DROP TABLE and an approved DELETE, with the database read back after each. Verifies with the private key deleted.
https://github.com/Tarshann/solo-builder-core/tree/main/docs/evidence/database-refused-v1What the policy does not read
A test that a scoped and an unscoped DELETE get the same decision. If the policy starts reading statements, it fails by name.
https://github.com/Tarshann/solo-builder-core/blob/main/tests/test_database_refused_evidence.pyBinding to the exact request
How the hosted kernel binds approval to one canonicalized payload. Shown on refunds; there is no hosted database capability for it to bind to yet.
/situations/bind-approval-to-exact-requestFrequently asked questions
How do I stop an AI agent from deleting my production database?+
Two layers. Give the agent a credential that cannot reach production tables it has no reason to change. Then wrap the destructive calls it can make so each one is decided at the call: held for a person, denied by rule, or allowed, with a signed record either way. The offline boundary in solo-builder-core does the second today, under a local key, and decides on the action's risk tier rather than reading the SQL. It covers the calls you wrap, not every path to the database.
Does Strix read my SQL?+
Not to decide. The offline policy decides on the action's risk tier, so it cannot tell a one-row DELETE from a DELETE of the whole table. The scanner can often tell them apart, but only to rank which call to wrap first.
Can I use this against the hosted API?+
Not for your database today. database.delete is refused as an unknown action on the hosted API, so no decision and no signed record is produced. Adding a hosted database capability is a policy change with a version bump and an approval, and it has not been made.
What does the signed record prove?+
That the boundary refused the call before it ran, and that the record has not been edited since. It is signed by a key on the machine that produced it, so it does not prove Strix-operated custody, a trusted timestamp, or that the machine owner could not replace the runtime.
Does this protect a real database?+
The evidence uses a throwaway in-memory database to show the mechanism. Protecting yours means wrapping your own calls with the boundary. A call that does not go through it is not covered; the scanner and coverage report exist to find those.
Does this replace backups, roles and point-in-time recovery?+
No. Those limit and repair damage. This decides whether one specific destructive call should run now, and keeps a record of the answer.
Related paths
Let an AI agent issue Stripe refunds
The same method applied to money, where a hosted capability is registered and Academy has production refund evidence.
Open →A write gate for MCP tools
The same boundary where the agent reaches a database tool over MCP.
Open →Human approval for agent actions
How approval and quorum work before any consequential call.
Open →