How to stop Cursor from wiping your database
There's no cold sweat quite like watching an AI agent run a terminal command with a little too much enthusiasm.
TL;DR — A `.cursorrules` file won't stop agent writes. Safeguard your database using mandatory terminal confirmations, a strict shell wrapper, and unprivileged database roles.
One prompt, a minor backend bug, and you glance away for three minutes. The agent decides that the fastest path to a passing test suite is starting from a clean slate, so it silently runs supabase db reset right inside the integrated terminal.
Local database wiped clean. Test seeds deleted. The database migration you were halfway through writing vanishes with it, and nothing in the conversation history gave you a warning it was about to happen. On a client project, the half-day you just lost was billed to someone's budget.
This is not a bug in Cursor. It is what happens when you give an agent a terminal and rely on a text file to restrain it. Plain text is not a security boundary; it is merely a politely formulated preference.
Why prompt rules in .cursorrules fail to stop an agent
When the accident happens the first time, the natural reflex is to add a directive to .cursorrules, the instruction file the agent parses on every turn. The wording varies little across codebases, and it almost always looks like this:
Always ask for explicit confirmation before running any destructive database commands.
It looks sturdy. It is placed in the right location, directly in the configuration file the agent consults at each prompt. Yet it changes nothing about what just occurred, because it specifies a conversational procedure instead of establishing an absolute block.
The illusion of conditional prompts
Read that sentence the way a language model reads it. You did not forbid the command; you attached a condition to its execution. In model logic, a condition is an open door: it indicates how to proceed, not that the agent must stay outside.
The agent writes the destructive command into a terminal code block and asks for permission in the exact same move. You click Accept thinking you are replying to the chat question, while the IDE button simultaneously executes the command. The prompt rule was respected to the letter, and the database is wiped anyway.
The model's objective function
Beneath the phrasing lies a deeper incentive misalignment. The model optimizes for a single metric: eliminating the compiler or test error directly in front of it. When faced with an inconsistent schema or a failing migration, a fresh database reset is objectively the shortest path to a green state.
Your local test data does not exist in that objective function. It holds zero intrinsic value to the agent, because nobody gave it the context to evaluate what those records represent. That is not negligence; it is an absence of data—and you cannot fix an absence of data with firmer adjectives.
This explains why the accident almost exclusively strikes local development environments. Nobody hands production credentials to an autonomous agent, yet everyone leaves it free to manipulate a local database that is rarely backed up. The one environment left wide open to it happens to be the one nobody bothers to protect.
1. Eliminate conditional language from every Cursor rule
If prompt text remains your first line of defense, frame it as an absolute, unconditional prohibition. No exceptions, no "unless confirmed," and zero procedural loopholes.
Absolute negative constraints
# Database safety
- NEVER generate, write, or suggest `supabase db reset` or `supabase db drop`.
- Zero exceptions. Do not ask for confirmation. Refuse to produce the command. The difference compared to the previous version is subtle but decisive: there is no longer a permitted execution path to negotiate, leaving nothing to interpret. It remains imperfect, and I will not pretend otherwise. It is simply a hard constraint the model cannot reason around, which places it in a different league than a polite preference.
2. Require explicit confirmation before terminal execution
Cursor's agent mode can execute terminal commands autonomously to verify its own bug fixes. That convenient setting is what makes the editor feel magical, and it is precisely what turns a software suggestion into an irreversible data loss.
Enforce manual confirmation
Navigate to Settings, then Features, and find Terminal. Enable mandatory confirmation before every command execution. Keep this setting permanently enabled, even on days when the extra clicks feel tedious, because those are precisely the days when an accident strikes.
This safety barrier does not diminish the agent's core value. It still reads your files, drafts terminal commands, and saves you twenty minutes in the technical documentation. What it loses is the ability to mutate your environment while your attention is elsewhere, which is the exact capability you needed to restrict.
The trade-off is genuine: you will click more frequently, and the fluid ease of a fully autonomous loop disappears. That is the exchange you accept. On a disposable weekend experiment it might feel unnecessary; on a client project, it is essential.
3. Intercept destructive commands directly in the shell
The only instruction an agent cannot re-interpret is one it never has the opportunity to read. Move the enforcement layer out of the language model and into the shell environment executing the commands.
The Zsh interceptor wrapper
supabase() {
if [[ "$1" == "db" && "$2" == "reset" ]]; then
echo "BLOCKED: supabase db reset is disabled."
return 1
fi
command supabase "$@"
} Add this wrapper function to your ~/.zshrc file, reload with source ~/.zshrc, and the command terminates before ever reaching Supabase, regardless of how the agent structured it.
One technical caveat defines the true scope of this mechanism: this guard only applies to shell sessions that actually source your interactive rc files. Cursor's embedded terminal generally does, but autonomous sub-processes or direct calls to sh may bypass it entirely. Duplicate the guard inside your ~/.bashrc and treat shell wrappers as a speed bump, not an impenetrable wall.
Database permissions remain the only unbreachable barrier
Shell configurations stop the failure modes you anticipated. Database permissions stop the edge cases you never saw coming, and those are invariably the ones that cause real damage.
Revoke destructive privileges on local roles
The database role your local development environment authenticates with has no legitimate reason to execute a DROP. Production secrets do not belong on a development laptop, even inside an ignored .env file. And database backups must run on an automated schedule, never relying on human memory.
Automated database reconstitution is the critical missing link. A local database that can be restored with a single seed command versioned directly inside the repository transforms data loss from a disaster into a one-minute reset. As long as your test data exists solely on your local drive, it remains the weakest link in your workflow.
Address these foundations, and the commands an agent types cease to carry catastrophic risk. It is the exact same hierarchy behind any AI automation worth shipping to production: constraints first, velocity second. Skip the first step and you have not automated a workflow; you have simply relocated the risk where you can no longer monitor it.
Ready to lock down permissions before letting agents run?
Zero trust is not about distrusting the model; it is about eliminating the need to trust it in the first place. Plain text tells an agent what you prefer; database permissions determine what is physically possible. Build on the latter, and you can stop hovering over the Escape key every time a terminal block runs.
👉 Want the same bulletproof guardrails around the automations you run in production? Get in touch