# Auth: the human in the loop

This page explains what happens after Doberman returns an `AUTH` verdict: how the challenge reaches you, what counts as approval, and why every failure mode denies.

An `AUTH` verdict means the engine wants a human decision before the tool call proceeds, and the agent waits until that decision arrives. `run_auth_challenge` runs the challenge on a deadline thread with a wall-clock timeout, so a channel that never responds cannot hang the agent: the challenge times out and denies.

## The prompter chain

Doberman tries to reach you through whichever channel is available, in order: the local dashboard first (only while its heartbeat is fresh), then MCP elicitation (when the calling agent supports it), then a GUI dialog, then your terminal. A channel that reports itself unavailable, times out, or goes unanswered hands off to the next one; nothing is skipped silently.

> **Fail closed** If every channel is exhausted, the verdict is a deny. There is no default-allow fallback. A prompt you never saw is treated the same as a prompt you refused: the action does not run.

The log tells these outcomes apart even though they all deny. A timeout, a channel error (the GUI couldn't open, the dashboard wasn't running), and an explicit human refusal are three different reason codes, so `doberman log` and `doberman tui` show you which one happened instead of a single undifferentiated "denied."

## Approval scope

An approval is single-use and bound to the specific action id it was issued for. Approving one `AUTH` prompt does not pre-approve a similar action later in the same session, and a captured approval cannot be replayed against a different action.

TOTP-backed elevation works the same way as the drift gate's possession factor (see [Policy and the drift gate](https://docs.trydoberman.dev/concepts/policy-and-drift/)): it is narrow in scope and time-limited, and `doberman revoke <elevation-id>` ends it early. Elevation can widen what a role is allowed to do within those bounds. It can never relax a hard block. The hard floors (secret exfiltration, destructive commands, control-plane self-protection) stay enforced no matter what elevation is active.

```mermaid
flowchart LR
  E2["engine: AUTH"] --> CH2["challenge.py<br/>run_auth_challenge<br/>(wall-clock deadline thread)"]
  CH2 --> PV["provider.py<br/>LocalAuthProvider"]
  PV --> FB["FallbackPrompter"]
  FB -- "1" --> G2["GuiPrompter<br/>(tkinter)"]
  FB -- "2" --> T2["TtyPrompter"]
  FB -- "3" --> C2["CliPrompter"]
  EL["elicitation_prompter<br/>(MCP-native)"] -.-> FB
  DP["dashboard_prompter<br/>(dash pending queue)"] -.-> FB
  G2 & T2 & C2 -- "PrompterUnavailableError" --> FB
  FB -- "all channels exhausted" --> DENY2["deny (fail closed)"]
  FB -- "human answers" --> RES["approve / deny<br/>single-use · action-bound"]
  TOTP["totp.py — 2FA<br/>elevation.py — narrow, temporary"] --- RES
  classDef block fill:#a83a32,color:#fff,stroke:#a83a32
  class DENY2 block
```

Timeout, channel error, and refusal all deny, but the log keeps them distinct so you can tell "nobody was there" from "somebody said no."

You enroll a possession factor with `doberman 2fa setup` (TOTP) or `doberman password set`. Until one is enrolled, every attempt to weaken policy fails closed.
