# Your Agent Passed Every Safety Check. Then It Deleted the Database File

> SQL firewalls can't stop rm: SQLite is a file, and files die at the OS layer. The 5-command Linux cage (separate user + staging copy) that makes prod deletion kernel-impossible. Inside.

Source: https://boringpush.com/blog/agent-filesystem-cage-sqlite

## Your cage has no floor

Picture the standard agent-safety stack in 2026: read-only database handles, a regex firewall blocking DROP and unbounded DELETE, a migration gate demanding human approval. Genuinely good work. Then the agent runs `rm db/app.db` — or overwrites it with garbage, or "tidies" the directory — and every layer watches helplessly. Nothing was disobeyed. The guards were never consulted. Deleting a file is an operating-system operation, and SQLite is only a file.

This is the exact shape of the PocketOS disaster: obedience treated as a control. An agent that ignores instructions ignores all of them equally — SQL rules included. Anything enforced by telling is bypassable by doing something you weren't told about.

BoringPush is the $199-once Next.js kit where this lesson is load-bearing. The SQL cage (read-only handles, firewall, human-gated migrations, off-site backups) already ships in the template. The filesystem cage below closes the hole SQL guards structurally cannot: the file itself.

## Two cages, two enforcers

| Threat | SQL-layer guards | Filesystem cage |
|---|---|---|
| `DROP TABLE users` | Blocked (firewall + scanner) | N/A |
| `DELETE FROM orders` (no WHERE) | Blocked | N/A |
| `rm db/app.db` | Never consulted | `Permission denied` (kernel) |
| Overwrite db with garbage | Never consulted | `Permission denied` (kernel) |
| Delete the staging copy | N/A | Allowed — rebuilt hourly, prod untouched |
| Argue with the enforcer | Possible (prompt tricks) | Impossible (kernel checks UID on every syscall) |

The last row is the whole essay. Application guards constrain what the app allows. OS permissions constrain what the machine allows. Only one of them survives a disobedient agent.

## Five commands, one standing user

The design fits in a paragraph: the agent runs as its own Linux user (`agent`, no sudo, key only). Prod's db directory is owned by the app user at mode `750` — the agent cannot list it, let alone delete from it. The agent works on a staging copy in its own directory, refreshed hourly by an atomic `sqlite3 .backup` cron job the agent never touches. Agent sessions are pointed at the copy, and the kit's read-only database handle for agents respects the same setting, so both layers agree on which file the agent sees.

Worst case, spelled out: the agent deletes the staging copy. The next hourly refresh rebuilds it. Production never flinches. That is the entire SQLite-destruction threat model, closed, on a $5 VPS with stock Linux — no containers, no special tooling, the same UID trick Android uses to sandbox Termux.

If you can think it, BoringPush can make it — and what buyers think after reading disaster postmortems is "make it so the agent physically cannot." The kit ships both cages: a read-only database handle for agents, and a filesystem setup guide in the docs that works on any VPS.

## Verify it (don't trust it)

Cages get tested, not believed:

```bash
sudo -u agent ls /var/www/myapp/db          # → Permission denied ✅
sudo -u agent rm /var/www/myapp/db/app.db   # → Permission denied ✅
sudo -u agent touch /srv/agent-staging/db/probe  # → works ✅
```

## FAQ

### Doesn't the read-only SQLite flag already do this?

No — `readonly: true` governs SQL writes through that handle. It says nothing about `rm`, overwrite, or directory operations. Different layer, different enforcer.

### Why not Docker instead of OS users?

Containers are a fine cage too, but they add image builds, volume mapping, and a daemon to babysit — overhead a solo founder feels weekly. UID + mode bits are free, auditable in five commands, and survive reboots without orchestration.

### What about Supabase and MongoDB?

They're servers, not files — no `rm` equivalent exists. Their twin of this cage is credential separation: service keys absent from the agent's environment, least-privilege roles (`app_agent`/`appAgent`) only. Same principle, different layer.

### One user per agent or per session?

Per agent (a standing `agent` user) is enough — sessions are time, users are identity. Go ephemeral per-session only for untrusted third-party agents.

### I deleted my staging copy. Now what?

Wait up to an hour, or re-run the `.backup` command yourself. That's the point: the copy is disposable by design. If losing it scared you, imagine it had been prod.
