How to enforce guardrails on AI agents that touch production data
Why a 'never touch production' line in CLAUDE.md is not a guardrail, and where permissions belong instead
To enforce guardrails on an AI agent that touches production data, put the restriction in a layer the model cannot talk its way past: tool permissions, hooks, and database credentials. A line such as "never touch production" in CLAUDE.md is context the model reads, not a control on what it can execute. Claude Code's permissions documentation describes allow, ask and deny rules that the client evaluates before a tool runs, and that is the layer where enforcement happens.
Key Takeaways
- •A CLAUDE.md instruction is a prompt. It shapes behavior, but nothing in it blocks a command from running.
- •Real enforcement lives in three places: client-side deny rules and hooks, the MCP server's tool surface, and the database role the agent connects with.
- •The strongest guardrail is a credential that cannot write. If the role is read-only, no prompt injection or mistaken approval can change that.
- •Autonomy should be set per domain, not per agent. Reading a staging table and altering a production schema are different risk classes and need different settings.
- •Persisted 'don't ask again' approvals are a standing permission. Treat them like credentials: review them, scope them, and keep them out of public repos.
How to enforce guardrails on AI agents that touch production data
The argument for moving permissions out of the prompt got louder this week. A Hacker News story about Claude Code's "Don't Ask Again" option reported passwords exposed in 1,122 public GitHub files. We have not audited that count ourselves, so treat the number as the headline's claim. The mechanism is the lesson: an approval the user clicked once becomes a persisted rule, and a persisted rule is a permission that lives in a file, outside any conversation, and can be committed by accident.
Data work raises the stakes. A coding agent that misbehaves in a repo usually leaves a diff you can revert. An agent with a warehouse connection can drop a table, rewrite a view that feeds a finance dashboard, or read a column of customer PII into a transcript. The blast radius sits in systems that git does not version.
Why a CLAUDE.md rule is not a guardrail
CLAUDE.md is loaded into the model's context at the start of a session. The model usually follows it, and that is exactly the problem: "usually" is a statistical property, and a guardrail is supposed to be a guarantee. In our reading of the docs, the file is guidance the model weighs alongside everything else it sees, including file contents, tool output and web pages. Any of those can contradict it.
Three failure modes come up repeatedly when we review agent setups on data teams:
- •Context dilution. In a long session, an early instruction competes with thousands of tokens of later material. The rule is still in the window, but it is one signal among many.
- •Prompt injection. A comment in a SQL file, a row in a table, or a README can carry instructions. The model cannot reliably tell your CLAUDE.md from text that merely looks like instructions.
- •Reasonable-looking exceptions. An agent debugging a failed job decides that one DELETE against production is the fastest fix. It is not defying the rule; it has talked itself into a case the rule did not anticipate.
None of this means CLAUDE.md is useless. It is the right place for conventions: naming, style, which dbt project to touch first. It is the wrong place for anything whose violation costs you an incident.
The four layers where enforcement actually lives
We think about agent permissions as four layers, ordered from weakest to strongest. Each layer you add shrinks what a mistake can do.
| Layer | What enforces it | What it stops | What it misses |
|---|---|---|---|
| 1. Prompt | CLAUDE.md, system prompt | Common mistakes, style drift | Injection, long-context drift, creative exceptions |
| 2. Client rules | Allow, ask and deny rules in the client settings | Specific commands and paths before they run | Anything reachable through a tool you allowed |
| 3. Hooks | PreToolUse hooks that inspect and block calls | Patterns you can test for, such as DDL against a prod host | Logic you did not think to write |
| 4. Credentials | Database roles, warehouse grants, network rules | Everything the role cannot do, regardless of what the agent decides | Nothing the role is allowed to do |
Layers 2 and 3 are client features. The Claude Code hooks reference documents PreToolUse hooks, which run before a tool call and can block it. That makes them a good place to encode "no write statements against the production host" as code instead of prose. Deny rules in the settings file do the same job for simpler patterns, and a deny rule takes precedence over an allow rule.
Layer 4 is the one teams skip, and it is the one that matters most. If the agent connects as a role with SELECT only, then "never write to production" is true by construction. The agent can be wrong, confused or manipulated, and the warehouse will still refuse the statement.
Step by step: a guardrail setup for production data
This is the sequence we would follow for a team putting Claude Code or Cursor in front of a real warehouse. It assumes nothing about which warehouse you run.
- •Start with credentials. Create a dedicated, read-only role for the agent. Grant it access to the schemas it needs, not the whole database. Give it a separate, short-lived credential for staging if it needs write access there.
- •Mask sensitive columns at the role level. If the agent should not see raw PII, use column masking or views so the data never reaches the transcript. A prompt telling the model to ignore a column does not remove the column from its context.
- •Route data access through an MCP server instead of a raw connection string. The Model Context Protocol lets you expose named tools such as 'describe table' or 'run read-only query' rather than a general shell. A narrow tool surface is a guardrail in itself.
- •Add deny rules for the commands you never want, such as destructive CLI calls and reads of credential files. Add a PreToolUse hook for patterns that are too contextual for a static rule.
- •Make approvals expire. Review persisted approvals the way you review IAM policies, and never let a command containing a secret become a saved rule.
- •Log every tool call to a place the agent cannot edit. If something goes wrong, you want a record that was written by the system, not reconstructed from the conversation.
Steps 1 and 2 do most of the work. Steps 3 through 6 are what let you raise autonomy later without losing control, which is the next problem.
Autonomy is a dial, and it should be set per domain
Most guardrail advice treats autonomy as one switch: the agent either asks first or it does not. Data platforms do not work that way. Querying a gold table to answer "which tables haven't been queried in 90 days?" is low risk. Applying a schema migration to a table that three downstream pipelines read is high risk. One agent, two very different permission needs.
This is how we built the Autonomous Data Conductor at Data Workers. Autonomy is a per-domain dial rather than a global mode. A team might set quality checks to fully autonomous, cost recommendations to propose-only, and anything touching PII or schema changes to require approval. The goal is to reduce data ops toward zero human toil, but the dial lets a team get there one domain at a time, as trust is earned, instead of betting everything on a prompt.
The open-source edition (dw-claw) is Apache 2.0, runs locally and is read-only by default. Writes, approval workflows and audit trails arrive in Pro. We chose read-only as the default for the same reason this post argues for read-only credentials: the safe state should be the one you get without configuring anything.
Where each kind of data task sits on the dial
Here is how we map the questions our users run to the setting we would start with. These are our recommendations, not a standard.
| Task | Example prompt | Suggested starting setting |
|---|---|---|
| Read-only investigation | "What changed in the orders schema since last deploy?" | Autonomous |
| Impact analysis | "What's the blast radius if I drop this column?" | Autonomous |
| Cost review | "How much are we spending on Snowflake this month?" | Autonomous reads, propose-only changes |
| Access audit | "Who has access to PII columns in analytics?" | Autonomous reads, approval for policy changes |
| Incident diagnosis | "Why did row counts drop 40% overnight?" | Autonomous diagnosis, approval for fixes |
| Schema or pipeline change | Apply a migration to a production table | Approval required |
Our Governance Agent handles the access and PII rows, including policy generation and audit trails. The Schema Agent produces the blast-radius analysis before any change is proposed, so the human approving a migration sees the downstream impact first. Both are MCP servers, so they run inside Claude Code or Cursor under the same permission model described above.
Audit trails are part of the guardrail
A guardrail you cannot inspect is a guess. When an agent touches production data, you need to answer three questions after the fact: what did it run, under whose approval, and what did it see. That requires tamper-evident logging at the framework level, not a transcript someone saved.
Data Workers applies RBAC, PII redaction, retention controls and tamper-evident audit trails across every agent and tool, and data stays inside the customer's infrastructure. We covered the governance side in more detail in our material on agents for data governance. The point for this post is narrower: logging that the agent can influence is not an audit trail.
A short checklist before you give an agent production access
- •Can the agent's credential write? If yes, is that intentional and scoped to one schema?
- •Is any sensitive column visible to the role that should be masked?
- •Do deny rules or hooks cover the destructive commands, and have you tested them by trying to trigger them?
- •Are there persisted approvals in settings files, and could any contain a secret?
- •Is every tool call logged somewhere the agent cannot modify?
- •Can you turn autonomy down for one domain without turning the whole agent off?
Frequently Asked Questions
Does a CLAUDE.md rule stop an agent from running a destructive command? No. CLAUDE.md is read as context by the model, and the model usually follows it, but nothing in the file blocks execution. To actually prevent a command, use a deny rule or a PreToolUse hook in the client, and a read-only database role underneath both.
What is the single most effective guardrail for an agent that queries a warehouse? A read-only credential. It enforces the restriction at the data layer, so a wrong approval, a prompt injection or a model error cannot produce a write. Everything else in this post reduces risk further, but the credential is the one control that does not depend on the agent behaving.
Is it safe to click 'don't ask again' on a command? Only if you are comfortable with that command running every time in that scope. Persisted approvals become rules stored in settings files. Review them periodically, avoid saving any command that contains a password or token, and keep local settings files out of version control.
How do I let an agent act autonomously without giving it full production access? Split autonomy by domain. Let it investigate, diagnose and recommend freely using read-only access, and require human approval for anything that changes schemas, policies or pipelines. That is the model behind the per-domain autonomy dial in our Autonomous Data Conductor.
Do MCP servers make agent access safer? They can. An MCP server exposes a defined set of tools, so you choose the surface instead of handing the agent a general shell. The protection depends on how narrow you make the tools and what credentials the server itself holds, so apply the same read-only and scoping rules there.
If your team is wiring Claude Code or Cursor into a warehouse, start with the credential and work upward. When you want agents to run more of the platform on their own, the Data Workers agents run as MCP servers inside the tools your engineers already use, with the autonomy dial set per domain.