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:
| Mode | Best for | Bulk-delete threshold | Step-up for unknown destinations | Step-up for behavioral anomalies | Lethal-trifecta exfil |
|---|---|---|---|---|---|
| Light | Exploratory / trusted environments | 100 files | No | No | AUTH |
| Balanced (default) | Everyday coding agents | 25 files | No | Yes | AUTH |
| Strict | Production repos, shared codebases | 10 files | Yes | Yes | BLOCK |
| Paranoid | Highly autonomous or security-critical agents | 3 files | Yes | Yes | BLOCK |
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 fetchalso 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
BLOCKin 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
BLOCKin 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 tuishow 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 rundoberman phone test.doberman phone testsends another test notification once you're subscribed.doberman phone statusshows 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 offturns 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.