Ctrl K
ComunidadGuías…

rules.yaml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Squidbrake rules - hot-reloaded on save, no restart needed.
#
# Policy: agents may LOOK at anything (read, list, search). Coding agents do everyday work on their own (edits,
# tests, builds, commits), but what can't be undone waits for a person and what's catastrophic is blocked. Any other
# change (sends, refunds, pays, updates, deletes, pushes) waits for a person, and so does an agent changing its own
# settings, hooks or MCP servers, Squidbrake's rules, or a secrets file.
#
# Rules are checked top to bottom; the FIRST match wins. If none match, `default` applies.
# match fields (all optional, all must match):
#   kind, name, source, client, session_id   glob pattern or list of globs (case-insensitive), e.g. "shell*"
#   input_regex                              regex searched in the JSON of the call's input
#   input                                    number checks on input fields: {field: {op: number}}, ops gt gte lt lte eq ne,
#                                            e.g. input: { amount: { gt: 100 } }; "a.b" reaches nested fields; a missing
#                                            or non-numeric field doesn't match
#   skip_reads: true                         a shell command that only looks (cat, grep, ls, jq ...) doesn't match;
#                                            a secret read, a write or a redirect into a file still does
# action: allow | deny | review
#
# Example - let small refunds run on their own (put it above approve-refunds to use it):
#   - id: allow-small-refunds
#     action: allow
#     reason: Small refund - runs on its own
#     match:
#       name: ["*refund*"]
#       input: { amount: { gte: 0, lte: 50 } }
# For allow rules use a range: `lte: 50` alone would also let through a negative amount like -5000.
# Amounts are in your API's units - Stripe sends cents, so 50 dollars is 5000 there.
#
# review = hold the call until a person approves or rejects it (dashboard, phone, Slack).
#   timeout_seconds  how long to wait for a person (default APPROVAL_TIMEOUT, 300)
#   on_timeout       deny (default) | allow  - what happens if nobody decides in time
#   approvers        who may decide: key names or roles, e.g. ["role:finance", "priya"]; default = any approver
#   second_person    true: the person whose agent asked can't approve it (maker-checker), even with
#                    Settings > second-person approval off
# A key can never approve its own request, and an agent can't reach the approval path at all (selfguard.py).

default: review
default_reason: This changes something, so a person has to approve it

# enforce: block and hold for real. observe (or shadow): block and hold nothing, only record what would have happened
# (Reports and `squidbrake report --week` show "would block" / "would hold"), the easy way to try Squidbrake on real
# work. Stops, catastrophic commands (rm -rf /, wiping a drive) and an agent reaching for Squidbrake's own approval
# path are enforced in observe mode too. A rule or sequence can set its own `mode: enforce | observe`, e.g. enforce
# the catastrophic ones first and observe the rest.
mode: enforce
shadow_agents: []          # shadow mode for just these agents (source or key names, globs), e.g. ["new-bot*"]

# While Squidbrake can't be reached:
#   block_risky  what these rules let run on its own (reads, everyday coding work) still runs; anything that would
#                wait for a person or be blocked, and commands that can't be undone or can't be read, are blocked
#   block        everything is blocked (the safest; agents stop working until Squidbrake is back)
#   allow        everything runs
#   warn         everything runs, with a loud warning on each action; when Squidbrake is back, what ran is put on
#                the record (protection.was_off). Opt in for a laptop that reboots; Squidbrake's keys stay out of reach
# Each rule can say its own with `when_unreachable: allow | block`. Agents keep a copy of these answers, so they
# follow them while it's down. What runs while it's down isn't recorded: the tradeoff is in THREAT_MODEL.md.
when_unreachable: block_risky

# Squidbrake checks every minute that its hooks in each agent, this file and its installed program haven't changed.
#   alert        a change is recorded and sent wherever approvals go
#   fail_closed  also: until a person runs `squidbrake integrity accept`, actions that would wait for a person or
#                can't be undone are blocked (a change to this file alone only alerts)
integrity: alert

# Undo copies: before an action that waits for a person, or a command that can't be undone, keep a copy of what it
# changes (files and git changes on the agent's computer, the record behind an API call through /proxy, or what a
# client.py snapshot function returns). required: if no copy can be made, it doesn't run. best_effort: try, run anyway.
# off: don't. A rule can set its own `snapshot: required | best_effort | off`; `snapshot: required` on a rule also
# blocks paths that can't make a copy at all (an MCP tool).
snapshots: required

# Spend limits: pause an agent once it has used this many model tokens (or dollars, `usd:`) in a day or month.
# per: agent gives each matching agent its own limit; per: together shares one between them. Claude Code reports its
# tokens by itself; other agents send them to POST /v1/usage (client.py: gw.usage(...)).
# budgets:
#   - id: daily-tokens
#     match: { source: "*" }
#     per: agent
#     period: day
#     tokens: 5000000

# Proof of work: when an agent says an action is done, look in the real system. Credentials only come from the
# gateway's environment. The answer (confirmed / not actually done) is recorded and a person is told on a mismatch.
# proof_checks:
#   - id: refund-really-made
#     match: { name: "*refund*" }
#     stripe: { object: refunds, id: "{output.id}" }             # STRIPE_SECRET_KEY
#     expect: { status: succeeded }
#   - id: pr-really-open
#     match: { name: "*create_pull_request*" }
#     github: { path: "repos/{input.owner}/{input.repo}/pulls/{output.number}" }   # GITHUB_TOKEN
#     expect: { state: open }
#   - id: order-really-updated
#     match: { name: "orders.update_status" }
#     sql: { url_env: ORDERS_DB_URL, query: "select status from orders where id = :id", params: { id: "{input.order_id}" } }
#     expect: { status: "{input.status}" }

# History checks look at what happened BEFORE an action, not just the action itself.
# Each one: block (stop it), review (a person decides), warn (flag it for the approver), off.
history_checks:
  company_domains: [acme.com]          # your real email domains; look-alikes (acrne.com, acme-corp.com) are suspicious
  lookback_hours: 24
  repeat_of_rejected: review           # an action a person already rejected (same tool + same target) goes back to a
                                       # person, flagged with their earlier no and note (block: refuse it outright)
  impersonation: block                 # moving money right after reading a message from a look-alike of your domain
  payment_request_in_message: review   # moving money right after reading a message that asks for a payment
  duplicate_change: warn               # the same change on the same thing again (e.g. a 2nd refund on one charge)

# Command checks read what a shell command actually DOES before it runs: they split `a && b`, look inside
# `bash -c "..."`, `sudo`, `xargs` and `$(...)`, and understand rm / git / terraform / kubectl / cloud CLIs / SQL.
# Each one: block, review, warn, off. They apply even when a rule below allows the tool.
command_checks:
  tools: ["Bash", "PowerShell", "*shell*", "*run_command*", "*execute_command*", "*terminal*", "*exec_command*"]
  catastrophic: block     # rm -rf /, rm -rf ~, wiping a drive (rmdir /s d:\), mkfs, dd onto a disk, chmod -R 777 /
  irreversible: review    # every delete (rm, git rm, docker rm, DELETE FROM), git push --force, git reset --hard, terraform destroy, kubectl delete, cloud deletes, DROP TABLE
  rebuildable: warn       # deletes of build output, caches or temp files only (rm -rf node_modules dist, rm app.log,
                          # rm -rf /tmp/x): flagged for you to see; review holds them like other deletes
  hidden: review          # code that can't be read first: eval, curl ... | sh, base64 -d | bash, powershell -EncodedCommand,
                          # programs written inline (python -c, node -e, perl -e), a program named only when it runs
  rename: warn            # renames or moves: mv, git mv, Rename-Item, git branch -m, aws s3 mv, ALTER TABLE ... RENAME:
                          # flagged for you to see; review holds them
  secret: review          # shows a secret to the agent (and its transcript): cat key.pem, cat ~/.kube/config,
                          # gh auth token, a keychain password, kubectl get secret -o yaml
  written_then_run: review  # a script the agent created in this conversation, then runs (python x.py): read it first
  edited_then_run: warn     # a script the agent changed in this conversation, then runs: flagged for you to see;
                            # review holds it (safer: an edit can hide anything, but fixing a script and running it
                            # is everyday work)
  unknown: warn           # a program that isn't a common developer tool: recorded so you can see what would wait;
                          # review holds them too
  read_only: allow        # commands that only look (ls, cat, grep, git status/log/diff) run without asking,
                          # unless a rule below says otherwise. Set to off to approve those too.

# Taint checks catch prompt injection without a model: content someone else wrote (a web page, an email, an issue)
# says "send X to <attacker>", and the agent does. When an action sends something to a destination (email address,
# URL, bank account, repo) that appears in that outside content but NOT in what the user asked (Claude Code prompts
# are recorded) or in your own tools' results, it came from the outside content.
taint_checks:
  untrusted_destination: review   # sends to an address/URL/account found only in outside content
  after_untrusted: warn           # sends anything out after outside content was read in this conversation
  after_untrusted_setup_edit: warn  # changes the agent's own setup (held by approve-agent-setup-changes) after outside
                                    # content was read in the same conversation: names which content came first (#74);
                                    # warn only, it never changes the hold into an allow
  lookback_hours: 24
  scope: identity                 # outside content read in one conversation still counts in the next (or: session)
  # untrusted: [...]  tools whose results are outside content (defaults: WebFetch, *fetch*, *inbox*, *issue*, *ticket* ...)
  # sinks: [...]      tools that send things out (defaults: *send*, *reply*, *publish*, *transfer*, *webhook*, *upload* ...)
  #                   Shell commands count as sinks when they upload (curl -d, scp, git push, ...).

# Data checks look at text an action puts where other people read it: a pull request, an issue or comment, a gist, a
# chat post, a page (and `gh pr|issue|gist ... create|comment|edit`). Looking things up doesn't count. Email has its
# own rule above. Each one: block, review, warn, off.
data_checks:
  customer_names: review   # names a customer on your list (dashboard → Settings → Customers), e.g. in a PR description
  personal_data: review    # a card number, a social security number, an IBAN, or a list of 3+ emails or phone numbers
  # where: [...]           tools whose text others read (defaults: *pull_request*, *issue*, *comment*, *gist*, ...)

# Sequence rules judge an action by what came BEFORE it, and say which earlier step caused it.
#   match   this action (same fields as rules: name, source, input_regex, input, ...)
#   after   an earlier step that went ahead, within within_hours (default 24);
#           scope: session (the same conversation, the default) | agent (any conversation of this agent) |
#                  identity (any conversation of any agent working for the same person: on your own computer, all of
#                  them; on a team gateway, the agents whose key "works for" that person) | all (any agent);
#           the step is remembered until within_hours runs out, across restarts (`squidbrake capabilities` lists them);
#           same_target: true = only if it was on the same thing (same charge, file, account ...)
#   count   more than `more_than` earlier matching actions within within_hours; scope: session | agent | identity | all;
#           sum: <field> = add up that input field (this action plus earlier ones) instead of counting actions
#             (missing or non-numeric values count as 0); same_target: true = only earlier actions on the same thing
#   action  deny | review | warn | notify (runs, and tells the team, like a rule's notify)
sequences:
  - id: destroy-after-recovery-removed
    action: review
    reason: Destroying data right after its backups or deletion protection were turned off
    match:
      input_regex: 'delete[-_ ]?db[-_ ]?(instance|cluster|snapshot)|delete[-_ ]?(bucket|volume|database|snapshot)|volumeDelete|s3\s+rb\b|terraform\s+destroy|drop\s+(table|database)'
    after:
      match:
        input_regex: 'backup[-_ ]?retention[-_ ]?period\W{0,4}0\b|disable[-_ ]?backup|delete[-_ ]?(db[-_ ]?)?snapshot|deletion[-_ ]?protection\W{0,4}(false|0)\b|no-deletion-protection|point[-_ ]?in[-_ ]?time[-_ ]?recovery\W{0,4}(false|disabled)'
      within_hours: 24
      scope: all          # backups switched off by any agent, in any conversation, then a delete

  - id: runaway-refunds
    action: deny
    reason: Too many refunds in a short time; the agent may be stuck in a loop
    match: { name: ["*refund*"] }
    count: { more_than: 10, within_hours: 1, scope: identity }   # a loop split across two agents still counts

  - id: mass-deletes
    action: review
    reason: Many deletions in a short time
    match: { input_regex: '\b(delete|destroy|drop|rm\s+-|rmdir|remove-item)\b' }
    count: { more_than: 20, within_hours: 0.25, scope: session }

  - id: unusual-burst
    action: notify
    reason: Far more actions than usual from one agent; it may be stuck in a loop or doing something nobody planned
    match: {}
    count: { more_than: 300, within_hours: 0.25, scope: agent }

rules:
  # ---- never allowed
  - id: block-destructive-shell
    action: deny
    reason: Destructive shell command
    match:
      name: ["shell*", "bash*", "powershell*", "exec*", "run_command*"]
      input_regex: 'rm\s+-(rf|fr)\s+(/\*?|~/?\*?|\*)(?=[\s"'';&|)]|$)|mkfs|dd\s+if=|:\(\)\s*\{|shutdown|reboot|Remove-Item\s.*-Recurse.*\s(C:\\\\|~|\$HOME)'

  - id: block-sql-drop
    action: deny
    reason: Dropping or truncating tables is not allowed
    match:
      name: ["sql*", "db*", "*query*"]
      input_regex: '\b(drop\s+(table|database|schema)|truncate\s+table)\b'

  - id: block-secret-files
    action: deny
    reason: Reading secret files is not allowed
    match:
      name: ["read*", "file*", "cat*"]
      input_regex: '\.env\b(?![\w.-]*(example|sample|template))|id_rsa|\.pem\b|\.p12\b|\.pfx\b|credentials|\.kube[\\/]+config\b|\.docker[\\/]+config\.json'

  # ---- an agent changing its own setup: its settings, hooks and MCP servers, Squidbrake's own files,
  # secrets, and files that run code at login or on every commit. Squidbrake would be easy to switch off without this.
  - id: approve-agent-setup-changes
    action: review
    reason: Changes an AI agent's own settings, hooks or MCP servers, Squidbrake itself, a secrets file, or code that runs at login
    match:
      name: ["Edit", "Write", "MultiEdit", "NotebookEdit", "bash*", "powershell*", "shell*", "run_command*"]
      skip_reads: true      # `cat ~/.claude/settings.json` only looks; `cat .env` (a secret) and `sed -i` still wait
      # an agent's own notes (Antigravity's brain/, Claude Code's plans/, todos/ and memory/) are markdown it writes on
      # every task, not its setup: those .md files aren't held. Settings, hooks, CLAUDE.md, commands/ and agents/ still are.
      input_regex: '(?i)\.(claude|cursor|codex|gemini|copilot|kiro|codeium|windsurf|squidbrake)[\\/](?![\\/]*(?:antigravity[\\/]+brain|plans|todos|projects[\\/]+[^"\\/]+[\\/]+memory)[\\/]+(?:(?!\.\.)[^"])*\.md")|mcp(_config)?\.json|claude_desktop_config\.json|\.vscode[\\/]+(mcp|tasks|settings)\.json|Code( - Insiders)?[\\/]+User[\\/]+settings\.json|\.env\b(?![\w.-]*(example|sample|template))|\.ssh[\\/]|\.aws[\\/]|\.(npmrc|pypirc|netrc)\b|\.(bashrc|zshrc|bash_profile|zprofile|profile)\b|PowerShell_profile\.ps1|\.git[\\/]+hooks[\\/]|managed-settings\.json|(/etc/|ProgramData[\\/]+|Program Files[\\/]+|Application Support/)(ClaudeCode|claude-code|codex|gemini-cli|cursor|squidbrake|OpenAI)\b'

  # ---- looking is fine: reading, listing, searching run without asking (and are still recorded)
  - id: allow-reads
    action: allow
    reason: Only looks at information
    match:
      name: ["*.list*", "*_list*", "list*", "*.get*", "*_get*", "get*", "*.read*", "*_read*", "*.find*", "*_find*", "find*",
             "*.search*", "*_search*", "search*", "*.describe*", "*_describe*", "*.query*", "*.fetch*", "*_fetch*",
             "*.view*", "*_view*", "*.show*", "*_show*", "*.count*", "*_count*", "*.lookup*", "*_lookup*",
             # Claude Code's own read-only / housekeeping tools
             "Read", "Grep", "Glob", "LS", "WebSearch", "WebFetch", "NotebookRead", "ToolSearch", "TodoWrite",
             "Task", "Agent", "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "Skill", "BashOutput", "TaskOutput",
             "ListMcpResourcesTool", "ReadMcpResourceTool",
             # Antigravity's own read-only / housekeeping tools (its file reads, edits and commands are checked above)
             "read_url_content", "view_content_chunk", "read_resource", "read_terminal", "command_status",
             "manage_task", "task_boundary", "notify_user"]

  # ---- changes: a person decides (these give the approver a clearer reason than the default)
  - id: approve-money-out
    action: review
    reason: Bank transfers, wires and payouts need finance sign-off
    timeout_seconds: 900
    approvers: ["role:finance", "role:admin"]
    match:
      name: ["*transfer*", "*wire*", "*payout*"]

  - id: approve-refunds
    action: review
    reason: Refund - money goes back to a customer
    timeout_seconds: 600
    match:
      name: ["*refund*"]

  - id: approve-outbound-email
    action: review
    reason: Email to someone outside the company
    timeout_seconds: 600
    match:
      name: ["email.send", "mail.*", "*send_email*", "*email_send*", "*send_mail*", "*gmail*send*"]
      input_regex: '"to"\s*:\s*"[^"]*@(?!acme\.com")'   # change acme.com to your company's email domain

  - id: approve-writes-to-db
    action: review
    reason: Changes data in the database
    match:
      name: ["sql*", "db*", "*query*"]
      input_regex: '\b(delete\s+from|update\s+\S+\s+set|alter\s+table|insert\s+(or\s+\w+\s+)?into|replace\s+into)\b'

  # Pushing a named feature branch is everyday work (it runs); pushing main, master, a release or production branch,
  # tags, everything, or the current branch without naming it waits for a person. Force pushes are held by the
  # command checks whatever the branch.
  - id: approve-git-push
    action: review
    reason: Pushing to a shared branch (main, master, release, production), tags, or an unnamed branch
    match:
      name: ["bash*", "shell*", "powershell*", "run_command*"]
      input_regex: 'git\s+push((\s+-\S+)*(\s+[^\s"&;|-]\S*)?\s*("|&&|;|\|)|[^"&;|]*\s(\S+:)?(main|master|trunk|develop|release\S*|prod|production|HEAD)\b|[^"&;|]*\s--(all|tags|mirror)\b)'

  # Deploying from a terminal: the hosting CLIs' production deploys. Previews (`vercel`, `netlify deploy` without
  # --prod, `firebase hosting:channel:deploy`) run on their own.
  - id: approve-prod-deploys-from-shell
    action: review
    reason: Deploys to production from the terminal
    match:
      name: ["bash*", "shell*", "powershell*", "run_command*"]
      input_regex: '\bvercel\b[^"&;|]*\s--prod\b|\bnetlify\s+deploy\b[^"&;|]*\s--prod\b|\bfirebase\s+deploy\b|\bfly(ctl)?\s+deploy\b|\brailway\s+up\b|\bwrangler\s+(deploy|publish)\b(?![^"&;|]*\s--env[= ]+(dev|development|staging|preview)\b)|\beb\s+deploy\b|\b(serverless|sls)\s+deploy\b[^"&;|]*--stage[= ]+prod'

  - id: approve-prod-deploys
    action: review
    reason: Production deploy
    timeout_seconds: 900
    match:
      name: ["deploy*", "release*"]
      input_regex: '\bprod(uction)?\b'

  # ---- coding agents: everyday work runs on its own, so nobody turns Squidbrake off out of approval fatigue.
  # The command checks still hold what can't be undone (git push --force, cloud deletes, DROP TABLE, curl | sh)
  # and block what's catastrophic (rm -rf ~/, wiping a disk); the rule above holds changes to the agents' own setup.
  - id: allow-coding-work
    action: allow
    reason: Everyday coding work (edits, tests, builds, installs, commits)
    match:
      name: ["Edit", "Write", "MultiEdit", "NotebookEdit", "Bash", "PowerShell"]

# Upstreams for the HTTP proxy:  /proxy/<name>/<path>  ->  <url>/<path>
upstreams:
  httpbin: https://httpbin.org