DobermanDocs
Menu

Reference › Reason codes

Reason codes

Every non-PASS Doberman decision carries one or more ReasonCode values plus a human explanation. ReasonCode itself is defined in src/doberman/models.py. This page catalogues every member of that enum: where in the code it actually gets attached to a decision, and what it means in plain language. The enum currently defines 65 codes. doberman log and doberman tune --json (see CLI reference) both surface these values directly, so matching on the name here is the stable way to script against a decision.


Code Group Raised in Meaning
normalization_failed General engine/rules/normalization.py, proxy/normalize.py The proxy's normalize() step raised, so the action falls back to a conservative high-risk placeholder and escalates to authentication instead of passing through un-normalized.
unknown_tool General (reserved, enum only) Reserved for a requested tool name absent from the downstream tool list. Defined for that purpose but not wired to a raise site in shipped code.
downstream_error General proxy/executor.py The downstream tool call, made over MCP (Model Context Protocol, the interface Doberman's proxy uses to reach real tools), raised while being forwarded, so the result is denied and the failure is never exposed to the agent.
single_use_elevation_unclaimable General proxy/executor.py A single-use elevation covered the action, but the atomic claim taken just before the forward found it already spent (a concurrent call won it) or could not be recorded, so the forward is refused rather than let two calls release on one approval.
effect_set_diverged General proxy/executor.py The proxy recomputes a delete-class command's bounded blast-radius effect set right before forwarding it. If that recompute's digest disagrees with what was shown to the human at approval time, in either direction, or a previously-known count becomes unknown, the forward is refused with a synthetic BLOCK rather than executed against a stale preview.
ambient_scoring_error General monitor/daemon.py The ambient monitor (FM.2) could not reconstruct a SecurityObject from a redacted ActivityEvent, or scoring it through decide() raised. Per-event isolation records a conservative alert row instead of dropping the event silently or letting one poisoned event kill the daemon's tick loop. Never produced on the live/inline decision path.
objective_guardrail_error General engine/decision_engine.py, proxy/executor.py The objective guardrail, or a post-decision integrity check such as the output secret scan or artifact digest check, raised an unhandled exception, so the action fails closed to BLOCK.
subjective_guardrail_error General engine/decision_engine.py, engine/subjective.py The subjective guardrail raised an unhandled exception after the objective guardrail already passed, so the action escalates to authentication instead of silently passing.
subjective_block_clamped General engine/decision_engine.py, policy/drift.py The subjective guardrail returned BLOCK, but none of its reason codes sit on the hard-block allowlist, so the execution rule clamps that BLOCK down to AUTH and records that the clamp happened.
proxy_handler_error General proxy/executor.py An unhandled exception escaped the proxy's top-level tools/call handler, outside normalize()'s and decide()'s own error handling, so the call is denied carrying only the exception's class name, never its message.
secret_exfiltration Objective guardrail engine/rules/secrets.py, hosthooks/hookio.py, policy/modes.py, proxy/executor.py A known credential or secret pattern, or a base64/hex payload that decodes to one, appeared in an action headed to an external destination. The rule hard-blocks it in every mode.
sensitive_secret_access Objective guardrail auth/challenge.py, engine/rules/secrets.py, proxy/executor.py The action reads or targets a file recognized as holding secrets, such as .env or a private key, with no evidence it is leaving the session, so it requires authentication.
possible_high_entropy_secret Objective guardrail engine/rules/secrets.py Only the weak, purely statistical high-entropy heuristic matched: no known credential shape and no secret file, so the action requires authentication. It is a lower-confidence sibling of sensitive_secret_access, kept separate so output scanning can pass through likely-benign hashes, UUIDs, and base64 fragments instead of hard-blocking a benign read.
protected_path_blocked Objective guardrail engine/rules/commands.py, engine/rules/paths.py, policy/modes.py The target path resolves inside a location explicitly protected by policy, such as Doberman's own control-plane files, so the rule hard-blocks the action.
sensitive_path_access Objective guardrail auth/challenge.py, engine/rules/paths.py, policy/drift.py The target path matches a sensitive-but-not-protected location, so the action requires authentication before it proceeds.
destructive_command Objective guardrail engine/rules/commands.py, policy/modes.py A shell or git command matched a pattern that irreversibly destroys data or history: a recursive force-delete, a history rewrite, or piping a downloaded payload into a shell. Also covers process kill/signal commands (kill/pkill/killall/taskkill, os.kill, psutil kill/terminate) at the AUTH tier; own-job targets (%N, $!) and probes (-0, -l) stay PASS.
environment_dump_command Objective guardrail engine/rules/commands.py A shell command whose sole effect is to enumerate or print the process environment (bare env, any form of printenv, export/export -p, declare -x/typeset -x with no named variable, or a PowerShell Env: drive listing). This is a locally scoped read of a common secret carrier, so it requires authentication before it runs.
bulk_operation Objective guardrail auth/challenge.py, engine/rules/commands.py, policy/drift.py A command deletes at or above the configured bulk-operand threshold, so it requires authentication before the mass operation proceeds.
opaque_command Objective guardrail auth/challenge.py, engine/rules/commands.py A command's effect cannot be determined statically: an inline shell payload (-c ...), or a command that fails to parse safely. It requires authentication before it runs. This also covers runuser -c/--command/--command=, which is opaque exactly like su -c and never a transparently-consumed wrapper option. It also covers an inline Python/Node payload (python -c/node -e) that opens a socket directly with no shell-spawn, a raw-egress shape folded into this general opaque-inline-payload code rather than raw_socket_channel. (A payload that opens a socket AND spawns a subprocess/shell is a reverse shell, which goes to raw_socket_channel BLOCK instead, not this code.) It also covers an interpreter one-liner that spawns a subprocess (subprocess, os.system, child_process, …); the command literals it passes are walked by the same rule and can raise the step-up to BLOCK. Finally, it covers an inline payload fragmented into more than 128 candidates (a length proxy: ordinary one-liners measure 7–43) or naming more than 32 absolute/~/.. paths (the filesystem-resolve budget; every path is still matched textually). This last check is a floor applied after every stronger check, so a control-plane/destructive/socket/kill/privilege match on the same payload still BLOCKs/AUTHs at its own tier first.
unknown_external_destination Objective guardrail auth/challenge.py, egress/local.py, engine/rules/destinations.py A network destination is not on the trusted-host allowlist and cannot otherwise be classified as safe, so it requires authentication, subject to mode thresholds and any proven-broker override.
egress_requires_auth Objective guardrail engine/rules/destinations.py A shell, package-install, or git command has an external destination whose runtime route static parsing cannot prove, so it requires authentication regardless of how trusted the parsed host looks.
encoded_exfiltration Objective guardrail auth/challenge.py Reserved for a base64/hex-encoded payload that decodes to a secret pattern. It is wired into auth tiering and policy, but the shipped secrets rule currently reports such decoded hits as secret_exfiltration or possible_high_entropy_secret, so this code is not presently emitted.
tool_schema_changed Objective guardrail proxy/executor.py The tool's (name, description, inputSchema) fingerprint no longer matches the pinned one, a possible rug-pull. This is AUTH in Light/Balanced and BLOCK in Strict/Paranoid until a human re-approves the pin.
rule_error Objective guardrail engine/objective.py, engine/subjective.py An individual objective or subjective rule raised, or returned something other than a GuardrailResult, while being evaluated. That one rule's result is isolated to AUTH instead of forcing a silent PASS or an unrelated hard BLOCK.
role_blocked_target Role policy engine/rules/role_boundary.py, policy/modes.py The active agent role's boundary matcher explicitly denies this target, so the rule hard-blocks the action.
role_out_of_scope Role policy auth/challenge.py, engine/rules/role_boundary.py, policy/drift.py The action falls outside the boundaries declared for the agent's active role without being on an explicit block list, so it requires authentication (a role elevation).
policy_source_blocked Role policy engine/rules/policy_source.py, policy/modes.py The instruction's originating source is on a higher-authority policy source's block list, so the rule hard-blocks the action.
policy_source_sensitive Role policy auth/challenge.py, engine/rules/policy_source.py The instruction's originating source is classified as sensitive, but not blocked, by the policy source, so it requires authentication.
unusual_for_workflow Subjective layer engine/subjective.py, policy/drift.py The action deviates from this agent's own learned per-workflow behavioral baseline (a running profile of what normal, everyday actions look like for that workflow), raising the subjective verdict.
unusual_for_deployment Subjective layer engine/subjective.py, policy/drift.py The action's surprise score, computed across the universal three-axis algebra, exceeds this deployment's care threshold, raising the subjective verdict.
confidentiality_sensitive_destination Subjective layer engine/subjective.py An above-threshold action's dominant care dimension is confidentiality, and the destination itself is sensitive.
irreversible_high_blast Subjective layer engine/subjective.py An above-threshold action's dominant care dimension is irreversibility and blast radius rather than confidentiality: it would be hard to undo and would affect a lot if it went wrong.
lethal_trifecta Subjective layer engine/decision_engine.py, engine/subjective.py, subjective/revealed.py The action's algebra shows untrusted-data provenance, access to private data, and an external communication channel all at once, flooring the verdict to AUTH (BLOCK in Strict/Paranoid). It is the only subjective code allowed onto the hard-block allowlist.
unclassified_action Subjective layer engine/subjective.py, policy/drift.py The action's algebra classification is still effectively unknown by the time the subjective layer runs, so it is flagged rather than treated as benign.
pii_data_class_egress Objective guardrail engine/rules/data_classes.py The outbound payload contains checksum-valid personal or financial data: a payment card number (issuer prefix plus Luhn check), an IBAN (mod-97), or a dashed US SSN. Because the action has an external destination, every mode requires authentication. Only the class label is recorded, never the matched value.
smuggled_token_channel Token-channel defense engine/rules/token_channels.py A deterministic, near-zero-false-positive objective token-channel signature matched: tokens smuggled through an unexpected channel. It produces AUTH or BLOCK depending on severity.
anomalous_token_pattern Token-channel defense engine/detectors/token_channels.py The subjective token-channel detector found a soft, probabilistic out-of-distribution signal: homoglyph confusables (characters from a different alphabet that look identical to the expected one, like a Cyrillic "а" standing in for a Latin "a"), under-trained glitch fragments, NFKC-normalization smuggling, or anomalous control/private-use characters. It requires authentication.
oversized_encoded_blob Encoded-exfiltration detector engine/detectors/base64_blob.py The subjective base64-blob detector found a suspiciously large base64-looking payload in a tool argument, a common encode-and-exfiltrate shape (file or secret contents smuggled out as one blob). It checks shape and size only, never decodes, and raises only to AUTH.
multi_step_exfil Host-hook containment engine/taint_floor.py, hosthooks/hookio.py This egress action's session already carries taint, a marker meaning a secret was read earlier in the session, so anything sent out afterward is treated as suspect. Even though this call's own payload looks clean, the verdict is raised (AUTH, or BLOCK in Strict/Paranoid) as a potential cross-call exfiltration.
confirmed_exfil Host-hook containment engine/taint_floor.py, hosthooks/hookio.py An outbound value's keyed-HMAC fingerprint (a one-way hash computed with a locally held key, so the value's identity can be matched without storing or exposing the value itself) matches a secret read earlier in this same session, confirming a read-then-send exfiltration. The rule hard-blocks it in every mode.
untrusted_value_echo Host-hook containment engine/taint_floor.py, hosthooks/spine.py, proxy/executor.py A host, URL, or email address in this call's outbound payload matches, by keyed-HMAC fingerprint, a value that first entered this session's context from an untrusted read (a WebFetch/WebSearch result). Whole-value match only, not flow analysis. Raises to AUTH, capped there in every mode (v1). Mail addresses are matched after undoing common [at]/(dot)/spaced obfuscation, so user [at] host [dot] com fingerprints the same as user@host.com. This is mechanical, not semantic, so a preposition next to a stray spaced period can occasionally be read as an address that was never actually named. That only ever produces an extra AUTH prompt on a later send to that exact address, never a silent allow.
turn_gate_error Turn gate engine/decision_engine.py, turngate/heuristics.py, turngate/hook.py The turn gate, either its Tier 0 signature scan or its Tier 1 heuristics, raised while evaluating a turn. It escalates to authentication (fails upward) instead of silently passing.
instruction_nullification Turn gate turngate/repeat.py, turngate/signatures.py A turn's text matched a Tier 0 pattern telling the agent to ignore, disregard, or override its previous instructions. Doberman blocks it outright if issued or found in untrusted content, and requires authentication if merely discussed in typed text.
authority_override Turn gate turngate/repeat.py, turngate/signatures.py A turn's text matched a Tier 0 authority-impersonation, jailbreak/mode-switch, or system-prompt-exfiltration pattern. Doberman blocks it outright if issued or found in untrusted content, and requires authentication if merely discussed.
secret_export Turn gate turngate/repeat.py, turngate/signatures.py A turn's text matched a Tier 0 credential, key, or token export request, for example "send me the API key." Doberman blocks it outright if issued or found in untrusted content, and requires authentication if merely discussed.
encoded_payload Turn gate turngate/repeat.py, turngate/signatures.py A turn contains a long, high-entropy base64/hex run or a punycode host, an encoded-carrier signature. Doberman blocks it in untrusted content and steps it up to authentication in typed content, since a user pasting base64 is plausibly legitimate.
indirect_injection Turn gate turngate/repeat.py, turngate/signatures.py A Tier 0 signature matched inside an untrusted, pasted or tool-fetched, content segment. Doberman treats this as indirect prompt injection and blocks it unconditionally, regardless of issue or mention framing.
embedded_instruction Turn gate turngate/heuristics.py Tier 1 heuristics detected an agent-directed imperative embedded in an untrusted content segment, escalating the turn to authentication.
persona_override Turn gate turngate/heuristics.py Tier 1 heuristics matched a persona- or role-override phrase in the turn, requiring authentication.
obfuscated_content Turn gate turngate/heuristics.py Tier 1 heuristics found obfuscated or sub-threshold encoded content that does not reach the hard encoded_payload carrier threshold, requiring authentication.
urgency_secrecy_framing Turn gate turngate/heuristics.py Tier 1 heuristics matched language that combines urgency and secrecy framing, a common social-engineering pattern, requiring authentication.
stylometric_outlier Turn gate turngate/heuristics.py The turn's writing style is an extreme statistical outlier for this session and coincides with a sensitive apparent intent, requiring authentication. Style alone never triggers this on its own.
repeat_after_block Turn gate turngate/hook.py, turngate/repeat.py An identical, same-fingerprint turn was resubmitted after being blocked. This converts the automatic block into a one-time AUTH challenge for the first repeat instead of blocking again outright.
turn_blocked_repeatedly Turn gate turngate/repeat.py A resubmitted turn's repeat challenge was already denied once, and the lockout threshold is reached. The rule hard-blocks further resubmission for a cooldown window instead of re-challenging.
egress_route_divergence Egress broker engine/rules/destinations.py A broker-observed connection for this entity, within a bounded recent window, actually went somewhere that diverges from this rule's static host-trust classification. This retroactively raises the already-computed verdict.
egress_broker_enforced Egress broker engine/rules/destinations.py A broker proven to enforce egress attests that this exact destination is allowlisted and will itself be enforced at the socket, so the rule contributes PASS instead of its usual AUTH.
egress_blocked_by_mode Egress broker engine/rules/destinations.py In Paranoid mode only, a broker proven to enforce egress attests that this exact destination is not allowlisted and will itself drop it at the socket, so the rule hard-blocks instead of its usual AUTH. This code is dormant in every other mode, and with no broker present.
anomalous_egress_velocity Egress broker engine/rules/destinations.py A bounded, in-memory per-entity velocity tracker detected a burst, volume, or fan-out anomaly across this entity's broker-observed connections in the same recent window egress_route_divergence uses. This retroactively raises the already-computed verdict.
artifact_digest_mismatch Artifact digest verification proxy/executor.py A pinned expected sha256 digest exists for a previously fetched identity, and the freshly returned tool-result content's digest disagrees with it. The result is withheld from the agent post-fetch.
correlated_trifecta Session correlator engine/correlator.py This session's recent decision history shows an untrusted-provenance ingress and a sensitive or secret read as separate earlier calls, and the current action is an external egress: a lethal trifecta assembled across calls rather than within one.
correlated_destructive_flow Session correlator engine/correlator.py This session's recent decision history shows a broad read or enumeration and a shell command as separate earlier calls, and the current action is an external egress: a possible archive-then-exfiltrate flow.
raw_socket_channel Objective guardrail engine/rules/commands.py A shell command opens a raw network channel outside the normal HTTP/tool egress path: a /dev/tcp//dev/udp redirection target, netcat/ncat/socat used in its exec-on-connect (reverse/bind-shell) form, or any openssl s_client invocation (a TLS client connection, whatever flag names the target). Detection reasons about command shape only and is deliberately narrow to keep the false-positive rate near zero. There are two tiers: the exec-on-connect subset (a socket wired to command execution, such as nc/ncat -e/--sh-exec, socat EXEC:/SYSTEM:, or an interpreter payload that opens a socket AND spawns a subprocess/shell) is an unambiguous reverse/bind shell and BLOCKs. The other shapes (/dev/tcp//dev/udp redirection, a port probe, openssl s_client, a bare inline socket) raise only to AUTH.
verification_bypass_flag Verification integrity engine/rules/commands.py A git commit command carries --no-verify, --no-gpg-sign, or the short alias -n (including combined short flags like -an), skipping the commit's pre-commit/pre-push hooks or its cryptographic signature. This is a narrower, dedicated code, never destructive_command, so it can never sit on the mode-independent hard-block floor.
test_file_removal Verification integrity engine/rules/paths.py The target of a file_delete action, or of a file_write/file_delete action whose tool name contains "rename"/"move", matches a test-file glob (test_*.py, tests/**, *.test.js, *.test.tsx, *.spec.mjs, etc.). This is another shape of an agent quietly disabling its own safety net rather than fixing what it flags. The rule is scoped to the action type: an ordinary edit of a test file, or a non-mutating action (e.g. a read) whose tool merely happens to be named "rename"/"move", stays PASS.
dependency_known_malicious Objective guardrail engine/rules/dependency_admission.py An installed package name (from a pip/npm/cargo/gem/go/... install command) exactly matches a name on Doberman's bundled known-malicious list, so the rule hard-blocks the install in every mode.
dependency_name_typosquat Objective guardrail engine/rules/dependency_admission.py An installed package name is exactly one character away (insertion, deletion, or substitution) from a name on Doberman's bundled popular-package list, and is not itself on that list, so it requires authentication before installing (a possible typosquat/slopsquat).

Notes#

  • Match on the enum name in automation. Values are stable strings, but the name is what the code actually branches on.
  • Evidence may be redacted before it reaches a log or a UI. Reason codes are the machine-stable half of explainability; the explanation string on a Decision is the human-readable half.
  • When you add a ReasonCode, update this table in the same PR. tests/unit/test_reason_codes_documented.py fails the build otherwise.
Doberman v0.18.7 · Apache-2.0 · Defense-in-depth, not airtight.

↑↓ navigate ↵ open esc close