Tune to your risk tolerance

September 10, 2026 · View on GitHub

Doberman ships with sane defaults, but you can change how loud it is. This page covers the dials: strictness mode, the enforcement dial, the opt-in default role, the subjective preference weights, friction telemetry, and message tone.

Strictness modes, doberman mode

Set a mode in .doberman/policies.yaml or with doberman mode <mode>. Every mode change made this way (the CLI or the setup wizard) is recorded in the append-only policy-change ledger, viewable with doberman policy-history. Lowering strictness (paranoid to strict to balanced to light) is a weaken. It needs confirmation plus a possession factor: TOTP (a one-time code from an authenticator app) if enrolled, otherwise the local Doberman password. With neither enrolled, the lowering fails closed (denies by default rather than guessing); confirmation alone never suffices. Raising stays frictionless and applies immediately:

ModeBest forBulk-delete thresholdStep-up for unknown destinationsStep-up for behavioral anomaliesLethal-trifecta exfil
LightExploratory / trusted environments100 filesNoNoAUTH
Balanced (default)Everyday coding agents25 filesNoYesAUTH
StrictProduction repos, shared codebases10 filesYesYesBLOCK
ParanoidHighly autonomous or security-critical agents3 filesYesYesBLOCK

Hard blocks (secret exfiltration, destructive commands, role-boundary violations, smuggled-token-channel exfiltration) are identical in every mode. The mode dial only changes where step-up authentication is required for ambiguous or high-risk actions.

A few behaviors depend on mode:

  • CI/CD pipeline configuration (GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure Pipelines) steps up to authentication in every mode: that config builds, tests, signs, and deploys the repo, so an agent rewrite is always a human-in-the-loop moment.
  • Default-registry package fetches get a Light/Balanced relaxation: a recognized package-manager fetch with no explicit URL (pip install requests, npm install, poetry install, go mod download, and similar) passes without a prompt, because its route is the manager's default registry, a stronger signal than the unknown hosts those two modes already allow. Anything that could redirect the route, an explicit URL, --index-url/--registry, a proxy or registry environment variable, a chained command, or the publish/upload direction, still steps up in every mode. git pull/git fetch also still step up, since their route depends on the configured remote. Strict/Paranoid keep the prompt for every package fetch.
  • Unknown network destinations step up to authentication only in Strict/Paranoid; Light and Balanced treat a plain unknown host as allowed (a secret leaving to any host is still a hard block in every mode, and sharper destination smells, embedded credentials, raw IPs, unresolvable hosts, still step up everywhere).
  • Checksum-valid personal or financial data (a payment card number, an IBAN, a dashed US SSN) bound for an external destination steps up to authentication in every mode, but only when the data co-occurs with an outbound destination, not merely written locally. Only the class label ("payment card number") ever reaches a log, never the value.
  • The lethal trifecta (sensitive data, untrusted-content provenance, and an external destination together) steps up to authentication in Light/Balanced and is a hard BLOCK in Strict/Paranoid, at both decision layers, so a step-up on some other signal can never mask it back down.
  • A local hard smuggled-token channel is a hard BLOCK in Strict/Paranoid and stays AUTH in Light/Balanced.

Enforce, monitor, or off, doberman enforcement

Orthogonal to strictness mode is the enforcement dial (enforce (default) / monitor / off), which decides whether Doberman acts on a verdict or only observes:

  • enforce: the normal behavior, AUTH prompts and BLOCK denies.
  • monitor: the discretionary layer (behavioral anomalies, soft step-ups) is evaluated and recorded (doberman log / doberman tui show what would have happened) but never blocks or prompts. Use it to try Doberman on a repo without friction, or to tune before turning it on.
  • off: the discretionary layer isn't evaluated at all.

Set it with doberman enforcement <enforce|monitor|off>. No argument prints the current state. Turning the dial down is gated the same way as lowering mode: confirmation plus the strongest enrolled possession factor, recorded in the ledger. With neither factor enrolled, the change fails closed. Turning it back up re-arms automatically, no gate.

The objective floor stays live in every state. Secret exfiltration, destructive commands, protected-path writes, role/policy blocks, and the lethal trifecta always block regardless of the dial. monitor/off only softens the discretionary verdicts. The on-disk value is ledger-verified on every call, so a hand-edited enforcement: off in policies.yaml with no matching approved change is caught and clamped back to enforce.

The built-in default role, doberman role enable-default

The role boundary is normally dormant until you hand-write .doberman/role.yaml. With no active role, the role rule abstains. doberman role enable-default opts a repo into a packaged, generic least-privilege role for a coding assistant instead, no YAML required. Ordinary source, config, docs, and test files anywhere in the repo classify as in-scope, while CI/CD config, infra/**, migrations/**, and any unrecognized file type step up to authentication. Paths outside the repo root are always a hard block, for every role. An explicit .doberman/role.yaml always takes precedence over the opt-in default. Turning the opt-in off (doberman role disable-default) is a weaken, gated behind the same possession-factor confirmation as lowering mode or enforcement.

Protecting extra branches from force-push, protected_branches

Force-pushing to main, master, release, or develop always blocks, no configuration needed. To protect additional branches for a repo, such as staging or a release train branch, add a protected_branches list to .doberman/role.yaml:

role: backend
protected_branches:
  - staging
  - release-2026.9

This only adds names. The four built-in branches always block and this key can never remove them. Names are matched exactly, case-insensitively, with no wildcards or glob patterns. Surrounding whitespace and a leading refs/heads/ are stripped before matching, so refs/heads/staging and " staging" both work the same as staging. The key sits alongside the role (or inline allowed/suspicious/blocked) definition in the same file, so a .doberman/role.yaml that sets only protected_branches and no role still falls back to the most-restrictive role, the same as any other role.yaml with no usable role.

A malformed protected_branches value, such as a plain string instead of a list, a list with a non-string entry, or an entry that is blank once whitespace is stripped, fails the whole role closed to the most restrictive role. This is not limited to branch protection: a bracket typo here also drops the operator's custom allowed/suspicious/blocked path scope, because the whole role.yaml file is rejected together, not just this one key.

Subjective preference weights, doberman prefs

The adaptive layer's four "care" weights, confidentiality, reversibility, interruption_tolerance, and blast_radius (how much damage a mistake could do), each in [0, 1], tune how readily discretionary behavioral signals step up. The objective hard-block floor never moves. Show the active vector with doberman prefs. Set one weight with doberman prefs <dimension> <value>. The same lowering rule as mode applies: lowering a weight requires TOTP if enrolled, otherwise the local Doberman password. With neither enrolled, it fails closed. Raising a weight is a strengthen and always applies immediately. Every attempt, approved or denied, is recorded in the append-only ledger.

Friction telemetry and gated tuning, doberman tune

Every AUTH prompt is already in the redacted decision log. doberman tune turns it into a friction report: interventions per session, top AUTH reasons, approval rates by reason and by target class, and a weekly trend. Where an (action_type, target_path_class) pair has been approved every single time, at least --min-occurrences times (default 5), and every one of those AUTHs was a role_out_of_scope prompt (never a secret/exfiltration/destructive/control-plane code, that allowlist is fixed), it proposes a standing elevation: a narrow, time-limited, revocable PASS for that exact class. doberman tune never applies anything by itself. Running it only reports. Accepting one with doberman tune --accept <id> routes through the same possession-factor-gated weaken chokepoint as any other policy loosening, and the resulting grant is revocable early with doberman revoke <elevation-id>.

Approval memory (5 minutes)

After a human completes a real local_auth or two_factor proof, Doberman remembers only a keyed HMAC fingerprint (a one-way hash tied to a secret key, standing in for the real values) of that exact action for 300 seconds. An identical repeat still prompts. It uses soft_confirm and says how many minutes ago the exact action was approved. A soft-confirm approval never extends or chains the window. The decision, risk, and reason codes do not change, and the decision log records soft_confirm+memory distinctly.

Identity includes action type, tool name, unredacted command or structured arguments, canonical target paths (the path after resolving ./.. and symlinks), and repo root. The raw values are HMAC input only and are never retained. Host hooks also require matching session ids when both sides have one. The raw MCP (Model Context Protocol) proxy has no session id and uses repo scope, as do entries where either side lacks a session id. Missing fingerprint keys, storage errors, missing repo roots, and tainted sessions (sessions that read something sensitive earlier) all fall back to the full tier.

Memory never reduces authentication for file deletion, critical risk, role-boundary violations, encoded exfiltration, opaque commands, protected paths, destructive or history-rewriting commands, bulk operations, irreversible high-blast actions, or correlated destructive flows. Role elevation keeps its own narrow, time-limited, single-use grant and never participates.

Use doberman approvals status, doberman approvals clear, or doberman approvals ttl <seconds>. The allowed TTL is 0–900 seconds. 0 disables all reads and writes. Raising the TTL, including enabling from 0, is a weakening and requires the existing possession-factor gate. Lowering, disabling, and clearing are strengthenings and are ungated.

Plain or technical wording, doberman message-tone

The authorization prompt speaks plain English by default, for example "Your agent wants to run a command: git push --force main. The command looked destructive. Approve this exact action?", so you can read a catch and decide in seconds without parsing reason codes. For the detailed engineering view, doberman message-tone technical switches to the terse [RISK: …] role: … reason: … block, and doberman message-tone human switches back. It changes wording only. The change is cosmetic, so it needs no possession-factor confirmation, and it never touches the decision, the reason codes, or what lands in the decision log. The command line shown is rendered from the raw arguments with Authorization, X-Api-Key, and X-Auth-Token header values, common password flags, and unbroken 40+ character credential-like runs masked, then cut at 300 characters; a base64 value containing / is not covered. The decision log keeps only its redacted copy.

Recovery actions

Taint clearing, tool-pin approval, learned-memory reset and pruning, and fully removing a project all live in the recovery guide.

Every gated change above is also recorded as a policy version (pv1: + a content hash) in .doberman/policies.db. doberman policy-versions lists them, and --verify confirms the policy on disk is the last recorded one. See POLICY_VERSIONS.md.

Phone approvals (ntfy), doberman phone

When a challenge needs a human, Doberman can push it to your phone through ntfy (a free, open push notification service; self-hosting is also possible) instead of only waiting on a local dialog. The notification carries Approve and Deny buttons, so a tap answers the challenge from wherever you are.

Every AUTH tier uses it once it is on. On soft_confirm and local_auth a tap is the confirmation. On two_factor and role_elevation a tap is the second factor and replaces the TOTP code: nothing else is asked after a tap. Enable it only if a tap on your unlocked phone is a good enough second factor for you. It covers the Claude Code, Codex, and Cursor hooks and the MCP proxy. OpenClaw hands an AUTH verdict to its own /approve flow and never runs Doberman's phone challenge.

Four commands:

  • doberman phone setup [--server URL] [--token TOKEN] [--wait SECONDS] [--force] turns it on. It generates two secret topic names, prints the one to subscribe to in the ntfy app (the second, reply topic, is never shown), and sends a test notification. If the test notification fails, the config is kept and the method stays enabled: subscribe, then run doberman phone test.
  • doberman phone test sends another test notification once you're subscribed.
  • doberman phone status shows whether it's on, the server host, the first four characters of the subscribe topic, and the wait time; never the reply topic or the token.
  • doberman phone off turns it off and deletes the local config.

The config file lives at %LOCALAPPDATA%\doberman\ntfy.json on Windows, or $XDG_CONFIG_HOME/doberman/ntfy.json (falling back to ~/.config/doberman/ntfy.json when that variable is unset) on Linux/macOS. DOBERMAN_NTFY_FILE overrides the path.

--wait sets how many seconds Doberman waits for a tap, clamped to 10-300 (default 60), on every tier. If nobody taps in time, the host hooks move on to the desk dialog (120 s), then the terminal prompt, and on the 2FA tiers end at the TOTP code entry; the MCP proxy tries its dashboard first, then the phone, then the same fallbacks. Silence is never taken as approval. If no channel answers, the whole challenge auto-denies after 10 minutes and the action is refused. --wait changes only the phone stage; it cannot extend that ceiling, and the Claude Code hook's 660 s harness timeout stays above it.

Phone approval is one of the approval methods doberman 2fa methods list shows, and the 2FA tiers use the first enabled method that is available here, in preference order. If Windows Hello is enabled ahead of ntfy, the phone never rings for a 2FA challenge on that machine; doberman 2fa methods status shows which proof the next challenge will use.

--server points setup at a self-hosted ntfy instance instead of the public ntfy.sh. Use an https:// URL: over plain HTTP the notification and the reply carry the secrets below in the clear. --token is a bearer token for that server, when it needs one. A token typed on the command line lands in shell history, so read it from a file instead (--token "$(cat ~/.ntfy-token)" in bash, --token (Get-Content ~/.ntfy-token) in PowerShell), or edit ntfy.json after setup. Scope the token to those two topics only, with server ACLs that let only your phone and Doberman read or publish them.

The push itself carries the command the agent asked to run, rendered the same way the desk dialog renders it, with Authorization, X-Api-Key, and X-Auth-Token header values, common password flags, and unbroken 40+ character credential-like runs masked (a base64 value containing / is not covered), and the text length-bounded. The gated command line therefore leaves the machine and is stored on whichever ntfy server you point at, public ntfy.sh included, until that message expires. Point --server at a self-hosted instance if that is not acceptable.

Both topic names and the token are secrets, not just the reply topic. The push notification itself carries the reply URL, the exact Approve/Deny reply text, and the bearer header, so anyone who can read the subscribe topic can approve or deny without ever learning the reply topic. The two-topic design stops replayed or stale replies and anyone who knows only the reply topic; it does not stop a reader of the subscribe topic. If a topic leaks, rotate with doberman phone setup --force, which generates two new topics. On ntfy.sh there is nothing to revoke and phone off only deletes the local file; on a self-hosted server, also revoke the old token and topic ACLs.