Guides / Backups an agent cannot delete

Database backups an AI coding agent cannot delete

A coding agent in your terminal runs commands as you. It can read your shell's environment, your config files and the keys beside them, and it can run psql, terraform and aws with whatever those hold. When it drops the wrong table, the backup is what brings the table back. So the backup has to be out of reach of everything the agent can read, and it has to exist from just before the command ran.

This guide sets that up in four steps, then lists what it does not cover.

1. Find what the agent can reach

List the credentials on the machine the agent runs on, and ask of each one whether it can delete or overwrite a backup:

CredentialWhere it usually isCan it delete a backup?
Cloud keys~/.aws/credentials, AWS_* in the shellIf its policy grants s3:DeleteObject or s3:DeleteObjectVersion on the backup bucket, yes, unless the bucket locks the objects (step 2).
The backup tool's own keyIts config fileMost tools use a key that can delete, so they can expire old backups.
The database URL.env, DATABASE_URLIt can destroy the database. It cannot touch a copy stored elsewhere.
An API token for the backup serviceThe agent's MCP configDepends on what the service lets a token do (step 3).

2. Lock the backups in the bucket

S3 Object Lock in compliance mode stops anyone from deleting or overwriting a locked object before its date, the bucket's owner and the account's root user included. Governance mode does not: a key with s3:BypassGovernanceRetention deletes through it. A plain DELETE on a locked bucket only writes a delete marker, which hides the backup from a listing and leaves the locked version in place. Immutable backups with S3 Object Lock shows each case against a real bucket.

Then give the machine a key that cannot delete at all, so it cannot even write the marker. The policy SafeGrd gives a backup host grants writing, locking, reading and listing, and no s3:DeleteObject (the policy). On SafeGrd's hosted storage the host holds no storage key: it asks SafeGrd's server for a URL signed for one part of one upload, and a URL cannot delete. Either way, the lock's date can only move later: safegrd keep extends a lock and nothing shortens one.

Cloudflare R2 and DigitalOcean Spaces have no S3 Object Lock, so backups there can be deleted by a key that can delete. Use another bucket for anything an agent can reach (details).

3. Give the agent a token that cannot delete

An agent that talks to SafeGrd uses a personal access token through the MCP server. Its eleven tools list organizations, projects, surfaces, snapshots and drills, read storage use and drill figures, and ask for a backup or a drill. None deletes a snapshot, changes where backups are stored, rotates a key, or changes members or billing, and every tool is marked non-destructive in its MCP annotations. The server refuses those changes to a token on every API route as well, so the agent cannot make them through REST either: they take a signed-in console session, which an agent does not have.

# Claude Code, with a token from Tokens in the console
claude mcp add --transport http safegrd https://safegrd.dev/mcp --header "Authorization: Bearer $SAFEGRD_TOKEN"

4. Take a snapshot before each destructive command

A nightly backup can be most of a day old when the agent runs DROP TABLE. The agent guard closes that gap: a hook in Claude Code, Cursor or Codex sends every shell command through safegrd guard, which lets a command through at once unless it matches the destructive list (DROP, TRUNCATE, DELETE without WHERE, dropdb, terraform destroy, prisma migrate reset, supabase db reset and the Rails and Django resets). For a match, it backs up the database, waits until the snapshot is uploaded and locked, and only then runs the command. If the backup fails or the snapshot is not locked, the command does not run.

# what the hook does for a matching command
safegrd guard --surface app-primary -- psql "$DATABASE_URL" -c 'DROP TABLE sessions'
# and how you undo it, from the snapshot guard printed
safegrd restore --snapshot snap-20261005-120428-524927 --table public.sessions --target env:DATABASE_URL

The restore brings the one table back as the snapshot defined it, and leaves the database's other tables as they are. The hook for each agent is a few lines of JSON in the project.

What this does not cover

Install SafeGrd on the machine, then add the hook from Agent guard.