# Rule recipes

Ready-to-paste rules for the things teams ask for most - secrets, main, production clusters, databases, the web, MCP tools and models.

Each recipe is a complete rule file you can paste into **Policies → Custom rules → Source**, or merge into your own. Every example on this page is compiled and checked against the policy engine before the docs are published.

## Keep secrets out of the agent's context

```yaml
default: allow
rules:
  - deny: { touches: dotenv }
    reason: "Secrets stay out of agent context: use the secret broker"
  - deny: { touches: private_key }
    reason: "Private keys never reach the agent"
  - deny: { touches: cloud_creds }
    reason: "Cloud credentials never reach the agent"
```

To let the agent *use* a secret without seeing it, see [the secret broker](secrets).

## Protect main

```yaml
default: allow
rules:
  - deny: "git push --force"
    reason: "Never force-push"
  - ask: "git push"
    when: { branch: [main, master] }
    reason: "Pushing to main needs a person"
  - deny: "git commit --no-verify"
    reason: "Hooks run on every commit"
```

`git push --force` covers every forcing form: `-f`, `--force-with-lease` and `+branch` refspecs.

## Read-only production clusters

```yaml
default: allow
rules:
  - deny: kubectl
    when: { flag.context: "prod-*" }
    unless: { subcommand: [get, describe, logs] }
    reason: "Production clusters are read-only for agents"
  - deny: aws
    when: { flag.profile: prod }
    reason: "No AWS calls with the prod profile"
  - deny: "terraform destroy"
    reason: "Nobody destroys infrastructure from a chat"
```

## No writes to production databases

```yaml
default: allow
rules:
  - deny: { tool: "*" }
    when: { env: prod, stmt: [update, delete, drop, truncate, alter, insert] }
    reason: "Production data is read-only for agents"
```

Warden works out `env` from host names, connection strings, profiles and MCP arguments, and reads the SQL statement and tables from `psql`, `mysql` and database MCP tools.

## Keep production data on approved models

```yaml
default: allow
rules:
  - deny: { tool: "*" }
    when: { env: prod }
    unless: { model_route: ["aws_bedrock/*", "anthropic/claude-opus-*"] }
    reason: "Production data only reaches models on our approved accounts"
```

The model is checked when the data is read, and again on every later request in that chat. If Warden can't tell which model is in use, the rule fires: missing facts fail closed.

## Web access only to sites you trust

```yaml
default: allow
rules:
  - deny: { tool: WebFetch }
    unless: { domain: [github.com, "*.github.com", docs.python.org, developer.mozilla.org] }
    reason: "Web fetches only to approved documentation"
  - deny: { domain: [pastebin.com, "*.pastebin.com", transfer.sh] }
    reason: "No uploads to paste or file-drop sites"
```

## Ask before destructive MCP tools

```yaml
default: allow
rules:
  - ask: { mcp.tool: ["*delete*", "*drop*", "*remove*"] }
    reason: "Destructive tool calls need a person"
  - deny: { mcp.server: stripe }
    when: { mcp.tool: "*refund*" }
    reason: "Refunds go through the finance team"
```

## Keep deletes inside the project

```yaml
default: allow
rules:
  - deny: "rm -rf"
    unless: { path: "./*" }
    reason: "Recursive deletes stay inside the project"
  - deny: { touches: outside_project }
    when: { access: write }
    reason: "The agent only writes inside this project"
```

`rm -rf` also matches `rm -fr`, `rm -r -f` and `rm --recursive --force`.

## Ask before new dependencies

```yaml
default: allow
rules:
  - ask: { touches: package_install }
    reason: "New dependencies need a person: check the licence and the maintainer"
```

Restoring what a lockfile or manifest already pins (`npm ci`, a bare `npm install`, `pip install -r requirements.txt`, `bundle install`, `uv sync`) isn't counted as adding a dependency.

## Stricter when nobody is watching

```yaml
default: allow
rules:
  - ask: "git push"
    when: { trigger_mode: automated }
    reason: "Unattended runs don't push"
  - deny: git push
    when: { trigger_mode: automated, assurance_meets_l2: "false" }
    reason: "Unattended pushes need L2 assurance"
```

See [Automated runs](automated-runs).

## A strict allow-list

`default: deny` makes the file an allow-list: only what a rule allows can run.

```yaml
default: deny
rules:
  - allow: { tool: [Read, Glob, Grep] }
  - allow: { tool: [Write, Edit] }
    unless: { touches: outside_project }
  - allow: npm
    when: { subcommand: [test, run] }
  - allow: git
    when: { subcommand: [status, diff, log, add, commit] }
```

> [!WARNING] Allow-lists are strict
> Even reading a file needs an allow rule. Start from a rule set and a deny list instead unless you really need this, and pin [policy tests](exceptions-and-tests#policy-tests) for the actions your agent must still be able to take.
