Threat reference
T7 (Full-system compromise via admin-settings tampering) — see docs/THREAT_MODEL.md §4.
Context
Carved out of #355. The original T7 mitigation (§8 mitigation #15) included an optional change-approval workflow for the highest-risk fields. This issue covers that piece only; step-up + in-band audit lives in re-scoped #355, and out-of-band alerting lives in its own follow-up.
Why this is a separate ticket
Step-up auth (#355) raises the bar for a single compromised admin session, but a determined insider can still satisfy step-up. Dual-approval is the next layer: for the highest-blast-radius fields, require two admins to approve via a pending-changes queue.
The previous triage on #355 (comment 2026-05-02) flagged this as L effort needing PM input: it requires a new pending-changes table, new pending-state semantics, and a UI surface in tmi-ux for the approval queue.
Scope
- Define the field-sensitivity classification: which
/admin/* writes require dual-approval (proposed: provider configs, SSRF allowlist, JWT key rotation).
- New
pending_admin_changes table: id, requester_uuid, field_path, proposed_value (encrypted as today), created_at, expires_at, status, approver_uuid, approved_at.
- New endpoints:
POST /admin/pending_changes — queue a change (called by the rewritten admin write handler when sensitivity = high)
GET /admin/pending_changes — list (admin)
POST /admin/pending_changes/{id}/approve — second admin approves; on approval, the change is applied and the in-band + OOB audit fire
POST /admin/pending_changes/{id}/reject
- Self-approval prevention: requester cannot approve.
- Expiry: pending changes auto-expire (e.g., 7 days) and are pruned.
- tmi-ux: pending-changes queue view; this needs a separate client ticket.
Acceptance criteria
Cross-references
Threat reference
T7 (Full-system compromise via admin-settings tampering) — see docs/THREAT_MODEL.md §4.
Context
Carved out of #355. The original T7 mitigation (§8 mitigation #15) included an optional change-approval workflow for the highest-risk fields. This issue covers that piece only; step-up + in-band audit lives in re-scoped #355, and out-of-band alerting lives in its own follow-up.
Why this is a separate ticket
Step-up auth (#355) raises the bar for a single compromised admin session, but a determined insider can still satisfy step-up. Dual-approval is the next layer: for the highest-blast-radius fields, require two admins to approve via a pending-changes queue.
The previous triage on #355 (comment 2026-05-02) flagged this as L effort needing PM input: it requires a new pending-changes table, new pending-state semantics, and a UI surface in tmi-ux for the approval queue.
Scope
/admin/*writes require dual-approval (proposed: provider configs, SSRF allowlist, JWT key rotation).pending_admin_changestable: id, requester_uuid, field_path, proposed_value (encrypted as today), created_at, expires_at, status, approver_uuid, approved_at.POST /admin/pending_changes— queue a change (called by the rewritten admin write handler when sensitivity = high)GET /admin/pending_changes— list (admin)POST /admin/pending_changes/{id}/approve— second admin approves; on approval, the change is applied and the in-band + OOB audit firePOST /admin/pending_changes/{id}/rejectAcceptance criteria
/admin/settings/oauth.providers.*does not apply immediately; returns 202 with a pending-change ID.Cross-references