Full-codebase-audit 2026-05-30 outputs: - Audit report: 09 - Audits/Full Codebase Audit - 2026-05-30.md - 81 issue files ISSUE-055..135 (decisions + 1 skipped no-brainer). - Scanner docs from scratch (was zero): architecture, data model, API ref, payment flow, operations runbook + repo README. - Doc-sync updates across API reference, data models, flows, design system. - Secret Rotation Runbook (08 - Operations) for the exposed credentials. - Reusable workflow guide (07 - Development) + .claude/workflows/full-codebase-audit.js. Issues remain status:open intentionally — the code fixes are uncommitted-then-committed working-tree changes per repo and aren't "resolved" until merged/deployed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14 KiB
UAT — Trezor Safekeeping (Task #11)
Feature: Hardware-protected admin release/refund actions via Trezor signing
Branch: integrate-main-into-development
Backend version: 2.6.59
Tester role required: Admin
Browser requirement: Chromium-based only (Chrome, Edge, Brave) — WebUSB is not supported in Firefox or Safari
Overview
Task #11 adds a hardware security layer over two critical admin actions:
| Action | Without Trezor | With Trezor |
|---|---|---|
| Release payment to seller | Admin clicks → tx hash → confirm | Admin clicks → Trezor signs → tx hash → confirm |
| Refund payment to buyer | Admin clicks → tx hash → confirm | Admin clicks → Trezor signs → tx hash → confirm |
The Trezor requirement is controlled by the backend env var TREZOR_SAFEKEEPING_REQUIRED. When false, the entire Trezor layer is transparent and the UI behaves exactly as before.
An emergency break-glass mechanism lets an admin temporarily bypass Trezor for 1 hour with a mandatory Telegram alarm.
Flows Implemented
Flow A — Trezor Registration (one-time setup)
URL: /dashboard/admin/trezor
When: First time, or when replacing the registered device.
Steps:
- Admin opens the Trezor Safekeeping page.
- (Optional) Admin enters a device label in the text field.
- Admin clicks Connect Trezor.
- Browser shows a USB device picker — admin selects the Trezor.
- Trezor device shows "Export public key?" — admin confirms on device.
- Backend issues a registration message tied to the xpub + address.
- UI moves to Sign step — admin clicks Sign on Trezor.
- Trezor device shows the message to sign — admin confirms on device.
- UI shows "Signature obtained — ready to register."
- Admin clicks Register Trezor → backend stores xpub fingerprint + registration address.
- Page refreshes and shows the registered account status (address, fingerprint, path, address count).
If a Trezor is already registered, the page shows a Re-register section instead of the initial registration section. The flow is identical; it overwrites the old record.
Flow B — Release Payment (with Trezor)
URL: /dashboard/admin/payments-awaiting-confirmation
Precondition: TREZOR_SAFEKEEPING_REQUIRED=true, break-glass inactive, Trezor registered.
Steps:
- Admin opens the Payments Awaiting Confirmation list.
- Clicks the green Release button on a payment row.
- Dialog opens at step 1 — Build Instruction.
- Shows payment ID and amount.
- Admin clicks Build Instruction.
- Backend builds the release instruction (validates payment state).
- Dialog advances to step 2 — Sign with Trezor.
- Shows provider and network info from the instruction.
- Admin connects Trezor (if not already connected) and clicks Sign on Trezor.
- Trezor device shows the operation message — admin confirms on device.
- Dialog advances to step 3 — Enter tx hash.
- Shows "Trezor approved — signer: 0x…" confirmation.
- Admin pastes the on-chain transaction hash of the actual transfer.
- Admin clicks Next.
- Dialog advances to step 4 — Confirm.
- Admin clicks Confirm Release (green).
- Backend verifies the Trezor signature against the registered xpub, records the tx hash, and marks the payment released.
- Dialog closes, list refreshes.
Flow C — Refund Payment (with Trezor)
URL: /dashboard/admin/payments-awaiting-confirmation
Precondition: Same as Flow B.
Identical to Flow B except:
- Admin clicks the orange Refund button.
- Step 1 shows an optional Refund reason text field.
- Final button is Confirm Refund (orange).
reasonis passed through to the confirm API if provided.
Flow D — Release / Refund without Trezor (break-glass active or safekeeping disabled)
Precondition: Either TREZOR_SAFEKEEPING_REQUIRED=false OR break-glass is active.
The dialog runs a shorter 3-step flow:
| Step | Label | Action |
|---|---|---|
| 1 | Build instruction | Same as above |
| 2 | Enter tx hash | No Trezor prompt; shows "Break-glass mode active" notice |
| 3 | Confirm | Submits without trezor field in body |
The backend accepts the request because isTrezorSafekeepingRequired() returns false in both cases.
Flow E — Break-glass Activation
URL: /dashboard/admin/trezor
When: Emergency — Trezor is unavailable, lost, or broken.
Steps:
- Admin opens the Trezor Safekeeping page.
- The Break-glass section shows "Trezor safekeeping is active (normal mode)".
- Admin clicks Activate Break-glass.
- Confirmation dialog appears with warning text.
- Admin clicks Activate.
- Backend activates break-glass for 1 hour and fires a Telegram alarm to the monitoring bot (
TG_NOTIFY_BOT_TOKEN). - Page shows red alert: "Break-glass is ACTIVE — Activated by: [email] — Expires: [time] — Remaining: ~60 min".
- The payments-awaiting-confirmation list now passes
skipTrezor={true}to the dialog.
Flow F — Break-glass Deactivation
URL: /dashboard/admin/trezor
Steps:
- Admin opens the Trezor Safekeeping page while break-glass is active.
- The section shows the red active alert with remaining time.
- Admin clicks Deactivate Break-glass.
- Confirmation dialog appears.
- Admin clicks Deactivate.
- Backend clears break-glass immediately and fires a Telegram "restored" notification.
- Page shows green "Trezor safekeeping is active (normal mode)".
Flow G — Break-glass auto-expiry
No UI action required.
- After 1 hour,
isBreakGlassActive()on the backend returnsfalseautomatically. - On the next page load or dialog open,
getBreakGlassStatus()returnsactive: false. skipTrezorreverts tofalseon the payments list.
API Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/api/trezor/account |
Get registered Trezor status |
GET |
/api/trezor/registration-message |
Get message to sign for registration |
POST |
/api/trezor/register |
Submit registration (xpub + proof signature) |
POST |
/api/trezor/operation-message |
Get message to sign for a release/refund operation |
POST |
/api/payment/:id/release |
Build release instruction |
POST |
/api/payment/:id/release/confirm |
Confirm release with tx hash (+ optional trezor sig) |
POST |
/api/payment/:id/refund |
Build refund instruction |
POST |
/api/payment/:id/refund/confirm |
Confirm refund with tx hash (+ optional trezor sig) |
GET |
/api/admin/settings/break-glass |
Get break-glass status |
POST |
/api/admin/settings/break-glass |
Activate break-glass |
DELETE |
/api/admin/settings/break-glass |
Deactivate break-glass |
Acceptance Tests
AT-01 — Page loads and shows unregistered state
Precondition: No Trezor registered in DB.
Steps: Navigate to /dashboard/admin/trezor as admin.
Expected:
- "No Trezor registered — admin actions are unprotected" warning is shown.
- "Register Trezor" card with device label field and Connect Trezor button is visible.
- Break-glass card shows "Trezor safekeeping is active (normal mode)" in green.
AT-02 — Registration flow completes (hardware path)
Precondition: Trezor Model One or T connected via USB. Chromium browser.
Steps: Follow Flow A above.
Expected:
- USB device picker appears after clicking Connect Trezor.
- Trezor device shows public key export prompt.
- After approval, page moves to Sign step and shows the registration message.
- Trezor device shows the message to sign.
- After signing, Submit step shows "Signature obtained".
- After Submit, page shows account status card with green "Trezor registered" alert.
- Chips show correct registration address (matches what was shown on device), fingerprint, path
m/44'/60'/0', and address count0.
AT-03 — Registration rejected on device
Precondition: Trezor connected.
Steps: Click Connect Trezor → reject the export on device.
Expected:
- Toast shows "Trezor getPublicKey failed: …" or similar error.
- Registration flow resets to idle (Connect button visible again).
AT-04 — Release without Trezor (safekeeping disabled)
Precondition: Backend TREZOR_SAFEKEEPING_REQUIRED=false.
Steps:
- Go to
/dashboard/admin/payments-awaiting-confirmation. - Click Release on any payment with status awaiting confirmation.
Expected:
- Dialog opens with 3-step stepper (Build instruction / Enter tx hash / Confirm).
- Build Instruction succeeds without Trezor prompt.
- "Break-glass mode active — Trezor signature not required." notice shown in step 1.
- Admin enters a dummy tx hash and clicks Next → Confirm.
POST /api/payment/:id/release/confirmcalled withtxHashbut withouttrezorfield.- Success toast, dialog closes, list refreshes.
AT-05 — Release with Trezor (full hardware path)
Precondition: TREZOR_SAFEKEEPING_REQUIRED=true, Trezor registered, break-glass inactive, Trezor device connected.
Steps: Follow Flow B above.
Expected:
- Dialog shows 4-step stepper.
- Sign with Trezor step shows provider/network info from instruction.
- Trezor device prompts to sign the operation message.
- After signing, tx hash step shows "Trezor approved — signer: 0x…".
- Confirm call includes
trezor: { message, signature }in body. - Backend returns 200. Toast: "Release confirmed".
AT-06 — Release blocked when Trezor required but not signed
Precondition: TREZOR_SAFEKEEPING_REQUIRED=true, Trezor registered, break-glass inactive.
Steps: POST directly to /api/payment/:id/release/confirm with only txHash, no trezor field.
Expected:
- Backend returns 403 or 400 with message indicating Trezor signature is required.
AT-07 — Refund with reason
Precondition: TREZOR_SAFEKEEPING_REQUIRED=false (for UI simplicity).
Steps: Follow Flow C. Enter "Item not received" in the reason field.
Expected:
- Reason field visible in Build Instruction step.
POST /api/payment/:id/refund/confirmincludesreason: "Item not received".- Toast: "Refund confirmed".
AT-08 — Break-glass activation fires Telegram alarm
Precondition: Backend TG_NOTIFY_BOT_TOKEN and TG_NOTIFY_CHAT_ID set.
Steps: Follow Flow E.
Expected:
- Confirmation dialog warns about 1-hour bypass and Telegram alarm.
- After clicking Activate, page shows red break-glass active alert.
- Telegram monitoring chat receives alarm message from the notify bot (NOT the mini app bot).
expiresAtshown is approximately 1 hour from now.remainingMsshown is approximately 60 min.
AT-09 — Break-glass makes Trezor step disappear in release dialog
Precondition: TREZOR_SAFEKEEPING_REQUIRED=true, break-glass active.
Steps:
- Confirm break-glass is active on Trezor page.
- Go to payments-awaiting-confirmation.
- Click Release on any payment.
Expected:
- Dialog shows 3-step stepper (Build instruction / Enter tx hash / Confirm).
- No "Sign with Trezor" step visible.
- Step 1 shows "Break-glass mode active — Trezor signature not required."
- Release completes without Trezor device.
AT-10 — Break-glass deactivation restores Trezor step
Precondition: Break-glass currently active.
Steps:
- Go to Trezor page and click Deactivate Break-glass → confirm.
- Go to payments-awaiting-confirmation and click Release.
Expected:
- Trezor page shows green "Trezor safekeeping is active (normal mode)".
- Telegram monitoring chat receives "break-glass restored" notification.
- Release dialog shows 4-step stepper again.
AT-11 — Break-glass expires automatically
Precondition: Break-glass activated.
Steps: Wait 1 hour (or manually set BREAK_GLASS_DURATION_MS to a short value in breakGlassRoutes.ts for testing).
Expected:
- After expiry,
GET /api/admin/settings/break-glassreturnsactive: false. - Page refresh shows normal mode.
- Release dialog shows 4-step stepper.
AT-12 — Non-admin cannot access Trezor endpoints
Precondition: Logged in as buyer or seller.
Steps: Call GET /api/trezor/account and GET /api/admin/settings/break-glass with non-admin JWT.
Expected:
- Both return 403.
AT-13 — Re-registration overwrites old device
Precondition: Trezor already registered.
Steps:
- Note the current registration address on the Trezor page.
- Click Register New Device, complete registration flow with same or different Trezor.
Expected:
- Page shows updated registration address after re-registration.
- If different device: new fingerprint, new address.
- Previous Trezor's signatures are no longer accepted on subsequent releases.
AT-14 — Cancel at any step resets dialog
Precondition: Any.
Steps: Open release dialog, complete Build Instruction step, click Cancel.
Expected:
- Dialog closes with no action taken.
- Reopening the dialog starts at step 1 with empty state.
Test Environment Setup
Minimal (no hardware — UI flow only)
# backend/.env
TREZOR_SAFEKEEPING_REQUIRED=false
Covers: AT-01, AT-04, AT-07, AT-08 to AT-10, AT-12, AT-14
With safekeeping enforced (no hardware — break-glass path)
# backend/.env
TREZOR_SAFEKEEPING_REQUIRED=true
- Go to
/dashboard/admin/trezor - Activate break-glass
- Now release/refund works without Trezor
Covers: AT-09, AT-10
Full hardware path
Requirements:
- Trezor Model One or Model T
- USB cable
- Chromium browser (Chrome / Edge / Brave)
TREZOR_SAFEKEEPING_REQUIRED=true- Frontend served on
https://orlocalhost(WebUSB blocks non-secure production origins)
Covers: AT-02, AT-03, AT-05, AT-06, AT-13
Known Limitations / Out of Scope
| Item | Status |
|---|---|
| Firefox / Safari WebUSB support | Not supported — Trezor registration and signing only works in Chromium |
| Multiple Trezor accounts | Only one active registration at a time; re-register to replace |
| Break-glass persistence across backend restarts | Intentionally not persisted — restart clears break-glass as a security property |
| Payout / sweep actions via Trezor | Backend assertTrezorSignatureForOperation() is wired; no UI yet |
| Audit log of Trezor operations | Not implemented in this task |