The short answer
Use a narrow credential first: a database role that can’t drop or truncate, cloud credentials with an explicit deny on deletes. It’s the only control that holds however the agent reaches the system. Add a pre-command hook for the commands the agent types, because it gives the clearest prompt at the right moment. Use the OS sandbox to fence files and network, knowing it doesn’t understand what a database command does. And if the agent writes to a database you care about, put a gate in front of it that judges each call and can make a person approve.
Deedglass
A tool call meets your rules before it runs
Asks to run
DELETE FROM visits;
Deletes every row in visits
Deedglass checks your rules
- Reading dataAllowed
- Deleting a whole table (DROP, TRUNCATE)Never
- Changing or deleting rows without saying which (WHERE)This one
- Other changesWait for a person
The four controls
| Control | Where it sits | What it judges |
|---|---|---|
| Pre-command hook | inside the coding agent, before each shell command | the command’s text |
| OS sandbox | around the processes the agent starts | which files they write, which hosts they reach |
| Credential scope | in the database or cloud account | what that login is allowed to do |
| A gate in front | between the agent and the system it calls | each tool call, by rule, with approval |
They overlap less than you’d think. Each one is blind to something another sees.
What each one stops, and misses
A pre-command hook reads the command before the shell runs it. Deedglass’s shell guard, for example, splits a command into everything it runs and asks first when a part matches its catalog of commands that destroy data.1 It’s good at the commands people actually type: prisma migrate reset, terraform destroy, psql -c "DROP TABLE …". Its limit is that it reads text. A variable, an alias, a Makefile target, encoded text, code an interpreter runs, or a database reached over HTTP passes untouched.1 And if it can’t run, the command goes ahead unchecked.1
An OS sandbox fences what processes can touch. Claude Code’s sandbox limits writes to the working directory and a few others, and routes network through a proxy that checks each host against an allowlist that starts empty.2 Codex’s workspace-write mode keeps writes to the working directory and network off unless you enable it.34 That’s strong for files. For databases it’s a blunt instrument: the Claude Code docs note that tools ignoring proxy variables, such as most database drivers, can’t reach allowed hosts at all.2 So you either block the database entirely or allow it, and once allowed, a DROP TABLE over that connection is just traffic. The sandbox also covers shell commands only; the agent’s file tools, MCP servers and hooks run outside it.2 And an excludedCommands entry runs matching commands with no sandbox at all.2
A narrow credential is enforced by the system itself. In Postgres, only a table’s owner, its schema’s owner or a superuser can drop it.5 A role with SELECT only is refused an INSERT, even if it turns off read-only mode in its session,6 and TRUNCATE is a table-level privilege you can simply not grant.7 In AWS, an explicit deny overrides any allow.8 In Kubernetes, RBAC has no deny rules, so you limit by never granting the verb.9 None of this cares how the request was built: shell, script, SDK or MCP tool. Its limit is granularity: a role that may UPDATE a table may update every row in it.
A gate sits on the path of each tool call and decides by rule: allow, refuse, or hold for a person. Deedglass’s lockdown mode is an example: every agent write waits for a person even when the policy allows it, high-risk writes need two different people,10 and DROP, TRUNCATE, schema and permission changes, and UPDATE or DELETE without a WHERE never run through its Postgres connector, whatever the policy says.10 A gate can do what a role can’t: approve one write and refuse the next on the same table. Its limit is coverage. It only sees calls that go through it. A connection string in the agent’s environment that bypasses the gate bypasses everything the gate does.
Side by side
| Situation | Hook | Sandbox | Narrow role | A gate in front |
|---|---|---|---|---|
Agent types psql -c "DROP TABLE orders" |
asks | blocks only if the host isn’t allowed | refused | refused (if the call goes through it) |
Agent runs npm run db:fresh that resets the DB |
asks when it can read package.json1; misses with --no-files |
same as above | refused if it needs DROP | refused if routed |
| Agent’s Python script deletes rows with an ORM | misses | same as above | refused if no DELETE grant | held or refused if routed |
UPDATE orders SET status = 'x' with no WHERE |
asks | same as above | allowed if UPDATE is granted | refused |
One legitimate UPDATE and one wrong one, same table |
can’t tell them apart | can’t | can’t | person approves each |
| Agent deletes files outside the project | misses (not in catalog) | blocks writes outside allowed paths | n/a | n/a |
| Agent sends data to an unknown host | misses | blocks unless allowed | n/a | n/a |
Read the columns, not the rows. The hook column is full of “asks” for typed commands and “misses” for everything else. The sandbox column is strong on files and network, weak on meaning. The role column is the most reliable and the least precise. The gate column is precise but only for traffic routed through it.
A layering that works
For a developer laptop:
- A database role for the agent without
DROP,TRUNCATEorDELETE, and no production URL in the agent’s environment. - A pre-command hook, so typed destructive commands stop for a decision.
- The agent’s sandbox on, for files and network.
For an agent writing to production data:
- A role scoped to the tables it needs.
- A gate in front of the database, with approvals for writes.
- Backups you have restored.
If you can only do one thing, do the role. It’s the control the other three assume is already there.
Questions people ask
Does Claude Code’s sandbox stop DROP TABLE?
Not as such. The sandbox fences files and network hosts; it doesn’t read SQL. Most database drivers can’t reach hosts through its proxy, so a database is either blocked or allowed, and an allowed connection carries any statement.2
Is a pre-command hook enough on its own?
No. It reads the command’s text, so anything decided at run time, or run by an interpreter, passes.1 It’s a second check, not a boundary.
What’s the most reliable single control?
A credential that can’t do the damage: a database role without DROP and TRUNCATE, or a cloud role with an explicit deny on deletes.58
More detail on each layer: the protect walkthrough for hooks, Claude Code sandbox settings, safe Postgres access for agents for roles, and lockdown mode for a gate. What all of them shrink is the blast radius.
To see which of these your setup already has, run npx deedglass scan in the repository (what the grade means), or watch a gate catch a write in the playground.
Written with AI assistance and checked against our claims register.