docs: align flow docs with code reality + create 35 implementation issue files
Flow docs updated (11 files): - Delivery Confirmation: reversed actor roles (buyer generates, seller verifies), fixed endpoint paths (/delivery-code/generate, /delivery-code/verify) - Passkey (WebAuthn): removed stub/simulated-key claims; real @simplewebauthn/server attestation is implemented; refresh tokens are persisted - Dispute: corrected resolve schema (action enum), removed non-existent statuses, documented security gaps (no role guards on status/resolve/assign), route shadowing, all socket events are TODO stubs - Seller Offer: corrected all endpoint paths, removed 'active' status, documented withdraw dead code, missing seller history page, select-offer notification gap - Notification: corrected mark-all-read method+path, fixed GET /:id broken lookup, added unread-count-update socket event - Authentication: corrected rate limiter (counts all attempts), axios 403 not handled, deleteAccount wrong endpoint bug, changePassword no UI - Password Reset: corrected 6-digit code (not 8), documented no-complexity gap on reset-with-code vs token reset - Payment Flow DePay: /create→/save, removed phantom sub-routes, SIM_ bypass risk, PaymentProvider type gap, getProviderIntentEndpoint routing bug - Payment Flow SHKeeper: removed phantom polling endpoint, fixed release/refund paths - Purchase Request: added pending_payment/active statuses, fixed sellers/attachments endpoints, corrected socket events, PUT→PATCH bug - Escrow: documented dispute resolve does not touch escrow, route shadowing, confirm-delivery auth gap Issues created (35 files in Issues/): - 9 security issues (critical) including: dispute privilege escalation ×4, unauthenticated payment/scanner endpoints ×2, SIM_ production bypass, confirm-delivery ownership gap - 26 additional major/critical bugs covering broken endpoints, missing features, data integrity gaps, and frontend-backend mismatches Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,9 @@ related_models: ["[[Payment]]", "[[PurchaseRequest]]", "[[Funds Ledger and Escro
|
||||
related_apis: ["POST /api/payment/:id/release", "POST /api/payment/:id/refund", "POST /api/payment/:id/release/confirm", "POST /api/payment/:id/refund/confirm"]
|
||||
---
|
||||
|
||||
> [!warning] Audit — 2026-05-29
|
||||
> This document was corrected against the live codebase. Key changes: `POST /api/disputes/:id/resolve` clarified as Dispute-document-only — it does NOT move escrow funds; route shadowing between the two dispute routers documented; `confirm-delivery` authorization gap flagged.
|
||||
|
||||
# Escrow Flow
|
||||
|
||||
The current escrow is a **hybrid custody system**, not a custom Solidity escrow contract.
|
||||
@@ -133,6 +136,27 @@ Remaining alignment work:
|
||||
- ensure every release/refund path calls the same policy service,
|
||||
- record immutable audit entries for dispute resolution and custody execution.
|
||||
|
||||
### 6. Dispute Resolution and Escrow Funds
|
||||
|
||||
> [!warning] Two different handlers share the same path — they do different things
|
||||
>
|
||||
> There are **two dispute routers** both mounted at `/api/disputes`. This creates route shadowing:
|
||||
>
|
||||
> | Handler | What it does |
|
||||
> |---|---|
|
||||
> | Dashboard dispute router: `POST /api/disputes/:id/resolve` | Updates the **Dispute document only** — changes dispute status, records resolution notes, etc. **Does NOT touch escrow funds.** |
|
||||
> | releaseHold router: `POST /api/disputes/:purchaseRequestId/resolve` | Unblocks escrow — removes the dispute hold from the `Payment` and `PurchaseRequest`, making the escrow eligible for release or refund. |
|
||||
>
|
||||
> Because the dashboard router is mounted first, a `POST /api/disputes/{id}/resolve` request will be handled by the dashboard router's `POST /:id/resolve` handler if the supplied ID matches a dispute document ID. If the intent is to unblock escrow funds, the correct target is the releaseHold router, but route registration order means the dashboard router intercepts the call first. This is a **route shadowing bug** — both routers claim the same URL pattern and the outcome depends entirely on registration order.
|
||||
>
|
||||
> In practice: calling `POST /api/disputes/:id/resolve` alone is **not sufficient to release or refund escrow**. The escrow unblock is only guaranteed when the releaseHold handler is reached. Verify router mount order in `backend/src/services/dispute/` before relying on either path in automation or admin tooling.
|
||||
|
||||
### 7. Delivery Confirmation Authorization Gap
|
||||
|
||||
> [!warning] ⚠️ Known authorization gap — `confirm-delivery`
|
||||
>
|
||||
> The `PATCH /api/marketplace/purchase-requests/:id/confirm-delivery` endpoint has **no authorization guard**. Any authenticated user (not just the buyer who owns the request) can call this endpoint and advance the purchase request status to `delivered`. This is a known gap and should be remediated by adding an ownership check (`req.user._id === purchaseRequest.buyerId`) before processing the status transition.
|
||||
|
||||
## Sequence Diagram - Funding
|
||||
|
||||
```mermaid
|
||||
@@ -182,6 +206,30 @@ sequenceDiagram
|
||||
BE->>DB: escrowState="released" or "refunded"
|
||||
```
|
||||
|
||||
## Sequence Diagram - Dispute Resolution (Escrow Path)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor A as Admin / Mediator
|
||||
participant DR as Dashboard Dispute Router\n(POST /api/disputes/:id/resolve)
|
||||
participant RH as releaseHold Router\n(POST /api/disputes/:purchaseRequestId/resolve)
|
||||
participant DB as MongoDB
|
||||
participant ES as Escrow / Payment
|
||||
|
||||
Note over DR,RH: Both routers mounted at /api/disputes — dashboard router registered first
|
||||
|
||||
A->>DR: POST /api/disputes/{disputeId}/resolve
|
||||
DR->>DB: Update Dispute document (status, notes)
|
||||
DR-->>A: 200 OK (Dispute updated only)
|
||||
Note over ES: Escrow funds still on hold at this point
|
||||
|
||||
A->>RH: POST /api/disputes/{purchaseRequestId}/resolve
|
||||
RH->>DB: Remove hold from Payment + PurchaseRequest
|
||||
RH->>ES: Escrow now eligible for release or refund
|
||||
RH-->>A: 200 OK (Hold removed)
|
||||
```
|
||||
|
||||
## API Calls
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
@@ -195,6 +243,8 @@ sequenceDiagram
|
||||
| `POST` | `/api/payment/:id/refund/confirm` | Confirm refund tx hash / signer proof |
|
||||
| `GET` | `/api/payment/:id` | Read payment details |
|
||||
| `GET` | `/api/payment/derived-destinations` | Admin list of derived destinations |
|
||||
| `POST` | `/api/disputes/:id/resolve` | Update Dispute document only — does NOT touch escrow |
|
||||
| `POST` | `/api/disputes/:purchaseRequestId/resolve` | Remove dispute hold from escrow (releaseHold router) — see shadowing note above |
|
||||
|
||||
## Side Effects And Risks
|
||||
|
||||
@@ -203,6 +253,8 @@ sequenceDiagram
|
||||
- **Trezor enforcement is configurable.** `TREZOR_SAFEKEEPING_REQUIRED=true` makes Trezor proof mandatory for release/refund confirmation, but target custody should be Safe multisig.
|
||||
- **Durable webhook ingress is still roadmap work.** Until the Worker/replay layer is live, backend availability remains important for Request Network webhook delivery.
|
||||
- **Dispute model is implemented but not fully canonical.** The current model works with legacy enum names; canonical status alignment remains required.
|
||||
- **Route shadowing on `/api/disputes`** — two routers registered at the same mount point. Dashboard router intercepts first; releaseHold handler may not be reachable by the expected URL in all configurations. See section 6 above.
|
||||
- **`confirm-delivery` has no authorization guard** — any authenticated user can advance a purchase request to `delivered`. See section 7 above.
|
||||
|
||||
## Linked Flows
|
||||
|
||||
|
||||
Reference in New Issue
Block a user