Approvals & TOTP Second Factor

Human-in-the-loop approvals let you keep an agent on a short leash: when it asks to run a sensitive tool, the request is held until you accept or reject it. The TOTP second factor adds a time-based one-time password check to high-risk actions so that a stolen API token alone is not enough to cause damage.

Included Topics


Approval Policy

Config: [approval] section in ~/.librefang/config.toml. Source: librefang-types/src/approval.rs (ApprovalPolicy).

[approval]
# Tools that always require approval. `true` = default set, `false` = disable all gating.
require_approval = ["shell_exec", "file_write", "file_delete", "apply_patch"]
timeout_secs = 60                      # 10..=300
auto_approve_autonomous = false        # auto-approve when agent runs unattended
auto_approve = false                   # clears the require list at boot
trusted_senders = []                   # user IDs that bypass the gate

# Second factor
second_factor = "none"                 # "none" | "totp" | "login" | "both"
totp_issuer = "LibreFang"              # shown in authenticator apps
totp_grace_period_secs = 30            # skip TOTP for follow-ups within this window
totp_tools = []                        # empty = all gated tools require TOTP

Second-factor scope

ValueTOTP required for approvalsTOTP required for dashboard login
none
totpyes
loginyes
bothyesyes

totp_tools accepts glob patterns (shell_*) — useful to gate only destructive tools with a second factor while leaving read-only tools on the simple approval gate.

Timeout fallback

When an approval times out, the action falls through to timeout_fallback:

  • reject (default) — agent receives a rejection and halts.
  • allow — action proceeds. Only safe in fully-trusted autonomous setups.
  • retry — re-requests approval once more.

Enabling TOTP

The TOTP flow is entirely UI-driven in the dashboard, but the underlying endpoints work from any client.

  1. Open Settings → Security → Second Factor.
  2. Click Enable TOTP. The server calls POST /api/approvals/totp/setup which generates a random base32 secret, stores it encrypted in the vault, and returns an otpauth:// URI plus a PNG QR code.
  3. Scan the QR in Google Authenticator, 1Password, Authy, or any RFC 6238 app.
  4. Important: copy the 10 recovery codes that appear once. They are shown only at enrollment and are the only way back in if you lose the device.
  5. Enter a live 6-digit code to confirm enrollment (POST /api/approvals/totp/confirm). The secret becomes active only after a successful confirmation.
  6. Flip second_factor in config.toml from none to totp, login, or both and restart the daemon — or just set it through Settings.

Until step 5 succeeds, the pending secret is inactive and can be discarded by calling setup again.


Dashboard Flow

The Approvals page (/approvals) shows three tabs:

  • Pending — live list of waiting requests with the agent, tool name, risk level, and full payload. Click an entry to see the structured arguments the agent intends to pass.
  • Audit — paginated audit log with decision, decider, and whether a TOTP code was used (second_factor_used = 1).
  • TOTP — enrollment/revocation UI with the QR code, recovery codes, and a "regenerate codes" action.

When TOTP is enforced, the approval card shows a 6-digit input. The Batch Approve button is automatically disabled (approvals.batch_disabled_totp) to prevent mass approval with a single stolen code.

Modify & Retry. Rejected tool calls can be edited and re-run from the approvals card. Useful when an agent asks for almost the right thing but needs a small parameter change (e.g. a different file path).


API Reference

All endpoints live under /api/approvals.

EndpointMethodPurpose
/api/approvalsGETList pending requests (add ?audit=1 for the log)
/api/approvals/{id}/approvePOSTApprove (body { "totp_code": "123456" } when enforced)
/api/approvals/{id}/rejectPOSTReject with optional feedback
/api/approvals/totp/setupPOSTGenerate secret, QR code, and recovery codes
/api/approvals/totp/confirmPOSTConfirm enrollment with a 6-digit code
/api/approvals/totp/statusGET{ enrolled, confirmed, enforced, remaining_recovery_codes }
/api/approvals/totpDELETERevoke enrollment (requires a valid TOTP or recovery code)

Approving with TOTP enforced:

curl -X POST "http://127.0.0.1:4545/api/approvals/${ID}/approve" \
  -H "Content-Type: application/json" \
  -d '{"totp_code": "123456"}'

A successful verification starts a totp_grace_period_secs window during which subsequent approvals on the same session skip the TOTP check. This balances safety with usability during rapid iteration.


Session-Scoped Batch Resolution

Source: librefang-api/src/routes/approvals.rs

When multiple agents run concurrently, each session may accumulate several pending approval requests at once. The global /api/approvals endpoint operates across all pending requests regardless of which session created them. The session-scoped endpoints let you narrow the scope to a single session, making it practical to review and resolve an entire agent's pending queue without accidentally approving requests from unrelated sessions.

New Endpoints

EndpointMethodPurpose
/api/approvals/session/:session_idGETList all pending approval requests belonging to session_id
/api/approvals/session/:session_id/approve_allPOSTApprove every pending request in the session (body: { "totp_code": "…" } when TOTP is enforced)
/api/approvals/session/:session_id/reject_allPOSTReject every pending request in the session with an optional { "reason": "…" }

Example — approve everything queued for a session:

SESSION_ID="01J3WXYZ..."

curl -X POST \
  "http://127.0.0.1:4545/api/approvals/session/${SESSION_ID}/approve_all" \
  -H "Content-Type: application/json" \
  -d '{"totp_code": "123456"}'

Example — list pending requests for a session:

curl "http://127.0.0.1:4545/api/approvals/session/${SESSION_ID}"

Difference from /api/approvals/all

The existing GET /api/approvals endpoint (with no additional path segments) returns all pending requests across every active session and agent. If you batch-approve using that list, you act on requests that may belong to entirely different agents running different tasks.

The session-scoped endpoints operate only on requests whose session_id field matches the path parameter, giving you fine-grained control when multiple concurrent agents are all waiting for human input simultaneously.

session_id Field on Approval Requests

session_id is an optional field on ApprovalRequest. Approval requests created by code paths that predate the session-scoped feature do not carry a session_id and will never appear in the session-filtered list. Those older requests remain fully accessible through the existing per-UUID endpoints:

# Still works for any approval regardless of session_id
curl -X POST "http://127.0.0.1:4545/api/approvals/${APPROVAL_UUID}/approve" \
  -H "Content-Type: application/json" \
  -d '{"totp_code": "123456"}'

Recovery Codes

Ten single-use codes are generated at enrollment and stored in the vault as totp_recovery_codes. Each code:

  • is 10 alphanumeric characters, random;
  • is consumed on use (removed from the vault);
  • can be used in place of a TOTP code anywhere the endpoint accepts totp_code — including the DELETE /api/approvals/totp revocation flow.

The GET /api/approvals/totp/status response exposes remaining_recovery_codes so the dashboard can warn you before they run out. If they do, revoke and re-enroll — this regenerates a fresh set.


Hardening Checklist

  1. Set second_factor = "both" on anything exposed to the network.
  2. Keep require_approval narrow but honest — list every tool that writes to disk, executes shell, or hits a mutating API.
  3. Configure trusted_senders only for first-party automation, never for shared webhook IDs.
  4. Use totp_tools to require the second factor on shell_exec, file_delete, and apply_patch even when the broader approval list is wider — a compromised first factor then still cannot run destructive tools.
  5. Prefer timeout_fallback = "reject" over "allow" in production.
  6. Store recovery codes in a password manager, not in the same place as the API token. If both leak, TOTP adds nothing.
  7. Rotate the vault key (LIBREFANG_VAULT_KEY, 32 raw bytes) on suspected compromise — this invalidates the stored TOTP secret and forces re-enrollment.

See also: Security Operations for loop-guard and session-repair guardrails, and the Communication API reference for the full approval request/response schema.