audit: 2026-05-30 full-codebase audit — report, issues, docs, runbooks
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>
This commit is contained in:
387
UAT - Trezor Safekeeping (Task #11).md
Normal file
387
UAT - Trezor Safekeeping (Task #11).md
Normal file
@@ -0,0 +1,387 @@
|
||||
# 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:
|
||||
1. Admin opens the Trezor Safekeeping page.
|
||||
2. (Optional) Admin enters a device label in the text field.
|
||||
3. Admin clicks **Connect Trezor**.
|
||||
4. Browser shows a USB device picker — admin selects the Trezor.
|
||||
5. Trezor device shows "Export public key?" — admin confirms on device.
|
||||
6. Backend issues a registration message tied to the xpub + address.
|
||||
7. UI moves to **Sign** step — admin clicks **Sign on Trezor**.
|
||||
8. Trezor device shows the message to sign — admin confirms on device.
|
||||
9. UI shows "Signature obtained — ready to register."
|
||||
10. Admin clicks **Register Trezor** → backend stores xpub fingerprint + registration address.
|
||||
11. 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:
|
||||
1. Admin opens the Payments Awaiting Confirmation list.
|
||||
2. Clicks the green **Release** button on a payment row.
|
||||
3. Dialog opens at step 1 — **Build Instruction**.
|
||||
- Shows payment ID and amount.
|
||||
- Admin clicks **Build Instruction**.
|
||||
4. Backend builds the release instruction (validates payment state).
|
||||
5. 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**.
|
||||
6. Trezor device shows the operation message — admin confirms on device.
|
||||
7. 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**.
|
||||
8. Dialog advances to step 4 — **Confirm**.
|
||||
- Admin clicks **Confirm Release** (green).
|
||||
9. Backend verifies the Trezor signature against the registered xpub, records the tx hash, and marks the payment released.
|
||||
10. 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).
|
||||
- `reason` is 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:
|
||||
1. Admin opens the Trezor Safekeeping page.
|
||||
2. The Break-glass section shows "Trezor safekeeping is active (normal mode)".
|
||||
3. Admin clicks **Activate Break-glass**.
|
||||
4. Confirmation dialog appears with warning text.
|
||||
5. Admin clicks **Activate**.
|
||||
6. Backend activates break-glass for 1 hour and fires a Telegram alarm to the monitoring bot (`TG_NOTIFY_BOT_TOKEN`).
|
||||
7. Page shows red alert: "Break-glass is ACTIVE — Activated by: [email] — Expires: [time] — Remaining: ~60 min".
|
||||
8. The payments-awaiting-confirmation list now passes `skipTrezor={true}` to the dialog.
|
||||
|
||||
---
|
||||
|
||||
### Flow F — Break-glass Deactivation
|
||||
|
||||
**URL:** `/dashboard/admin/trezor`
|
||||
|
||||
Steps:
|
||||
1. Admin opens the Trezor Safekeeping page while break-glass is active.
|
||||
2. The section shows the red active alert with remaining time.
|
||||
3. Admin clicks **Deactivate Break-glass**.
|
||||
4. Confirmation dialog appears.
|
||||
5. Admin clicks **Deactivate**.
|
||||
6. Backend clears break-glass immediately and fires a Telegram "restored" notification.
|
||||
7. 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 returns `false` automatically.
|
||||
- On the next page load or dialog open, `getBreakGlassStatus()` returns `active: false`.
|
||||
- `skipTrezor` reverts to `false` on 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 count `0`.
|
||||
|
||||
---
|
||||
|
||||
### 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:**
|
||||
1. Go to `/dashboard/admin/payments-awaiting-confirmation`.
|
||||
2. 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/confirm` called with `txHash` but without `trezor` field.
|
||||
- [ ] 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/confirm` includes `reason: "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).
|
||||
- [ ] `expiresAt` shown is approximately 1 hour from now.
|
||||
- [ ] `remainingMs` shown is approximately 60 min.
|
||||
|
||||
---
|
||||
|
||||
### AT-09 — Break-glass makes Trezor step disappear in release dialog
|
||||
|
||||
**Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, break-glass active.
|
||||
**Steps:**
|
||||
1. Confirm break-glass is active on Trezor page.
|
||||
2. Go to payments-awaiting-confirmation.
|
||||
3. 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:**
|
||||
1. Go to Trezor page and click Deactivate Break-glass → confirm.
|
||||
2. 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-glass` returns `active: 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:**
|
||||
1. Note the current registration address on the Trezor page.
|
||||
2. 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)
|
||||
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```bash
|
||||
# backend/.env
|
||||
TREZOR_SAFEKEEPING_REQUIRED=true
|
||||
```
|
||||
|
||||
1. Go to `/dashboard/admin/trezor`
|
||||
2. Activate break-glass
|
||||
3. 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://` or `localhost` (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 |
|
||||
Reference in New Issue
Block a user