DobermanDocs
Menu

Concepts Verdicts, risk and reason codes

Verdicts, risk and reason codes

This page covers the vocabulary a Doberman decision is made of: the verdict, the risk level, and the reason codes and explanation that justify anything short of a plain pass.

View as Markdown

Every decision resolves to one of three verdicts: PASS, AUTH, BLOCK. These aren't independent flags. They form a total order, PASS < AUTH < BLOCK, and that order is the whole safety model in one sentence: nothing in the decision path is allowed to move a verdict down it. combine() takes the higher of two verdicts every time it merges guardrail results; a guardrail can escalate an action another guardrail passed, never quietly wave through one that another flagged.

Risk is a separate, parallel scale, low, medium, high, critical, carried on both the individual guardrail result and the normalized action, and raised the same way the verdict is: by taking the max. Risk isn't the whole picture of how dangerous an action is, though. Two more dimensions travel alongside it: reversibility (low/medium/high) describes how easily the action's effects can be undone, and blast radius (single/few/many/mass/unknown), part of the action's algebra classification, describes how many entities it touches. The subjective layer weighs these when it decides whether an action deserves a step-up.

A GuardrailResult is what one guardrail hands back for one action: a verdict, a risk level, a list of reason codes, and a one-line explanation. It's immutable, and it's validated the moment it's built: any verdict other than PASS is rejected unless it carries at least one reason code and a non-empty explanation. There's no code path where an AUTH or BLOCK reaches you unexplained.

Reason codes are shared constants, not free text: secret_exfiltration, destructive_command, unusual_for_deployment, lethal_trifecta, 57 in total, each snake_case, each documented with the module that raises it and what it means. See the full reason codes catalogue. The explanation string attached to a code is written to describe the rule, not the payload: "path is under a protected directory," never the path itself, and never the matched secret or command. That is a hard rule for anyone writing a guardrail.

Paths get one more layer of care before any rule compares them. A single canonicalize helper resolves ., .., and symlinks, and confines the result to the repo root. Every path-matching rule in Doberman goes through it, so there's no second comparison path a .. or a symlink could slip past.

flowchart TB
  SO["SecurityObject<br/><i>normalized action</i>"]
  AT["ActionType<br/>file_write · shell_exec ·<br/>network_request · …"]
  RK["Risk<br/>score + BlastRadius +<br/>Reversibility"]
  EC["EvalContext<br/>repo root · role · mode ·<br/>session metadata"]
  GR["GuardrailResult<br/>verdict + reason_codes +<br/>explanation"]
  V["Verdict<br/>PASS < AUTH < BLOCK<br/><i>total order</i>"]
  RC["ReasonCode<br/>shared constants<br/>(57 documented)"]
  D["Decision<br/>final record, redacted"]
  TO["TurnObject<br/>(turn gate input)"]
  AL["Algebra<br/>action classification"]
  SO --- AT
  SO --- RK
  SO --- AL
  GR --- V
  GR --- RC
  EC -- "context for" --> GR
  GR -- "combined into" --> D
  TO -. "parallel vocabulary<br/>for turns" .- SO
  classDef god fill:#a97e4f,color:#fff,stroke:#a97e4f
  class SO,V god

SecurityObject and Verdict are the most-connected symbols in the codebase; a change to either is a whole-system change.

Doberman v0.18.3 · Apache-2.0 · Defense-in-depth, not airtight.

navigate open esc close