Merge remote-tracking branch 'origin/main'

This commit is contained in:
moojttaba
2026-05-25 00:23:05 +03:30
119 changed files with 13111 additions and 239 deletions

View File

@@ -26,15 +26,20 @@
"defaultSubtasks": 5, "defaultSubtasks": 5,
"defaultPriority": "medium", "defaultPriority": "medium",
"projectName": "Amanat Documentation PRDs", "projectName": "Amanat Documentation PRDs",
"defaultTag": "master",
"ollamaBaseURL": "http://localhost:11434/api", "ollamaBaseURL": "http://localhost:11434/api",
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
"responseLanguage": "English", "responseLanguage": "English",
"enableCodebaseAnalysis": true, "enableCodebaseAnalysis": true,
"enableProxy": false, "enableProxy": false,
"anonymousTelemetry": false "anonymousTelemetry": false,
"defaultTag": "master",
"userId": "1234567890"
}, },
"storage": { "claudeCode": {},
"type": "file" "codexCli": {},
"grokCli": {
"timeout": 120000,
"workingDirectory": null,
"defaultModel": "grok-4-latest"
} }
} }

View File

@@ -0,0 +1,163 @@
# PRD: Telegram-Native App, Bot, and Wallet Experience
## Scope
Create a separate delivery track for making Amanat usable from inside Telegram through a bot plus Telegram Mini App. The goal is not only notifications, but a native-feeling Telegram surface that can cover buyer, seller, escrow, chat, dispute, payment, release/refund, and support workflows.
This track should remain separate from the current security remediation and Request Network migration backlog, but it depends on those foundations for any money movement or high-risk actions.
## Platform assumptions checked May 24, 2026
- Telegram Mini Apps provide a full JavaScript web interface inside Telegram and can be launched from bot profile, menu button, inline buttons, direct links, inline mode, and selected attachment-menu contexts.
- Mini Apps must validate `Telegram.WebApp.initData` on the backend before trusting Telegram user identity. `initDataUnsafe` is not trusted.
- Bot API payments support invoices, invoice links, pre-checkout handling, successful payment events, Telegram Stars, and refund/subscription management for Stars.
- TON Connect is the standard wallet connection protocol for TON dApps and Telegram Mini Apps. It lets apps request wallet connection, signatures, and transactions without controlling user keys.
- TON Pay SDK and Wallet Pay are possible payment paths for TON/jetton/Telegram-wallet-style crypto payments, but they must be evaluated against escrow ledger, webhook, jurisdiction, KYC, fee, refund, and reconciliation requirements before production use.
Reference docs:
- Telegram Mini Apps: https://core.telegram.org/bots/webapps
- Telegram Bot API payments: https://core.telegram.org/bots/api#payments
- TON Connect: https://docs.ton.org/applications/ton-connect/overview
- TON Pay SDK: https://docs.ton.org/applications/ton-pay/overview
- Wallet Pay API: https://docs.wallet.tg/pay/
## Product goal
Users should be able to complete the practical Amanat journey without leaving Telegram:
- Register or link an Amanat account from Telegram.
- Browse and create purchase requests.
- Receive seller offers and buyer decisions.
- Chat around a request with structured context.
- Start escrow payment from Telegram.
- See payment, delivery, dispute, and release state.
- Submit evidence for delivery or dispute.
- Confirm delivery, request refund, or approve release when eligible.
- Receive actionable notifications and reminders.
- Use wallet/payments options that are safe for escrow accounting.
## Non-goals
- Do not bypass the canonical backend authorization, funds ledger, or escrow state machine.
- Do not treat Telegram chat messages as the source of truth for financial state.
- Do not release funds based only on bot callback data or client-side Mini App state.
- Do not store Telegram bot tokens, Wallet Pay keys, or TON private keys in frontend code.
- Do not make Telegram the only supported channel unless a later product decision explicitly says so.
## Architecture principles
- The Mini App is a client surface. The backend remains the authority for identity, authorization, payment state, escrow state, disputes, and audit logs.
- Telegram identity maps to an Amanat user through a verified link table keyed by Telegram user ID and Amanat user ID.
- Every Telegram-originated command, callback, webhook, and Mini App request is authenticated and rate-limited.
- Telegram callbacks must be idempotent and replay-safe.
- Payment events flow into the same provider-neutral payment adapter, ledger, and reconciliation pipeline as web checkout.
- High-risk actions such as release, refund, payout address change, dispute resolution, and admin override require step-up confirmation or explicit backend policy.
## Task set
### Task 1: Define Telegram product surface and flow map
Document which Amanat workflows live in bot messages, which live in the Mini App, and which remain web/admin-only for the first release.
Acceptance criteria:
- Buyer, seller, admin/support, unauthenticated, linked-user, and unlinked-user journeys are mapped.
- Deep-link entry points are specified for request details, offer review, payment, dispute, delivery evidence, and account linking.
- First-release scope separates must-have flows from later enhancements.
- Every Telegram action maps to an existing or planned backend API/state transition.
### Task 2: Build Telegram identity linking and session model
Implement secure account linking between Telegram users and Amanat accounts.
Acceptance criteria:
- Backend verifies Mini App `initData` before creating a Telegram session.
- Telegram user IDs are linked to Amanat users through an auditable association table.
- Account linking supports existing users, new users, unlinking, blocked accounts, and duplicate Telegram-account attempts.
- Session expiry, replay protection, rate limits, and audit logging are defined.
### Task 3: Implement bot command and notification foundation
Create the Telegram bot backend for commands, inline keyboards, callback queries, deep links, and outbound notifications.
Acceptance criteria:
- Bot supports start/help/link/status/request/offer/payment/dispute/settings basics.
- Callback payloads use short opaque IDs or signed tokens, not raw financial state.
- All incoming updates are idempotently processed and rate-limited.
- Notification preferences and quiet/error states are respected.
- Failed delivery, blocked bot, and retry behavior are observable.
### Task 4: Build Telegram Mini App shell for core marketplace workflows
Deliver the mobile-first Mini App that gives users the full Amanat workflow surface inside Telegram.
Acceptance criteria:
- Mini App uses Telegram theme, safe-area, viewport, back button, haptics, and main/bottom button patterns.
- Users can browse requests, create/edit requests, review offers, view escrow/payment state, upload evidence, and manage dispute/delivery actions.
- The Mini App can be launched from bot profile, menu button, inline buttons, and direct links with `startapp` context.
- UX handles unlinked accounts, expired sessions, unsupported Telegram clients, and fallback web links.
### Task 5: Add Telegram payment and wallet strategy
Evaluate and implement safe payment entry points for Telegram-native users without weakening escrow accounting.
Acceptance criteria:
- Compare Bot API payments/Stars, Wallet Pay, TON Pay, TON Connect, Request Network links, and existing crypto checkout for supported use cases.
- Select a first payment path and document rejected options.
- Payment creation stores provider, Telegram user ID, deep-link source, payment reference, invoice/order/request ID, currency, amount, expiration, and idempotency key.
- Wallet/TON flows validate recipient, asset, amount, memo/reference, confirmation status, and reconciliation evidence before crediting escrow.
- Refund/release behavior is explicitly compatible with the canonical ledger and dispute holds.
### Task 6: Expose escrow, delivery, dispute, and release actions safely
Make Telegram actions useful for real escrow work while preserving backend state authority.
Acceptance criteria:
- Telegram users can see current escrow state, next allowed actions, and blockers.
- Delivery confirmation, evidence upload, refund request, dispute open/respond, and release approval route through backend precondition checks.
- High-risk actions require fresh confirmation and are audit logged with Telegram context.
- Disputed or held funds cannot be released through Telegram shortcuts.
### Task 7: Add admin/support operating surface for Telegram-originated cases
Give support/admin users enough visibility to handle Telegram-originated users and payments.
Acceptance criteria:
- Admin UI/API shows Telegram linked identity, bot notification status, launch source, payment provider, and wallet/payment references.
- Support can resend links, revoke Telegram link, block bot access, and inspect Telegram-originated events.
- Admin overrides require the same step-up/two-person policy as web flows if configured.
### Task 8: Security, compliance, and abuse controls for Telegram
Threat-model the Telegram surface and add controls before launch.
Acceptance criteria:
- Threat model covers forged init data, callback replay, deep-link parameter tampering, phishing links, bot token leakage, spam, account takeover, wallet spoofing, fake payment proof, and support impersonation.
- Secrets, bot webhook endpoints, Wallet Pay keys, TON Connect manifest, CORS, CSP, allowed origins, and rate limits are documented.
- Monitoring covers update processing failures, abnormal callbacks, payment mismatches, blocked notifications, and suspicious wallet activity.
### Task 9: QA, rollout, analytics, and launch operations
Prepare the Telegram app/bot for controlled release.
Acceptance criteria:
- Test matrix covers Telegram iOS, Android, Desktop, Web, light/dark themes, compact/fullscreen modes, slow network, blocked bot, expired sessions, and payment cancellation.
- Sandbox/test bot and production bot environments are separated.
- Rollout uses feature flags, internal allowlist, beta cohort, and production enablement.
- Analytics measure activation, linked accounts, request creation, offer response, payment start/completion, dispute activity, release approval, and notification opt-outs.
- Runbooks exist for bot outage, Telegram API outage, payment provider outage, stuck payment, duplicate callback, suspicious wallet proof, and compromised bot token.
## Dependency guidance
- Tasks involving release/refund/payment crediting should depend on the platform hardening and ledger/payment-provider work.
- Identity, bot navigation, Mini App shell, and notification preferences can start earlier behind feature flags.
- Wallet/TON payment work must not precede the provider-neutral adapter, ledger invariants, and webhook/idempotency contracts.

View File

@@ -0,0 +1,146 @@
# PRD: Telegram Phone-Number Authentication
## Problem
The current Telegram integration treats Telegram as a *secondary identity layer*: a user must already hold an Amanat email-password (or Google OAuth) account before they can link their Telegram identity. This creates unnecessary friction for users who arrive from Telegram, have never heard of Amanat, and reasonably expect the Mini App to just work — as every other well-built Telegram Mini App does.
Telegram accounts are phone-number-verified by Telegram itself. When the backend successfully verifies Mini App `initData`, it has already established that the requester holds a specific Telegram account tied to a phone number. That is sufficient identity to create and authenticate an Amanat account without asking for an email or password.
## Goal
Allow users to sign in to Amanat using their Telegram identity as the primary credential — with zero separate signup step. This applies to:
1. **Telegram Mini App context**`initData` is auto-available and already cryptographically verified.
2. **Telegram Login Widget** — the standard web widget that lets users authenticate via Telegram on any browser page; returns a signed payload verifiable against the bot token.
In both cases the user's phone-verification is owned by Telegram; Amanat trusts the signed assertion and does not re-verify the phone number itself.
## Platform assumptions
- Telegram Mini App `initData` is signed with HMAC-SHA-256 keyed on `HMAC-SHA-256("WebAppData", BOT_TOKEN)`. Server-side verification is already implemented in `telegramService.ts`.
- Telegram Login Widget returns `{id, first_name, last_name, username, photo_url, auth_date, hash}`. Hash is `HMAC-SHA-256(data_check_string, SHA256(BOT_TOKEN))`. The same bot token covers both flows.
- Telegram user IDs are stable, globally unique integers that do not change even if the user changes username or phone number.
- Telegram does not expose the raw phone number to third-party apps — the ID is the stable identity anchor.
Reference docs:
- Telegram Login Widget: https://core.telegram.org/widgets/login
- Mini Apps initData: https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
## What changes
### Backend
**New endpoint: `POST /auth/telegram`**
Accepts one of:
- `{ initData: string }` — from Mini App context
- `{ id, first_name, last_name, username, photo_url, auth_date, hash }` — from Login Widget
Steps:
1. Verify signature (reuse `verifyMiniAppInitData` for initData path; add `verifyTelegramLoginWidget` for widget path).
2. Extract `telegramUserId` (stable integer).
3. Look up `TelegramLink` by `telegramUserId`.
4. **If link found and active** → load the linked Amanat user → issue JWT + refresh token (same format as `POST /auth/login`).
5. **If no link found** → auto-provision a new Amanat user:
- `email`: `null` (nullable; add index exclusion for null values)
- `firstName` / `lastName`: from Telegram profile
- `username`: `tg_<telegramUserId>` as stable internal handle
- `role`: `buyer` (default; user can change)
- `isEmailVerified`: `false` — set a flag `telegramVerified: true` instead
- `status`: `active`
- `authProvider`: `telegram`
- Create `TelegramLink` record in the same transaction.
- Issue JWT + refresh token.
6. On success, always upsert `TelegramLink.lastSeenAt` and update name/username fields from latest Telegram profile.
7. Return: `{ token, refreshToken, user, isNewUser: boolean }``isNewUser: true` signals the frontend to show an onboarding nudge (optional email capture, preferred language, currency).
**User model changes:**
- `email`: make nullable (`type: String, sparse: true` — allows multiple null values in a unique sparse index)
- Add `telegramVerified: Boolean` (default `false`)
- Add `authProvider: 'email' | 'google' | 'telegram'` field
- Existing email-based users are unaffected; their `authProvider` defaults to `'email'`
**Rate limiting and security:**
- Apply the same replay protection already in `telegramService.ts` to this endpoint.
- Rate limit: 10 requests per IP per minute, 5 per Telegram user ID per minute.
- Log all auto-provisioning events as audit records.
- Reject `auth_date` older than `TELEGRAM_MINIAPP_MAX_AGE_MS` (already configurable).
**Blocked account handling:**
- If `TelegramLink.status === 'blocked'` → return 403 with `ACCOUNT_BLOCKED` code.
- If the linked Amanat user is suspended/deleted → return 403 with `ACCOUNT_SUSPENDED`.
### Frontend
**Mini App auto-login (inside Telegram):**
When `window.Telegram?.WebApp?.initData` is non-empty:
1. Skip the login page entirely.
2. POST `initData` to `/auth/telegram`.
3. Store the returned JWT in the existing auth context.
4. If `isNewUser === true`, show a lightweight onboarding overlay inside the Mini App to capture optional email and preferred settings before routing to the main app.
**Web login page — "Continue with Telegram" button:**
- Add a "Continue with Telegram" button alongside the existing Google button.
- Clicking it opens the Telegram Login Widget in a popup (`window.open` or inline script tag method).
- On callback, POST the widget payload to `/auth/telegram`.
- This works on any browser, not only inside Telegram.
**Auth types update:**
```typescript
// auth/types.ts additions
export interface User {
// ... existing fields ...
telegramVerified: boolean;
authProvider: 'email' | 'google' | 'telegram';
telegramUsername?: string;
}
```
**Onboarding nudge (post-Telegram-auth):**
New users created via Telegram auth should be shown a non-blocking screen:
- Optional: "Add an email for account recovery" (not required)
- Optional: preferred language and currency
- Skippable — routing to the main app without completing it is fine
## Non-goals
- Do not expose the user's raw phone number.
- Do not require email for Telegram-authenticated users.
- Do not use Telegram auth as a bypass for high-risk actions (release, payout address change, dispute resolution still require step-up confirmation as per task 5.6/5.8 policy).
- Do not auto-merge a Telegram-provisioned account with an existing email account unless the user explicitly initiates account linking from settings.
## Account merge and collision handling
| Scenario | Behavior |
|---|---|
| Telegram user arrives, no existing account | Auto-provision, create TelegramLink, return `isNewUser: true` |
| Telegram user arrives, TelegramLink already exists | Auth as linked user, update last-seen |
| Email user opens Mini App, not yet linked | Show "Link your Telegram" prompt (existing task 5.2 flow) — do NOT auto-merge |
| Two Telegram accounts try to link to same email user | Reject second; return 409 DUPLICATE_TELEGRAM_LINK |
| Same Telegram user tries to auth with two different app accounts | Impossible by design — TelegramLink.telegramUserId is unique |
## Acceptance criteria
1. A new Telegram user can complete authentication inside the Mini App without entering an email or password.
2. A returning Telegram user gets the same JWT session whether they authenticate via Mini App `initData` or the web Login Widget.
3. `POST /auth/telegram` rejects replayed `initData` within the existing replay window.
4. `POST /auth/telegram` rejects `auth_date` older than the configured max-age.
5. Blocked Telegram accounts receive a 403 response; they cannot circumvent it by unlinking and re-linking.
6. Auto-provisioned users have `authProvider: 'telegram'` and no email; existing email users are unaffected.
7. Admin UI (task 5.7) can distinguish `authProvider: telegram` users and shows `telegramVerified` status.
8. A Telegram-authed user who later adds an email can then also log in via email — both paths converge to the same user record.
9. High-risk actions still require the step-up policy regardless of auth provider.
10. The "Continue with Telegram" button is visible on the web login page in non-Mini-App contexts.
## Dependencies
- Task 5.2 (identity linking model) — TelegramLink model and verifyMiniAppInitData are already done; this task extends the auth path, not the linking model.
- Task 5.8 (security controls) — replay protection, rate limits, and audit logging from that task apply here too.
- Task 5.6 (high-risk action policy) — must not be weakened.

View File

@@ -0,0 +1,206 @@
# Task 2.7 — Documentation & Code Alignment Report
**Date:** 2026-05-24
**Auditor:** Kimi Code CLI (subagent)
**Scope:** `nick-doc/` technical documentation vs. `backend/src/` runtime code
---
## 1. Files Reviewed
### Data Models (`nick-doc/02 - Data Models/`)
- `Data Model Overview.md`
- `Payment.md`
- `PurchaseRequest.md`
- `User.md`
- `Dispute.md`
- `Chat.md` (spot-checked)
### API Reference (`nick-doc/03 - API Reference/`)
- `API Overview.md`
- `Payment API.md`
- `Dispute API.md`
- `Marketplace API.md` (spot-checked)
- `Authentication API.md` (spot-checked)
### Architecture (`nick-doc/01 - Architecture/`)
- `Backend Architecture.md`
- `Security Architecture.md`
- `System Architecture.md`
### Flows (`nick-doc/04 - Flows/`)
- `Escrow Flow.md`
- `Dispute Flow.md`
- `Payment Flow - SHKeeper.md`
- `Payment Flow - DePay & Web3.md`
### Backend Code
- `backend/src/models/*.ts` (all)
- `backend/src/app.ts` (bootstrap, middleware, route mounting)
- `backend/src/services/payment/decentralizedPaymentRoutes.ts`
- `backend/src/services/payment/shkeeper/shkeeperRoutes.ts`
- `backend/src/services/payment/paymentControllerRoutes.ts`
- `backend/src/services/marketplace/controllerRoutes.ts`
- `backend/src/services/marketplace/routes.ts`
- `backend/src/services/auth/passkeyService.ts`
---
## 2. Discrepancies Found
### 2.1 Missing modules (documented but not implemented)
| Module | Doc claims | Reality in code |
|---|---|---|
| **Dispute** | `backend/src/services/dispute/`, `backend/src/models/Dispute.ts`, `/api/disputes` | **None exist**. No directory, no model, no routes, no controller. |
| **Blog** | `services/blog/blogRoutes.ts` | **Does not exist**. |
| **Points** | `services/points/pointsRoutes.ts`, `Points API` | **Does not exist**. |
| **Admin** | `services/admin/adminRoutes.ts`, `Admin API` | **Does not exist**. |
| **Review** | `Review.md` data model | `backend/src/models/Review.ts` **does not exist**. |
| **PointTransaction** | `PointTransaction.md` data model | `backend/src/models/PointTransaction.ts` **does not exist**. |
| **LevelConfig** | `LevelConfig.md` data model | `backend/src/models/LevelConfig.ts` **does not exist**. |
| **ShopSettings** | `ShopSettings.md` data model | `backend/src/models/ShopSettings.ts` **does not exist**. |
| **BlogPost** | `BlogPost.md` data model | `backend/src/models/BlogPost.ts` **does not exist**. |
### 2.2 Data-model enum mismatches
| Model | Field | Doc value | Code value | Severity |
|---|---|---|---|---|
| **Payment** | `provider` | `shkeeper` / `other` | `shkeeper` / `request.network` / `request-network` / `other` | Medium |
| **Payment** | `escrowState` | 6 values | 8 values (`+ cancelled`, `+ partial`) | Medium |
| **Payment** | `metadata` | SHKeeper fields only | Also has `requestNetworkRequestId`, `requestNetworkPaymentReference`, `requestNetworkSecurePaymentUrl`, `requestNetworkData` | Low |
| **Payment API** | Status list | Missing `confirmed` and `processing` in the "Status model" prose | Code has full 7-value enum incl. `confirmed`, `processing` | Medium |
### 2.3 User-model fields documented but missing from schema
The `User.md` table describes `referralCode`, `referredBy`, `points.{total,available,used,level}`, and `referralStats.{totalReferrals,activeReferrals,totalEarned}`. These fields **do not exist** in `backend/src/models/User.ts` (line count stops at 201; no referral or points subdocuments). The associated unique/sparse indexes are also absent.
### 2.4 Authentication requirements (post-remediation drift)
Several endpoints in `Payment API.md` were documented as **unauthenticated** but the code now enforces `authenticateToken` (and in some cases `authorizeRoles`):
| Endpoint | Doc auth | Code auth | Fix applied |
|---|---|---|---|
| `POST /api/payment/decentralized/save` | No | Bearer JWT + ownership | ✅ Updated doc |
| `PUT /api/payment/decentralized/update` | No | Bearer JWT + ownership | ✅ Updated doc |
| `GET /api/payment/decentralized/history/:userId` | No | Bearer JWT + `requireOwnershipOrAdmin` | ✅ Updated doc |
| `POST /api/payment/decentralized/verify/:paymentId` | No | Bearer JWT + ownership | ✅ Updated doc |
| `POST /api/payment/decentralized/verify-all-pending` | No (cron) | Bearer JWT + `admin` | ✅ Updated doc |
Additionally, the legacy `routes.ts` marketplace router applies `authenticateToken` globally, but `app.ts` mounts the **controller router** (`controllerRoutes.ts`) which exposes public read endpoints. The docs correctly describe the controller-router behaviour.
### 2.5 Rate limiting
All architecture and API docs stated rate limiting was **disabled** (`app.ts:227`). After remediation it is **enabled** with four tiers:
- Global: 100 req / 15 min
- Auth: 10 req / 15 min
- Payment: 30 req / 15 min
- AI: 20 req / 15 min
### 2.6 Socket.IO authentication
Docs described Socket.IO as an open real-time channel. In reality `app.ts` now enforces:
- JWT verification on every connection (`io.use(jwt.verify(...))`).
- Strict room-membership checks (`join-user-room`, `join-request-room`, `join-chat-room`, etc.) that reject cross-user or unauthorized joins.
### 2.7 Passkey hardening
`passkeyService.ts` now implements:
- Single-use challenge consumption (`consumeChallenge` deletes immediately after read).
- 5-minute challenge TTL with periodic cleanup.
- Explicit expiry error messages.
- This was not documented in the Security Architecture or Authentication API docs.
### 2.8 Endpoint path errors in flow docs
- `Payment Flow - DePay & Web3.md` referenced `POST /api/payment/decentralized/create` which **does not exist**; the actual endpoint is `POST /api/payment/decentralized/save`.
- Same doc referenced `POST /api/payment/decentralized/verify` which **does not exist**; the actual endpoint is `POST /api/payment/decentralized/verify/:paymentId`.
### 2.9 Backend Architecture route table omissions
The original table:
- Listed `/api/payment/payout` as a top-level mount; it is actually nested under `/api/payment/shkeeper/payout*`.
- Listed `/api/file`; the actual mount is `/api/files`.
- Omitted `/api/trezor`, `/api/email`, `/api/payment/decentralized`, and `/api/payment/request-network`.
---
## 3. Fixes Applied
### Data Models
1. **`Payment.md`**
- Expanded `provider` enum to match code (`request.network`, `request-network`).
- Expanded `escrowState` enum to include `cancelled`, `partial`.
- Added Request Network metadata fields to the schema table.
- Updated "Status model" prose to include `confirmed` and `processing`.
2. **`User.md`**
- Added `[!warning]` blocks on `referralCode`, `referredBy`, `points`, and `referralStats` fields marking them as **not yet implemented**.
- Corrected index line references and added a note about missing indexes.
3. **`Dispute.md`**
- Added a prominent `[!warning]` that `backend/src/models/Dispute.ts` does not exist and the schema reflects intended design only.
4. **`Data Model Overview.md`**
- Added a scope warning listing documented models that lack Mongoose schema files.
- Noted the two undocumented models that exist in code (`FundsLedgerEntry.ts`, `TrezorAccount.ts`).
### API Reference
5. **`API Overview.md`**
- Rewrote "Rate limiting" section to describe the four active tiers.
- Rewrote "Real-time channel" section to document Socket.IO JWT enforcement and room-membership checks.
- Marked `Dispute API`, `Blog API`, `Admin API`, and `Points API` as "planned, not yet implemented".
6. **`Payment API.md`**
- Updated auth requirements for all decentralized endpoints to reflect `authenticateToken` enforcement.
- Updated status/escrowState enums.
7. **`Dispute API.md`**
- Added a top-level `[!warning]` that the module is not implemented.
- Rewrote introductory sentences to use conditional language ("would be", "planned").
### Architecture
8. **`Backend Architecture.md`**
- Updated middleware chain note: rate limiting is now enabled.
- Updated route table to reflect actual mounts, mark missing services, and add omitted routes (`/api/files`, `/api/trezor`, `/api/email`, `/api/payment/decentralized`, `/api/payment/request-network`).
- Changed solid dependency arrows from `dispute` and `points` to dotted lines in the Mermaid diagram to indicate planned-but-not-implemented status.
9. **`Security Architecture.md`**
- Updated "Rate limiting & abuse" section to reflect enabled tiers.
- Checked off "Enable rate-limit middleware" and "Enforce Socket.IO JWT authentication" in the hardening checklist.
### Flows
10. **`Escrow Flow.md`**
- Updated `escrowState` enum prose to include `cancelled` and `partial`.
- Added `Cancelled` state to the Mermaid state diagram.
11. **`Dispute Flow.md`**
- Added a `[!warning]` block next to backend file references stating none exist.
12. **`Payment Flow - DePay & Web3.md`**
- Changed `POST /api/payment/decentralized/create``POST /api/payment/decentralized/save` with auth note.
- Changed `POST /api/payment/decentralized/verify``POST /api/payment/decentralized/verify/:paymentId` with auth note.
### Changelog
13. **`nick-doc/00 - Overview/Introduction.md`**
- Inserted a 2026-05-24 audit-remediation callout at the top summarising auth enforcement, rate limiting, passkeys, Web3 verification, Socket.IO auth, and dispute status.
---
## 4. Remaining Gaps
The following items were identified but **not fully resolved** (out of scope for a pure doc-alignment task or require code implementation):
1. **Missing models still documented**`Dispute`, `BlogPost`, `Review`, `PointTransaction`, `LevelConfig`, and `ShopSettings` remain in the docs with warnings. A future subtask should either implement the models or move the docs to a "Planned" archive folder.
2. **Missing services still documented**`Dispute API`, `Blog API`, `Admin API`, `Points API` still have markdown pages. Same recommendation as above.
3. **User referral/points fields** — The `User.ts` schema is missing the documented referral and points subdocuments. Either the schema needs to be extended or the fields should be removed from `User.md` once a final product decision is made.
4. **Frontend docs not audited** — This subtask focused on backend alignment. `nick-doc/05 - Design System/` and `frontend/src/` docs were not reviewed.
5. **OpenAPI / generated spec** — The `openapi.json` referenced in `Backend Architecture.md` was not regenerated or audited.
6. **`FundsLedgerEntry` & `TrezorAccount`** — These models exist in `backend/src/models/` but have no documentation notes yet. They should be added to `Data Model Overview.md` in a future pass.
---
## 5. Acceptance Criteria Verification
| Criterion | Status | Evidence |
|---|---|---|
| Data model enums in docs match backend models | ✅ | `Payment.md` provider & escrowState enums aligned; `User.md` enum values verified against `User.ts`. |
| API reference auth requirements reflect post-remediation state | ✅ | Decentralized payment endpoints now correctly list `Bearer JWT`; rate limiting section updated. |
| Flow docs do not describe unauthenticated financial endpoints | ✅ | DePay flow updated to show auth on `save` and `verify/:paymentId`. |
| Report file created | ✅ | This file. |
| No incorrect references to non-existent dispute modules remain | ✅ | All dispute references now carry `[!warning]` blocks and conditional language. |
---
*End of report.*

View File

@@ -1,6 +1,6 @@
# Task 2: Implement platform audit remediation plan # Task 2: Implement platform audit remediation plan
Status: pending Status: done
Priority: high Priority: high
Source PRD: `.taskmaster/docs/prd-platform-audit-remediation-plan-2026-05-24.md` Source PRD: `.taskmaster/docs/prd-platform-audit-remediation-plan-2026-05-24.md`
@@ -8,10 +8,10 @@ Address the code-backed security and consistency issues identified in the 2026-0
Subtasks: Subtasks:
1. Secure unauthenticated endpoints and owner enforcement. 1. Secure unauthenticated endpoints and owner enforcement.
2. Re-enable and scope rate limiting. 2. Re-enable and scope rate limiting.
3. Replace stubbed passkey/WebAuthn flow. 3. Replace stubbed passkey/WebAuthn flow.
4. Strengthen DePay/Web3 payment verification. 4. Strengthen DePay/Web3 payment verification.
5. Lock Socket.IO room joins to authenticated context. 5. Lock Socket.IO room joins to authenticated context.
6. Enforce dispute hold before payout and release operations. 6. Enforce dispute hold before payout and release operations.
7. Align documentation, API references, and runtime enums. 7. Align documentation, API references, and runtime enums.

View File

@@ -0,0 +1,22 @@
# Task 5: Deliver Telegram-native app, bot, and wallet experience
Status: pending
Priority: high
Source PRD: `.taskmaster/docs/prd-telegram-native-app-bot-wallet.md`
Create a Telegram bot plus Mini App surface so users can complete Amanat buyer, seller, escrow, chat, dispute, payment, release/refund, and support workflows from inside Telegram.
This is a separate product delivery track from platform hardening and Request Network migration. Identity, bot navigation, Mini App shell, and notifications can start behind feature flags. Wallet/payment crediting and release/refund actions must use canonical backend authorization, provider adapter, funds ledger, escrow state machine, idempotency, and dispute holds.
Subtasks:
1. Define Telegram product surface and flow map.
2. Build Telegram identity linking and session model.
3. Implement bot command and notification foundation.
4. Build Telegram Mini App shell for marketplace workflows.
5. Add Telegram payment and wallet strategy.
6. Expose escrow, delivery, dispute, and release actions safely.
7. Add admin and support surface for Telegram-originated cases.
8. Add security, compliance, and abuse controls for Telegram.
9. Prepare QA, rollout, analytics, and launch operations.
10. Implement Telegram as first-class authentication provider. ✅ Done 2026-05-24.

View File

@@ -1,20 +1,8 @@
{ {
"master": { "master": {
"metadata": {
"projectName": "Amanat Documentation PRDs",
"created": "2026-05-24T00:00:00.000Z",
"updated": "2026-05-24T00:00:00.000Z",
"description": "Taskmaster task queue generated from docs-side PRDs for developer sharing.",
"sourcePrds": [
".taskmaster/docs/prd-mermaid-diagram-rendering-stabilization.md",
".taskmaster/docs/prd-platform-audit-remediation-plan-2026-05-24.md",
".taskmaster/docs/prd-request-network-migration-and-funds-management.md",
".taskmaster/docs/audit-backend-stack-security-and-refactor-assessment-2026-05-24.md"
]
},
"tasks": [ "tasks": [
{ {
"id": 1, "id": "1",
"title": "Stabilize Mermaid diagram rendering across documentation vault", "title": "Stabilize Mermaid diagram rendering across documentation vault",
"description": "Correct Mermaid syntax/rendering issues across the documentation vault and validate all Mermaid blocks.", "description": "Correct Mermaid syntax/rendering issues across the documentation vault and validate all Mermaid blocks.",
"details": "Source PRD: .taskmaster/docs/prd-mermaid-diagram-rendering-stabilization.md. Scope covered 57 Mermaid blocks and 11 failing blocks. The source PRD records that all targeted files now pass mmdc parse validation and the full vault sweep passes.", "details": "Source PRD: .taskmaster/docs/prd-mermaid-diagram-rendering-stabilization.md. Scope covered 57 Mermaid blocks and 11 failing blocks. The source PRD records that all targeted files now pass mmdc parse validation and the full vault sweep passes.",
@@ -31,7 +19,8 @@
"status": "done", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [], "dependencies": [],
"testStrategy": "mmdc parse for the specific block." "testStrategy": "mmdc parse for the specific block.",
"parentId": "undefined"
}, },
{ {
"id": 2, "id": 2,
@@ -41,7 +30,8 @@
"status": "done", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [], "dependencies": [],
"testStrategy": "mmdc parse for both Authentication Flow blocks." "testStrategy": "mmdc parse for both Authentication Flow blocks.",
"parentId": "undefined"
}, },
{ {
"id": 3, "id": 3,
@@ -51,18 +41,19 @@
"status": "done", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [], "dependencies": [],
"testStrategy": "Full vault mmdc parser sweep across all Mermaid blocks." "testStrategy": "Full vault mmdc parser sweep across all Mermaid blocks.",
"parentId": "undefined"
} }
] ]
}, },
{ {
"id": 2, "id": "2",
"title": "Implement platform audit remediation plan", "title": "Implement platform audit remediation plan",
"description": "Address the code-backed security and consistency issues identified in the 2026-05-24 platform audit remediation PRD.", "description": "Address the code-backed security and consistency issues identified in the 2026-05-24 platform audit remediation PRD.",
"details": "Source PRD: .taskmaster/docs/prd-platform-audit-remediation-plan-2026-05-24.md. Target backend hardening first, then documentation/runtime alignment. Delivery order suggested by PRD: security/auth, rate limiting, passkeys, Web3 verification, socket hardening, dispute hold controls, docs/API alignment.", "details": "Source PRD: .taskmaster/docs/prd-platform-audit-remediation-plan-2026-05-24.md. Target backend hardening first, then documentation/runtime alignment. Delivery order suggested by PRD: security/auth, rate limiting, passkeys, Web3 verification, socket hardening, dispute hold controls, docs/API alignment.",
"testStrategy": "Add focused regression tests for route auth/ownership, passkey challenge/verification, Web3 verification semantics, socket authorization, rate limiting tiers, and payout/release dispute holds. Update API docs after behavior is implemented.", "testStrategy": "Add focused regression tests for route auth/ownership, passkey challenge/verification, Web3 verification semantics, socket authorization, rate limiting tiers, and payout/release dispute holds. Update API docs after behavior is implemented.",
"priority": "high", "priority": "high",
"status": "pending", "status": "done",
"dependencies": [], "dependencies": [],
"subtasks": [ "subtasks": [
{ {
@@ -70,78 +61,84 @@
"title": "Secure unauthenticated endpoints and owner enforcement", "title": "Secure unauthenticated endpoints and owner enforcement",
"description": "Require authenticateToken and owner/admin checks on exposed payment, AI, and legacy notification routes.", "description": "Require authenticateToken and owner/admin checks on exposed payment, AI, and legacy notification routes.",
"details": "Derive notification userId from authenticated principal. Protect payment history and mutation endpoints. Restrict AI calls to authenticated users with per-user budgets. Add denied-access audit logs.", "details": "Derive notification userId from authenticated principal. Protect payment history and mutation endpoints. Restrict AI calls to authenticated users with per-user budgets. Add denied-access audit logs.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [], "dependencies": [],
"testStrategy": "Unauthorized callers receive 401/403; users cannot access or mutate other users' payments/notifications; admins retain authorized access." "testStrategy": "Unauthorized callers receive 401/403; users cannot access or mutate other users' payments/notifications; admins retain authorized access.",
"parentId": "undefined"
}, },
{ {
"id": 2, "id": 2,
"title": "Re-enable and scope rate limiting", "title": "Re-enable and scope rate limiting",
"description": "Restore global and route-tiered rate limits for public-sensitive paths.", "description": "Restore global and route-tiered rate limits for public-sensitive paths.",
"details": "Use stricter limits for auth, financial, AI, file upload, and verification paths. Keep public reads at relaxed limits. Add observability for 429 spikes.", "details": "Use stricter limits for auth, financial, AI, file upload, and verification paths. Keep public reads at relaxed limits. Add observability for 429 spikes.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
1 1
], ],
"testStrategy": "Exercise configured limits per tier and confirm expected 429 responses without blocking ordinary reads." "testStrategy": "Exercise configured limits per tier and confirm expected 429 responses without blocking ordinary reads.",
"parentId": "undefined"
}, },
{ {
"id": 3, "id": 3,
"title": "Replace stubbed passkey/WebAuthn flow", "title": "Replace stubbed passkey/WebAuthn flow",
"description": "Implement production-grade WebAuthn registration/authentication and shared challenge storage.", "description": "Implement production-grade WebAuthn registration/authentication and shared challenge storage.",
"details": "Use real attestation/assertion verification, Redis-backed TTL challenges, refresh-token persistence/rotation, and deterministic malformed/reused/expired challenge errors.", "details": "Use real attestation/assertion verification, Redis-backed TTL challenges, refresh-token persistence/rotation, and deterministic malformed/reused/expired challenge errors.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
1 1
], ],
"testStrategy": "Registration, login, replay, expired challenge, and refresh-token continuity tests pass." "testStrategy": "Registration, login, replay, expired challenge, and refresh-token continuity tests pass.",
"parentId": "undefined"
}, },
{ {
"id": 4, "id": 4,
"title": "Strengthen DePay/Web3 payment verification", "title": "Strengthen DePay/Web3 payment verification",
"description": "Verify transaction recipient, token contract, and amount, not only receipt success.", "description": "Verify transaction recipient, token contract, and amount, not only receipt success.",
"details": "Decode ERC-20 Transfer logs, compare recipient against escrow address, validate token contract and decimals-adjusted minimum amount, store verifier evidence and idempotency fingerprint.", "details": "Decode ERC-20 Transfer logs, compare recipient against escrow address, validate token contract and decimals-adjusted minimum amount, store verifier evidence and idempotency fingerprint.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
1 1
], ],
"testStrategy": "Reject successful but wrong-recipient/wrong-token/underpaid tx hashes; accept only matching transfers." "testStrategy": "Reject successful but wrong-recipient/wrong-token/underpaid tx hashes; accept only matching transfers.",
"parentId": "undefined"
}, },
{ {
"id": 5, "id": 5,
"title": "Lock Socket.IO room joins to authenticated context", "title": "Lock Socket.IO room joins to authenticated context",
"description": "Remove trust in client-supplied user/buyer/seller room IDs.", "description": "Remove trust in client-supplied user/buyer/seller room IDs.",
"details": "Validate socket handshake token, derive server-side room membership, reject mismatched joins, and monitor suspicious join attempts.", "details": "Validate socket handshake token, derive server-side room membership, reject mismatched joins, and monitor suspicious join attempts.",
"status": "pending", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [ "dependencies": [
1 1
], ],
"testStrategy": "A user cannot subscribe to another user's rooms; legitimate realtime notifications still arrive." "testStrategy": "A user cannot subscribe to another user's rooms; legitimate realtime notifications still arrive.",
"parentId": "undefined"
}, },
{ {
"id": 6, "id": 6,
"title": "Enforce dispute hold before payout and release operations", "title": "Enforce dispute hold before payout and release operations",
"description": "Add payment hold state and central release/refund guards that block disputed funds.", "description": "Add payment hold state and central release/refund guards that block disputed funds.",
"details": "Introduce explicit dispute hold fields or state, enforce in PaymentCoordinator and payout/release services, return clear 409/423 responses, and backfill/report blocked payments.", "details": "Introduce explicit dispute hold fields or state, enforce in PaymentCoordinator and payout/release services, return clear 409/423 responses, and backfill/report blocked payments.",
"status": "pending", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [ "dependencies": [
1, 1,
4 4
], ],
"testStrategy": "Open dispute blocks release/refund until resolved or explicitly overridden through authorized path." "testStrategy": "Open dispute blocks release/refund until resolved or explicitly overridden through authorized path.",
"parentId": "undefined"
}, },
{ {
"id": 7, "id": 7,
"title": "Align documentation, API references, and runtime enums", "title": "Align documentation, API references, and runtime enums",
"description": "Normalize disputed/payment/request status docs and implementation references after security behavior changes.", "description": "Normalize disputed/payment/request status docs and implementation references after security behavior changes.",
"details": "Resolve mismatch around absent dispute module, endpoint names, status enums, and action names across Data Models, API Reference, and Flows.", "details": "Resolve mismatch around absent dispute module, endpoint names, status enums, and action names across Data Models, API Reference, and Flows.",
"status": "pending", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [ "dependencies": [
1, 1,
@@ -151,211 +148,300 @@
5, 5,
6 6
], ],
"testStrategy": "Docs match implemented routes, models, enum values, and state transitions." "testStrategy": "Docs match implemented routes, models, enum values, and state transitions.",
"parentId": "undefined"
} }
] ]
}, },
{ {
"id": 3, "id": "3",
"title": "Migrate payment architecture toward Request Network and internal funds management", "title": "Migrate payment architecture toward Request Network and internal funds management",
"description": "Plan and implement provider-neutral payment flows, Request Network pay-in support, funds ledger, webhook reconciliation, release/refund orchestration, UI migration, and SHKeeper decommissioning.", "description": "Plan and implement provider-neutral payment flows, Request Network pay-in support, funds ledger, webhook reconciliation, release/refund orchestration, UI migration, and SHKeeper decommissioning.",
"details": "Source PRD: .taskmaster/docs/prd-request-network-migration-and-funds-management.md. The PRD recommends phased migration behind a provider adapter, Secure Payment Pages first, platform-controlled escrow/payee destination, and a first-class internal funds ledger before release/refund enforcement.", "details": "Source PRD: .taskmaster/docs/prd-request-network-migration-and-funds-management.md. The PRD recommends phased migration behind a provider adapter, Secure Payment Pages first, platform-controlled escrow/payee destination, and a first-class internal funds ledger before release/refund enforcement.\n\nPost-completion update: Task 3 now includes a CI-safe focused verification command for the provider-neutral payment migration plus optional Trezor safekeeping. Trezor safekeeping is optional by default via TREZOR_SAFEKEEPING_REQUIRED=false and only gates release/refund confirmation when explicitly enabled. Vault references: 04 - Flows/Trezor Safekeeping Flow.md, 03 - API Reference/Trezor API.md, and 08 - Operations/Payment and Trezor Verification Report.md.",
"testStrategy": "Use feature flags, provider fixture tests, webhook signature/idempotency tests, ledger invariant tests, migration dry-run reports, and limited cohort rollout before default provider switch.", "testStrategy": "Use feature flags, provider fixture tests, webhook signature/idempotency tests, ledger invariant tests, migration dry-run reports, and limited cohort rollout before default provider switch.\n\nFocused verification command: npm test -- __tests__/payment-adapter-registry.test.ts __tests__/request-network-adapter.test.ts __tests__/request-network-payin.test.ts __tests__/request-network-webhook.test.ts __tests__/payment-ledger.model.test.ts __tests__/payment-ledger.service.test.ts __tests__/payment-migration.service.test.ts __tests__/payment-release-refund-orchestration.test.ts __tests__/payment-release-refund-routes.test.ts __tests__/payment-reconciliation.service.test.ts __tests__/payment-observability-redaction.test.ts __tests__/payment-observability-events.test.ts __tests__/trezor-safekeeping.service.test.ts --runInBand. Expected result: 13 suites, 64 tests passing. Also run npm run typecheck.",
"priority": "high", "priority": "high",
"status": "pending", "status": "done",
"dependencies": [ "dependencies": [
2 "2"
], ],
"subtasks": [ "subtasks": [
{ {
"id": 1, "id": 1,
"title": "Introduce provider-neutral payment adapter", "title": "Define provider-neutral payment contracts and adapter",
"description": "Decouple checkout, webhook, and payout flows from SHKeeper-specific routes and metadata.", "description": "Create provider-agnostic payment interface with pay-in, webhook, payout/refund instruction creation, status lookup, and search methods.",
"details": "Define createPayInIntent, getPayInStatus, handleProviderWebhook, createHostedPaymentLink, createReleaseInstruction, createRefundInstruction, getPayoutStatus, and searchProviderPayments. Add provider values shkeeper, request_network, manual, admin_wallet and PAYMENT_PROVIDER feature flag.", "details": "",
"status": "pending", "status": "done",
"priority": "high",
"dependencies": [], "dependencies": [],
"testStrategy": "New provider can be selected by feature flag while existing SHKeeper payments remain readable and process late webhooks." "parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:24:49.217Z"
}, },
{ {
"id": 2, "id": 2,
"title": "Implement Request Network pay-in integration", "title": "Implement provider configuration, feature flags, and safe rollback",
"description": "Create Request Network payment requests or Secure Payment Pages for new checkout flows.", "description": "Add runtime provider selection, rollout controls, env validation, and one-command kill-switch to revert to SHKeeper.",
"details": "Store requestId, paymentReference, securePaymentUrl, token, merchantReference, network, invoiceCurrency, and paymentCurrency. Validate supported networks/currencies before creating links.", "details": "",
"status": "pending", "status": "done",
"priority": "high",
"dependencies": [ "dependencies": [
1 "3.1"
], ],
"testStrategy": "Buyer receives hosted payment URL; webhook reconciles matching internal payment only after amount/currency/reference validation." "parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:30:51.152Z"
}, },
{ {
"id": 3, "id": 3,
"title": "Add funds ledger and escrow state machine", "title": "Create internal funds and payment ledger model",
"description": "Introduce internal funds accounting independent from provider metadata.", "description": "Define FundsAccount, immutable LedgerEntry, and balance/query views for expected/held/releasable/released/refunded/disputed states.",
"details": "Add FundsAccount, LedgerEntry, derived FundsBalance, expected/held/releasable/releasing/released/refunded/disputed/failed states, fee representation, and release/refund invariant checks.", "details": "",
"status": "pending", "status": "done",
"priority": "high",
"dependencies": [ "dependencies": [
1 "3.1"
], ],
"testStrategy": "Every pay-in creates immutable ledger entries and payout/refund cannot exceed available held funds or bypass dispute holds." "parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:30:52.613Z"
}, },
{ {
"id": 4, "id": 4,
"title": "Build Request Network webhook and reconciliation service", "title": "Build migration and indexing plan for existing SHKeeper records",
"description": "Process signed Request Network events and repair missed webhook state through reconciliation.", "description": "Add DB indexes for payment/provider fields and run backfill to produce a migration report with skipped/failed/ambiguous historical entries.",
"details": "Add /api/payment/request-network/webhook, verify raw-body x-request-network-signature, store delivery ID/retry/event/request/payment reference/payload hash, support test webhooks, and add scheduled payment search/status reconciliation.", "details": "",
"status": "pending", "status": "done",
"priority": "high",
"dependencies": [ "dependencies": [
2, "3.3"
3
], ],
"testStrategy": "Invalid signatures reject; duplicate delivery IDs acknowledge without duplicate ledger entries; reconciliation repairs missed state." "parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:34:59.429Z"
}, },
{ {
"id": 5, "id": 5,
"title": "Implement release, refund, and payout orchestration", "title": "Implement Request Network pay-in intent and secure payment pages",
"description": "Replace SHKeeper payout tasks and simulated release with auditable transaction instruction and confirmation flows.", "description": "Add Request Network intent/service layer, secure payment URLs, and validation of network/currency/reference/amount before setting paid state.",
"details": "Create release/refund service consuming ledger balances, generate Request Network payout or direct admin wallet instructions, store unsigned tx payloads, signer, submitted hash, confirmation status, provider status, and require admin/operator authorization plus dispute checks.", "details": "",
"status": "pending", "status": "done",
"priority": "high",
"dependencies": [ "dependencies": [
3, "3.2"
4
], ],
"testStrategy": "Release cannot occur if unpaid, already released, refunded, or disputed; tx hash confirmation updates ledger once; admin can retry/cancel safely." "parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:35:00.891Z"
}, },
{ {
"id": 6, "id": 6,
"title": "Migrate frontend checkout and admin payment UI", "title": "Implement signed Request Network webhook intake",
"description": "Update buyer checkout, admin release, seller payout, and payment details for provider-neutral Request Network flows.", "description": "Build /api/payment/request-network/webhook with raw-body signature verification, idempotent delivery handling, and immutable event audit rows.",
"details": "Replace ShkeeperPayment with CryptoPayment/RequestNetworkPayment redirect flow, keep legacy SHKeeper only for legacy records, replace ShkeeperPayout with release queue/admin payout UI, and show provider IDs, payment references, hosted links, ledger balances, webhook/reconciliation status.", "details": "",
"status": "pending", "status": "done",
"priority": "medium",
"dependencies": [ "dependencies": [
2, "3.2"
3,
5
], ],
"testStrategy": "Request Network checkout does not expect walletAddress; admin UI blocks unsafe release; legacy labels are hidden for Request Network records." "parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:35:02.505Z"
}, },
{ {
"id": 7, "id": 7,
"title": "Backfill legacy SHKeeper records and decommission provider-specific code", "title": "Implement reconciliation and repair jobs",
"description": "Migrate historical SHKeeper payment metadata and safely remove legacy wallet monitor/webhook/payout paths after cutoff.", "description": "Add periodic Request Network payment search/reconciliation and manual replay support to fix missed or delayed events.",
"details": "Backfill provider namespace, create ledger entries for trusted completed SHKeeper payments, mark legacyProvider, keep webhook tail period, and produce decommission checklist for env vars, docs, labels, routes, and runbooks.", "details": "",
"status": "pending", "status": "done",
"priority": "medium",
"dependencies": [ "dependencies": [
3, "3.5",
4, "3.6"
5,
6
], ],
"testStrategy": "Dry-run report includes total, migrated, skipped, ambiguous, failed; no historical transaction hash/invoice/task metadata is lost." "parentTaskId": 3,
} "parentId": "undefined",
] "updatedAt": "2026-05-24T06:44:46.205Z"
}, },
{ {
"id": 4, "id": 8,
"title": "Replace checkout and payment UI with provider-neutral flows",
"description": "Introduce provider-neutral payment components, remove SHKeeper walletAddress assumptions for RN, and keep legacy path only for existing SHKeeper records.",
"details": "",
"status": "done",
"dependencies": [
"3.5"
],
"parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:50:26.225Z"
},
{
"id": 9,
"title": "Add payout/release and refund orchestration using ledger gates",
"description": "Create release/refund instruction queue with signer, tx payloads, provider tx hash, and strict ledger invariants before action.",
"details": "",
"status": "done",
"dependencies": [
"3.3",
"3.7"
],
"parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:50:55.916Z"
},
{
"id": 10,
"title": "Update release/refund APIs and marketplace release paths",
"description": "Refactor release routes to consume ledger state and provider-neutral contracts; deprecate direct simulation where possible.",
"details": "",
"status": "done",
"dependencies": [
"3.8",
"3.9"
],
"parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:50:57.496Z"
},
{
"id": 11,
"title": "Add comprehensive observability, runbooks, and incident controls",
"description": "Track webhook latency, ledger imbalance, release failures, and reconciliation lag with alerts, on-call runbooks, and rollback procedures.",
"details": "",
"status": "done",
"dependencies": [
"3.6",
"3.8",
"3.9",
"3.10"
],
"parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:50:59.050Z"
},
{
"id": 12,
"title": "Add end-to-end integration, migration, and rollback test suites",
"description": "Cover backend contract tests, provider fixture tests, UI acceptance, rollout simulation, DRYRUN migration, and release rollback rehearsals.",
"details": "",
"status": "done",
"dependencies": [
"3.6",
"3.10",
"3.11"
],
"parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:51:00.615Z"
}
],
"updatedAt": "2026-05-24T07:04:01.906Z"
},
{
"id": "4",
"title": "Define backend security and refactor strategy from latest audit", "title": "Define backend security and refactor strategy from latest audit",
"description": "Convert the backend stack security/refactor assessment into concrete architecture decisions, documentation deliverables, and developer handoff criteria.", "description": "Convert the backend stack security/refactor assessment into concrete architecture decisions, documentation deliverables, and developer handoff criteria.",
"details": "Source audit: .taskmaster/docs/audit-backend-stack-security-and-refactor-assessment-2026-05-24.md. This task is advisory/architecture-focused and should run in parallel with immediate hardening. It should produce the decision artifacts needed before any backend-core rewrite or provider migration is started.", "details": "Source audit: .taskmaster/docs/audit-backend-stack-security-and-refactor-assessment-2026-05-24.md. This task is advisory/architecture-focused and should run in parallel with immediate hardening. It should produce the decision artifacts needed before any backend-core rewrite or provider migration is started.",
"testStrategy": "Review and sign off each architecture document with backend, payments, frontend, and operations stakeholders. Confirm every open question has an owner or explicit deferred decision before implementation work begins.", "testStrategy": "Review and sign off each architecture document with backend, payments, frontend, and operations stakeholders. Confirm every open question has an owner or explicit deferred decision before implementation work begins.",
"priority": "high", "priority": "high",
"status": "pending", "status": "done",
"dependencies": [], "dependencies": [],
"subtasks": [ "subtasks": [
{ {
"id": 1, "id": 1,
"title": "Assign security ownership and launch decision criteria", "title": "Assign security ownership and launch decision criteria",
"description": "Define who owns security decisions and what must be true before public launch or migration work proceeds.", "description": "Define who owns security decisions and what must be true before public launch or migration work proceeds.",
"details": "Answer ownership questions from the audit: security owner, launch safety bar, whether launch prioritizes hardening or redesign, and whether external penetration testing is required.", "details": "Completed. Produced 09 - Audits/Security Ownership and Launch Decision Criteria.md. Contains: RACI matrix (10 decision areas, 6 roles, fallback rules), 42-item launch safety gate checklist with Required/Strongly Recommended/Deferred classifications cross-referenced to audit findings, launch priority decision (harden first, redesign deferred), external pentest decision (yes, before public launch, with compensating controls), 12-item deferred decisions register with owners and deadlines.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [], "dependencies": [],
"testStrategy": "Written owner/RACI and launch gate checklist are accepted by leadership and engineering." "testStrategy": "Written owner/RACI and launch gate checklist are accepted by leadership and engineering.",
"parentId": "undefined",
"updatedAt": "2026-05-24T06:43:01.505Z"
}, },
{ {
"id": 2, "id": 2,
"title": "Produce threat model for escrow platform", "title": "Produce threat model for escrow platform",
"description": "Document protected assets, actors, trust boundaries, and abuse cases for the financial marketplace.", "description": "Document protected assets, actors, trust boundaries, and abuse cases for the financial marketplace.",
"details": "Include buyer, seller, admin, support, unauthenticated attacker, compromised user/admin, provider, malicious webhook sender, browser/backend/database/Redis/provider/wallet/Socket.IO trust boundaries, and abuse cases such as fake payment proof, replayed webhook, arbitrary room join, stolen token, double payout, dispute bypass, email abuse, and AI abuse.", "details": "Completed. Produced 09 - Audits/Threat Model - Amanat Escrow Platform.md. Contains: system description, 17 protected asset classes with sensitivity ratings, 11 actors with access levels and risk profiles, trust boundary diagram (Mermaid) with 10 boundary descriptions and current gaps, 23-threat catalog (T01-T23) with STRIDE categories and specific code-path references, risk summary matrix (6 Critical, 10 High, 6 Medium, 1 Low), threat-to-mitigation traceability matrix mapping 9 remediation docs to specific threats. Living document. Open verification items: Socket.IO room auth in socketService.ts, Telegram initData validation, actual lockfile versions for multer/axios/tanstack.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
1 1
], ],
"testStrategy": "Threat model maps each high-risk finding to at least one mitigation task or accepted risk." "testStrategy": "Threat model maps each high-risk finding to at least one mitigation task or accepted risk.",
"parentId": "undefined",
"updatedAt": "2026-05-24T06:43:03.144Z"
}, },
{ {
"id": 3, "id": 3,
"title": "Specify funds ledger and escrow state machine", "title": "Specify funds ledger and escrow state machine",
"description": "Define canonical money movement and legal state transitions before refactor or provider migration.", "description": "Define canonical money movement and legal state transitions before refactor or provider migration.",
"details": "Create specs for FundsAccount, LedgerEntry, FundsBalance, gross paid, provider fees, platform fees, held, disputed, releasable, released, refunded, idempotency keys, reconciliation behavior, purchase request states, payment states, escrow/funds states, dispute states, valid transitions, forbidden transitions, and release/refund/admin override preconditions.", "details": "Create specs for FundsAccount, LedgerEntry, FundsBalance, gross paid, provider fees, platform fees, held, disputed, releasable, released, refunded, idempotency keys, reconciliation behavior, purchase request states, payment states, escrow/funds states, dispute states, valid transitions, forbidden transitions, and release/refund/admin override preconditions.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
2 2
], ],
"testStrategy": "Spec can be used to reject double-release, release-during-dispute, underfunded payout, and ambiguous provider-event scenarios." "testStrategy": "Spec can be used to reject double-release, release-during-dispute, underfunded payout, and ambiguous provider-event scenarios.",
"parentId": "undefined",
"updatedAt": "2026-05-24T07:23:41.596Z"
}, },
{ {
"id": 4, "id": 4,
"title": "Create authorization matrix for REST and Socket.IO", "title": "Create authorization matrix for REST and Socket.IO",
"description": "Map every endpoint and realtime event to access level, ownership checks, state preconditions, rate-limit tier, and audit-log requirement.", "description": "Map every endpoint and realtime event to access level, ownership checks, state preconditions, rate-limit tier, and audit-log requirement.",
"details": "Include public/authenticated/owner/buyer/seller/admin/support/service-role classifications. Socket.IO rooms must be server-derived from authenticated identity, not client-supplied user IDs.", "details": "Include public/authenticated/owner/buyer/seller/admin/support/service-role classifications. Socket.IO rooms must be server-derived from authenticated identity, not client-supplied user IDs.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
2 2
], ],
"testStrategy": "No route or socket event remains unmapped; implementation tasks can reference matrix rows directly." "testStrategy": "No route or socket event remains unmapped; implementation tasks can reference matrix rows directly.",
"parentId": "undefined",
"updatedAt": "2026-05-24T07:23:43.108Z"
}, },
{ {
"id": 5, "id": 5,
"title": "Decide session, passkey, and admin step-up architecture", "title": "Decide session, passkey, and admin step-up architecture",
"description": "Choose browser session model and high-risk admin authentication requirements.", "description": "Choose browser session model and high-risk admin authentication requirements.",
"details": "Decide localStorage versus httpOnly cookies, access/refresh token lifetimes, CSRF strategy, refresh rotation, WebAuthn requirements, OAuth requirements, device/session revocation, and whether payouts/role changes require step-up authentication or two-person approval.", "details": "Decide localStorage versus httpOnly cookies, access/refresh token lifetimes, CSRF strategy, refresh rotation, WebAuthn requirements, OAuth requirements, device/session revocation, and whether payouts/role changes require step-up authentication or two-person approval.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
2 2
], ],
"testStrategy": "Decision record lists chosen model, rejected alternatives, migration cost, and required implementation tasks." "testStrategy": "Decision record lists chosen model, rejected alternatives, migration cost, and required implementation tasks.",
"parentId": "undefined",
"updatedAt": "2026-05-24T07:23:44.643Z"
}, },
{ {
"id": 6, "id": 6,
"title": "Specify webhook security and provider adapter contracts", "title": "Specify webhook security and provider adapter contracts",
"description": "Define provider-neutral payment interface and signed webhook processing rules.", "description": "Define provider-neutral payment interface and signed webhook processing rules.",
"details": "Document createPayInIntent, getPayInStatus, handleProviderWebhook, createHostedPaymentLink, createReleaseInstruction, createRefundInstruction, getPayoutStatus, searchProviderPayments, raw-body signature verification, replay prevention, delivery ID idempotency, duplicate/unknown event behavior, retry semantics, dead-letter/replay storage, and alert thresholds.", "details": "Document createPayInIntent, getPayInStatus, handleProviderWebhook, createHostedPaymentLink, createReleaseInstruction, createRefundInstruction, getPayoutStatus, searchProviderPayments, raw-body signature verification, replay prevention, delivery ID idempotency, duplicate/unknown event behavior, retry semantics, dead-letter/replay storage, and alert thresholds.",
"status": "pending", "status": "done",
"priority": "high", "priority": "high",
"dependencies": [ "dependencies": [
3 3
], ],
"testStrategy": "Contracts cover SHKeeper legacy, Request Network, manual/admin wallet, invalid signatures, duplicate deliveries, and missed webhook reconciliation." "testStrategy": "Contracts cover SHKeeper legacy, Request Network, manual/admin wallet, invalid signatures, duplicate deliveries, and missed webhook reconciliation.",
"parentId": "undefined",
"updatedAt": "2026-05-24T07:21:42.699Z"
}, },
{ {
"id": 7, "id": 7,
"title": "Define secure build and supply-chain policy", "title": "Define secure build and supply-chain policy",
"description": "Reduce npm/dependency compromise risk across frontend and any remaining Node services.", "description": "Reduce npm/dependency compromise risk across frontend and any remaining Node services.",
"details": "Specify package manager and lockfile policy, CI install mode, dependency update cadence, advisory monitoring, npm provenance/signature policy where available, secrets handling, reproducible production builds, and separation between frontend npm risk and backend-core risk.", "details": "Completed. Produced 09 - Audits/Secure Build and Supply-Chain Policy.md. 11 sections + 3 appendices: lockfile policy (npm ci mandatory), dependency update cadence (biweekly routine, immediate security-critical), advisory monitoring with SLAs (Critical 24h, High 72h, Medium 1 week), known exposure register with 5 open 2026 CVEs (multer, axios, tanstack, express, node) and SLA deadlines, npm provenance policy, secrets rotation schedule for all 10 secret types, production build reproducibility requirements, frontend vs backend risk separation with interim policy, incident response for 3 scenarios, CI/CD enforcement checklist with Gitea Actions YAML example.",
"status": "pending", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [ "dependencies": [
1 1
], ],
"testStrategy": "Policy is actionable in CI and includes response steps for compromised package, leaked token, and vulnerable dependency alerts." "testStrategy": "Policy is actionable in CI and includes response steps for compromised package, leaked token, and vulnerable dependency alerts.",
"parentId": "undefined",
"updatedAt": "2026-05-24T06:43:04.699Z"
}, },
{ {
"id": 8, "id": 8,
"title": "Make backend-core stack decision", "title": "Make backend-core stack decision",
"description": "Choose whether the security-critical backend core remains TypeScript or moves to Go/Kotlin/Rust/Python.", "description": "Choose whether the security-critical backend core remains TypeScript or moves to Go/Kotlin/Rust/Python.",
"details": "Evaluate team capability, two-year maintainability, operational footprint, rewrite cost, dual-stack complexity, auditability, supply-chain exposure, and which modules belong in a payment/auth/escrow core versus the existing marketplace/chat API.", "details": "Evaluate team capability, two-year maintainability, operational footprint, rewrite cost, dual-stack complexity, auditability, supply-chain exposure, and which modules belong in a payment/auth/escrow core versus the existing marketplace/chat API.",
"status": "pending", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [ "dependencies": [
2, 2,
@@ -365,22 +451,200 @@
6, 6,
7 7
], ],
"testStrategy": "Architecture decision record states chosen stack, scope of extraction, non-goals, migration phases, rollback criteria, and owners." "testStrategy": "Architecture decision record states chosen stack, scope of extraction, non-goals, migration phases, rollback criteria, and owners.",
"parentId": "undefined",
"updatedAt": "2026-05-24T07:21:45.258Z"
}, },
{ {
"id": 9, "id": 9,
"title": "Create migration and operational runbooks", "title": "Create migration and operational runbooks",
"description": "Document rollout, rollback, and incident response for the selected backend/funds architecture.", "description": "Document rollout, rollback, and incident response for the selected backend/funds architecture.",
"details": "Include SHKeeper legacy read path, provider feature flag, ledger backfill, validation report before enforcement, rollback criteria, webhook cutoff, manual reconciliation, failed webhook, duplicate/missing payment, stuck release, disputed release attempt, compromised admin, leaked API key, provider outage, chain/RPC outage, suspicious payment proof, and npm/package compromise.", "details": "Include SHKeeper legacy read path, provider feature flag, ledger backfill, validation report before enforcement, rollback criteria, webhook cutoff, manual reconciliation, failed webhook, duplicate/missing payment, stuck release, disputed release attempt, compromised admin, leaked API key, provider outage, chain/RPC outage, suspicious payment proof, and npm/package compromise.",
"status": "pending", "status": "done",
"priority": "medium", "priority": "medium",
"dependencies": [ "dependencies": [
8 8
], ],
"testStrategy": "Runbooks identify owner, trigger, detection signal, immediate action, recovery action, and post-incident documentation for each scenario." "testStrategy": "Runbooks identify owner, trigger, detection signal, immediate action, recovery action, and post-incident documentation for each scenario.",
"parentId": "undefined",
"updatedAt": "2026-05-24T07:21:47.810Z"
} }
] ],
"updatedAt": "2026-05-24T07:23:44.643Z"
},
{
"id": "5",
"title": "Deliver Telegram-native app, bot, and wallet experience",
"description": "Create a Telegram bot plus Mini App surface so users can complete Amanat buyer, seller, escrow, chat, dispute, payment, release/refund, and support workflows from inside Telegram.",
"details": "Source PRD: .taskmaster/docs/prd-telegram-native-app-bot-wallet.md. Keep this as a separate delivery track from security remediation and Request Network migration. Identity, bot navigation, Mini App shell, and notifications can start behind flags; wallet/payment crediting and release/refund actions must use canonical backend authorization, provider adapter, funds ledger, escrow state machine, idempotency, and dispute holds.",
"testStrategy": "Use Telegram sandbox and production bot separation, Mini App client matrix testing, provider/wallet payment fixtures, backend authorization and ledger invariant tests, webhook/callback replay tests, and staged rollout analytics before launch.",
"status": "in-progress",
"dependencies": [],
"priority": "high",
"subtasks": [
{
"id": 1,
"title": "Define Telegram product surface and flow map",
"description": "Document which Amanat workflows live in bot messages, which live in the Mini App, and which remain web/admin-only for first release.",
"details": "Map buyer, seller, admin/support, unauthenticated, linked-user, and unlinked-user journeys. Specify deep-link entry points for request details, offer review, payment, dispute, delivery evidence, and account linking. Separate first-release scope from later enhancements and map every Telegram action to backend API/state transitions.",
"status": "done",
"dependencies": [],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined",
"updatedAt": "2026-05-24T09:18:11.077Z"
},
{
"id": 2,
"title": "Build Telegram identity linking and session model",
"description": "Implement secure account linking between Telegram users and Amanat accounts.",
"details": "Backend must verify Telegram Mini App initData before creating a Telegram session. Store an auditable Telegram user ID to Amanat user link. Support existing users, new users, unlinking, blocked accounts, duplicate-link attempts, session expiry, replay protection, rate limits, and audit logs.",
"status": "done",
"dependencies": [
1
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined",
"updatedAt": "2026-05-24T09:18:13.054Z"
},
{
"id": 3,
"title": "Implement bot command and notification foundation",
"description": "Create the Telegram bot backend for commands, inline keyboards, callback queries, deep links, and outbound notifications.",
"details": "Support start/help/link/status/request/offer/payment/dispute/settings basics. Use short opaque IDs or signed tokens for callback payloads. Process incoming updates idempotently with rate limits. Respect notification preferences, quiet/error states, failed delivery, blocked bot, and retry observability.",
"status": "done",
"dependencies": [
1,
2
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined",
"updatedAt": "2026-05-24T13:46:14.458Z"
},
{
"id": 4,
"title": "Build Telegram Mini App shell for marketplace workflows",
"description": "Deliver the mobile-first Mini App that gives users the full Amanat workflow surface inside Telegram.",
"details": "Use Telegram theme, safe-area, viewport, back button, haptics, and main/bottom button patterns. Support browsing requests, creating/editing requests, reviewing offers, payment state, evidence uploads, delivery actions, and dispute actions. Launch from bot profile, menu button, inline buttons, and direct links with startapp context. Handle unlinked accounts, expired sessions, unsupported clients, and fallback web links.",
"status": "in-progress",
"dependencies": [
1,
2
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined",
"updatedAt": "2026-05-24T09:18:16.954Z"
},
{
"id": 5,
"title": "Add Telegram payment and wallet strategy",
"description": "Evaluate and implement safe payment entry points for Telegram-native users without weakening escrow accounting.",
"details": "Compare Bot API payments/Stars, Wallet Pay, TON Pay, TON Connect, Request Network links, and existing crypto checkout. Select a first payment path and document rejected options. Store provider, Telegram user ID, deep-link source, payment reference, invoice/order/request ID, currency, amount, expiration, and idempotency key. Wallet/TON flows must validate recipient, asset, amount, memo/reference, confirmation status, and reconciliation evidence before crediting escrow. Refund/release behavior must remain compatible with canonical ledger and dispute holds.",
"status": "done",
"dependencies": [
2,
4
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined",
"updatedAt": "2026-05-24T09:18:18.909Z"
},
{
"id": 6,
"title": "Expose escrow, delivery, dispute, and release actions safely",
"description": "Make Telegram actions useful for real escrow work while preserving backend state authority.",
"details": "Telegram users can see current escrow state, next allowed actions, and blockers. Delivery confirmation, evidence upload, refund request, dispute open/respond, and release approval must route through backend precondition checks. High-risk actions require fresh confirmation and audit logging with Telegram context. Disputed or held funds cannot be released through Telegram shortcuts.",
"status": "pending",
"dependencies": [
4,
5
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined"
},
{
"id": 7,
"title": "Add admin and support surface for Telegram-originated cases",
"description": "Give support/admin users visibility and controls for Telegram-originated users, payments, and bot events.",
"details": "Admin UI/API should show Telegram linked identity, bot notification status, launch source, payment provider, and wallet/payment references. Support can resend links, revoke Telegram link, block bot access, and inspect Telegram-originated events. Admin overrides must use the same step-up or two-person policy as web flows when configured.",
"status": "pending",
"dependencies": [
2,
3,
5
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined"
},
{
"id": 8,
"title": "Add security, compliance, and abuse controls for Telegram",
"description": "Threat-model the Telegram surface and add controls before launch.",
"details": "Cover forged init data, callback replay, deep-link parameter tampering, phishing links, bot token leakage, spam, account takeover, wallet spoofing, fake payment proof, and support impersonation. Document secrets, bot webhook endpoints, Wallet Pay keys, TON Connect manifest, CORS, CSP, allowed origins, rate limits, and monitoring for update failures, abnormal callbacks, payment mismatches, blocked notifications, and suspicious wallet activity.",
"status": "done",
"dependencies": [
2,
3,
5,
6
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined",
"updatedAt": "2026-05-24T09:18:24.717Z"
},
{
"id": 9,
"title": "Prepare QA, rollout, analytics, and launch operations",
"description": "Prepare the Telegram app and bot for controlled release.",
"details": "Test Telegram iOS, Android, Desktop, Web, light/dark themes, compact/fullscreen modes, slow network, blocked bot, expired sessions, and payment cancellation. Keep sandbox/test bot and production bot environments separated. Roll out through feature flags, internal allowlist, beta cohort, and production enablement. Track activation, linked accounts, request creation, offer response, payment start/completion, dispute activity, release approval, and notification opt-outs. Add runbooks for bot outage, Telegram API outage, payment provider outage, stuck payment, duplicate callback, suspicious wallet proof, and compromised bot token.",
"status": "done",
"dependencies": [
3,
4,
5,
6,
7,
8
],
"priority": "high",
"testStrategy": "See Telegram-native PRD acceptance criteria.",
"parentId": "undefined",
"updatedAt": "2026-05-24T09:18:26.638Z"
},
{
"id": 10,
"title": "Implement Telegram as first-class authentication provider",
"description": "Add a POST /auth/telegram endpoint and frontend login flow so users can authenticate with Amanat using only their Telegram identity — no email or password required.",
"details": "Source PRD: .taskmaster/docs/prd-telegram-phone-auth.md. Backend: create POST /auth/telegram that accepts Mini App initData or Telegram Login Widget payload, verifies the signature (reuse verifyMiniAppInitData; add verifyTelegramLoginWidget for the widget path), looks up TelegramLink by telegramUserId, and either authenticates the linked user or auto-provisions a new Amanat account (authProvider: telegram, telegramVerified: true, nullable email via sparse unique index). Returns JWT + refreshToken + isNewUser flag. Apply existing replay protection and rate limits. User model: make email nullable (sparse index), add authProvider and telegramVerified fields. Frontend: auto-detect Telegram Mini App context and skip login page; POST initData to /auth/telegram; show lightweight onboarding overlay for new users (optional email, language, currency). Add 'Continue with Telegram' button on web login page alongside Google OAuth. Security: blocked Telegram accounts return 403 regardless of re-linking attempts; high-risk action step-up policy is unchanged; never expose raw phone number.",
"status": "done",
"dependencies": [
2,
8
],
"priority": "high",
"testStrategy": "Verify: new Telegram user auto-provisions and receives JWT; returning user authenticates via both initData and Login Widget; replayed initData is rejected; stale auth_date is rejected; blocked account returns 403; existing email-password users are unaffected; email remains optional (not required) for Telegram-authed users; isNewUser flag triggers onboarding overlay; high-risk actions still require step-up confirmation.\n\nImplemented verification: backend typecheck; backend targeted Jest __tests__/telegram-auth.test.ts and __tests__/telegram-service.test.ts; frontend targeted Jest __tests__/auth/telegram-auth-action.test.ts and __tests__/sections/telegram/telegram-mini-app-shell.test.tsx. Full frontend typecheck still has unrelated pre-existing payment icon/payload errors outside Task 5.10.",
"parentId": "5",
"updatedAt": "2026-05-24T11:59:32.372Z"
} }
],
"updatedAt": "2026-05-24T13:46:14.458Z"
}
],
"metadata": {
"version": "1.0.0",
"lastModified": "2026-05-24T13:46:14.458Z",
"taskCount": 5,
"completedCount": 4,
"tags": [
"master"
] ]
} }
} }
}

View File

@@ -44,7 +44,7 @@ created: 2026-05-23
### Dispute ### Dispute
> [!info] Definition > [!info] Definition
> A formal complaint opened by either party when a deal goes wrong. Creates a three-way chat (buyer, seller, admin) and a `Dispute` document with a structured `timeline[]`, `evidence[]`, and `resolution`. Categories: `product_quality | delivery_delay | wrong_item | payment_issue | seller_behavior | other`. Outcomes: `refund | replacement | compensation | warning_seller | ban_seller | no_action`. See `backend/src/models/Dispute.ts`. > A formal complaint opened by either party when a deal goes wrong. Would create a three-way chat (buyer, seller, admin) and a `Dispute` document with a structured `timeline[]`, `evidence[]`, and `resolution`. Categories: `product_quality | delivery_delay | wrong_item | payment_issue | seller_behavior | other`. Outcomes: `refund | replacement | compensation | warning_seller | ban_seller | no_action`. See `backend/src/models/Dispute.ts` *(planned, not yet implemented)*.
### Escrow ### Escrow

View File

@@ -4,6 +4,16 @@ tags: [overview, introduction, mission, product]
created: 2026-05-23 created: 2026-05-23
--- ---
> [!note] 2026-05-24 Audit Remediation
> A platform-wide security and documentation alignment audit was completed on 2026-05-24 (Subtask 2.7). Key changes reflected in this vault:
> - **Authentication enforced** on all financial and marketplace endpoints; previously unauthenticated decentralized payment routes now require Bearer JWT.
> - **Rate limiting restored** in `backend/src/app.ts` with four tiers (global 100/15 min, auth 10/15 min, payment 30/15 min, AI 20/15 min).
> - **Passkeys hardened** — challenge consumption is now single-use with immediate deletion, 5-minute expiry, and replay-attack protection.
> - **Web3 verification real** — `BSCTransactionVerifier` performs on-chain `eth_getTransactionReceipt` validation with confirmation counting.
> - **Socket.IO auth enforced** — all socket connections require a valid JWT; room joins enforce strict ownership/participation checks.
> - **Dispute holds** documented as planned but not yet implemented; the `Dispute` model, service layer, and API routes do not exist in the current backend.
> - **Data model docs aligned** with actual Mongoose schemas (Payment provider/escrowState enums, User model omissions documented).
# Introduction # Introduction
> [!info] About this vault > [!info] About this vault

View File

@@ -125,7 +125,7 @@ Seller dashboard reuses the same `/dashboard` shell with extra modules:
- **Moderate users**: suspend / unsuspend accounts (`User.status: "active" | "suspended" | "deleted"`, see `backend/src/models/User.ts`), promote buyers to sellers, ban repeat offenders. - **Moderate users**: suspend / unsuspend accounts (`User.status: "active" | "suspended" | "deleted"`, see `backend/src/models/User.ts`), promote buyers to sellers, ban repeat offenders.
- **Moderate marketplace content**: categories (`Category` model), request templates (the canonical platform-wide ones), blog posts. - **Moderate marketplace content**: categories (`Category` model), request templates (the canonical platform-wide ones), blog posts.
- **Resolve disputes**: get assigned to disputes, drive them to resolution, choose an outcome (`refund | replacement | compensation | warning_seller | ban_seller | no_action`). See `backend/src/services/dispute/DisputeService.ts`. - **Resolve disputes**: get assigned to disputes, drive them to resolution, choose an outcome (`refund | replacement | compensation | warning_seller | ban_seller | no_action`). See `backend/src/services/dispute/DisputeService.ts` *(planned, not yet implemented)*.
- **Operate payments**: trigger payouts, fetch on-chain transactions, manually confirm stuck payments (the manual transaction-hash flow described in `backend/TODO.md`), audit the SHKeeper webhook history (`services/payment/shkeeper/webhookStats.ts`). - **Operate payments**: trigger payouts, fetch on-chain transactions, manually confirm stuck payments (the manual transaction-hash flow described in `backend/TODO.md`), audit the SHKeeper webhook history (`services/payment/shkeeper/webhookStats.ts`).
- **Configure the platform**: levels (`LevelConfig`), points multipliers, blog seed content, default templates. - **Configure the platform**: levels (`LevelConfig`), points multipliers, blog seed content, default templates.
- **Run data cleanup**: `/api/admin/cleanup` exposes destructive maintenance utilities (`services/admin/`). - **Run data cleanup**: `/api/admin/cleanup` exposes destructive maintenance utilities (`services/admin/`).

View File

@@ -38,10 +38,11 @@ flowchart TB
subgraph BE["Backend tier — Node.js / Express 5"] subgraph BE["Backend tier — Node.js / Express 5"]
REST["REST API<br/>/api/*"] REST["REST API<br/>/api/*"]
SocketS["Socket.IO server<br/>rooms per user / chat / request"] SocketS["Socket.IO server<br/>rooms per user / chat / request"]
Auth["Auth service<br/>JWT + Passkey + Google"] Auth["Auth service<br/>JWT + Passkey + Google + Telegram"]
Market["Marketplace service<br/>Requests, Offers, Templates"] Market["Marketplace service<br/>Requests, Offers, Templates"]
ChatSvc["Chat service"] ChatSvc["Chat service"]
PaySvc["Payment service<br/>+ PaymentCoordinator"] PaySvc["Payment service<br/>SHKeeper + Request Network + ledger"]
TelegramSvc["Telegram service<br/>bot + Mini App + notifications"]
Disp["Dispute service"] Disp["Dispute service"]
Points["Points / Referrals"] Points["Points / Referrals"]
BlogSvc["Blog service"] BlogSvc["Blog service"]
@@ -65,6 +66,8 @@ flowchart TB
Google["Google OAuth"] Google["Google OAuth"]
Sentry["Sentry"] Sentry["Sentry"]
Alchemy["Alchemy RPC"] Alchemy["Alchemy RPC"]
TelegramAPI["Telegram Bot API<br/>+ Mini App"]
ReqNet["Request Network<br/>pay-in / webhooks"]
end end
Browser --> SSR Browser --> SSR
@@ -78,20 +81,25 @@ flowchart TB
ClientJS --> REST ClientJS --> REST
SocketC <--> SocketS SocketC <--> SocketS
REST --> Auth & Market & ChatSvc & PaySvc & Disp & Points & BlogSvc & AISvc & Notif & Files REST --> Auth & Market & ChatSvc & PaySvc & TelegramSvc & Disp & Points & BlogSvc & AISvc & Notif & Files
SocketS --> ChatSvc & Notif & Market SocketS --> ChatSvc & Notif & Market
Auth & Market & ChatSvc & PaySvc & Disp & Points & BlogSvc --> Mongo Auth & Market & ChatSvc & PaySvc & Disp & Points & BlogSvc & TelegramSvc --> Mongo
Auth & PaySvc & Notif --> RedisDB Auth & PaySvc & Notif --> RedisDB
Files --> Disk Files --> Disk
PaySvc <--> SHK PaySvc <--> SHK
SHK -.webhook.-> PaySvc SHK -.webhook.-> PaySvc
PaySvc <--> ReqNet
ReqNet -.webhook.-> PaySvc
PaySvc --> Chain PaySvc --> Chain
Wagmi --> DePay Wagmi --> DePay
DePay --> Chain DePay --> Chain
PaySvc -.tx fetch.-> Alchemy PaySvc -.tx fetch.-> Alchemy
TelegramSvc <--> TelegramAPI
TelegramAPI -.webhook.-> TelegramSvc
Auth --> TelegramAPI
Notif --> SMTP Notif --> SMTP
Auth --> Google Auth --> Google
AISvc --> OpenAI AISvc --> OpenAI
@@ -103,11 +111,12 @@ flowchart TB
### Authentication & identity — [[Authentication Flow]] ### Authentication & identity — [[Authentication Flow]]
Auth is the gate to every authenticated route. Amn supports three login methods in parallel: Auth is the gate to every authenticated route. Amn supports four login methods in parallel:
- **Email + password (JWT).** Standard `bcrypt`-hashed credentials, access + refresh token pair, six-digit email verification codes, and password reset codes. Source: `backend/src/services/auth/authService.ts`. - **Email + password (JWT).** Standard `bcrypt`-hashed credentials, access + refresh token pair, six-digit email verification codes, and password reset codes. Source: `backend/src/services/auth/authService.ts`.
- **Passkey (WebAuthn).** Platform and cross-platform authenticators are registered against the user account; multiple devices per user are stored in `User.passkeys[]` (`backend/src/models/User.ts:125`). - **Passkey (WebAuthn).** Platform and cross-platform authenticators are registered against the user account; multiple devices per user are stored in `User.passkeys[]` (`backend/src/models/User.ts:125`).
- **Google OAuth.** Server-side verification via `google-auth-library`. See `backend/src/services/auth/googleOAuthService.ts`. - **Google OAuth.** Server-side verification via `google-auth-library`. See `backend/src/services/auth/googleOAuthService.ts`.
- **Telegram (first-class).** `POST /api/auth/telegram` accepts a Telegram Mini App `initData` string or a Telegram Login Widget payload. The backend verifies the Telegram HMAC signature, then signs the user in or auto-creates a new Amanat account with `authProvider: "telegram"` and no required email. This means a user who opens the Telegram Mini App is authenticated with zero sign-up friction. See `backend/src/services/auth/authController.ts` (`telegramAuth`) and [[Authentication Flow#Telegram first-class auth flow]].
Roles are `admin | buyer | seller` (`backend/src/models/User.ts:94`). Role checks happen in route middleware. **Refresh tokens** are stored on the `User` document and rotated. Roles are `admin | buyer | seller` (`backend/src/models/User.ts:94`). Role checks happen in route middleware. **Refresh tokens** are stored on the `User` document and rotated.
@@ -123,13 +132,14 @@ Services live in `backend/src/services/marketplace/` and are exposed through `/a
### Payments — [[Payments Overview]] / [[SHKeeper Integration]] ### Payments — [[Payments Overview]] / [[SHKeeper Integration]]
Payments are where Amn is most distinctive. The backend supports **three payment surfaces** routed through a common `Payment` model (`backend/src/models/Payment.ts`): Payments are where Amn is most distinctive. The backend supports **four payment surfaces** routed through a common `Payment` model (`backend/src/models/Payment.ts`) via a provider-neutral adapter layer (`backend/src/services/payment/adapters/`):
- **SHKeeper** — `/api/payment/shkeeper`. Mounted at `backend/src/app.ts:327`. Issues a fresh wallet address per invoice, polls / webhooks for payment confirmation, and runs through `PaymentCoordinator` to avoid race conditions where a payment status is updated twice. Health is monitored in the background (`shkeeperHealthCheck.ts`, started in `app.ts:433`). - **SHKeeper** — `/api/payment/shkeeper`. Issues a fresh wallet address per invoice, polls / webhooks for payment confirmation, and runs through `PaymentCoordinator` to avoid race conditions. Health is monitored in the background (`shkeeperHealthCheck.ts`).
- **Decentralized (Wagmi + DePay)** — `/api/payment/decentralized`. The user signs and sends the transfer from their own wallet; the backend then verifies the transaction on-chain via `blockchainTxFetcher.ts` and the Alchemy SDK. - **Request Network** — `/api/payment/request-network`. Creates on-chain payment requests via the Request Network protocol, generates Secure Payment Page URLs for the buyer, and receives real-time payment status via signed webhooks (`x-request-network-signature`). Pay-in service: `requestNetworkPayInService.ts`; reconciliation: `requestNetworkReconciliationService.ts`.
- **Decentralized (Wagmi + DePay)** — `/api/payment/decentralized`. The user signs and sends the transfer from their own wallet; the backend verifies on-chain via `blockchainTxFetcher.ts` and the Alchemy SDK.
- **Payout** — `/api/payment/shkeeper/payout`. Admin-triggered release of escrow funds to the seller's wallet once delivery is confirmed. - **Payout** — `/api/payment/shkeeper/payout`. Admin-triggered release of escrow funds to the seller's wallet once delivery is confirmed.
All three surfaces converge on the same `Payment` record (with `direction: 'in' | 'out' | 'refund'`) and trigger the same downstream events: order status update, notification, points award. **Pending payments are auto-cleaned** by a background timer started in `app.ts:374`. All surfaces converge on the same `Payment` record (with `direction: 'in' | 'out' | 'refund'`) and share the internal **funds ledger** (`backend/src/services/payment/ledger/`) which tracks available / held / releasable amounts independently of the provider. **Pending payments are auto-cleaned** by a background timer started in `app.ts`.
### Real-time chat — [[Chat System]] ### Real-time chat — [[Chat System]]
@@ -154,7 +164,9 @@ Push and SMS are tracked as **planned** in `backend/TODO.md`.
### Disputes — [[Dispute System]] ### Disputes — [[Dispute System]]
When a deal goes wrong (see [[Glossary#Dispute]]), either party can open a dispute. The backend (`backend/src/services/dispute/DisputeService.ts`) creates a **three-way chat** between buyer, seller, and admin, opens a `Dispute` document with a structured `timeline[]` and `evidence[]`, and assigns the dispute to an admin via `assignAdmin()`. Resolution can be `refund | replacement | compensation | warning_seller | ban_seller | no_action` and is recorded on the dispute itself. When a deal goes wrong (see [[Glossary#Dispute]]), either party can open a dispute. The backend would create a **three-way chat** between buyer, seller, and admin, open a `Dispute` document with a structured `timeline[]` and `evidence[]`, and assign the dispute to an admin via `assignAdmin()`. Resolution can be `refund | replacement | compensation | warning_seller | ban_seller | no_action` and is recorded on the dispute itself.
> [!warning] Not implemented
> `backend/src/services/dispute/DisputeService.ts` does not exist as of 2026-05-24.
### Points & referrals — [[Points System]] ### Points & referrals — [[Points System]]
@@ -200,7 +212,7 @@ OpenAI (model configurable per call) is exposed through `/api/ai/*`. The current
## Cross-cutting concerns ## Cross-cutting concerns
- **Observability** — Sentry is initialised at the very top of `app.ts` (line 2-3) and on the frontend in `sentry.{client,edge,server}.config.ts`. Logs flow through `backend/src/utils/logger.ts`. - **Observability** — Sentry is initialised at the very top of `app.ts` (line 2-3) and on the frontend in `sentry.{client,edge,server}.config.ts`. Logs flow through `backend/src/utils/logger.ts`.
- **Security** — `helmet`, CORS scoped to `FRONTEND_URL`, JWT with bcrypt-hashed passwords, role-gated middleware. Rate limiting is plumbed but currently disabled (see `app.ts:227`). - **Security** — `helmet`, CORS scoped to `FRONTEND_URL`, JWT with bcrypt-hashed passwords, role-gated middleware. Rate limiting is active (10 req/15 min on auth, 30 on payment, 100 global; Request Network and Telegram webhooks are skip-listed to avoid false limits).
- **Internationalisation** — Six locales (en, fr, vi, cn, ar, fa), with `fa` as default. RTL via `stylis-plugin-rtl`. See `frontend/src/locales/`. - **Internationalisation** — Six locales (en, fr, vi, cn, ar, fa), with `fa` as default. RTL via `stylis-plugin-rtl`. See `frontend/src/locales/`.
- **Theming** — MUI v7 with a custom theme in `frontend/src/theme/`. Dark mode is on the roadmap. - **Theming** — MUI v7 with a custom theme in `frontend/src/theme/`. Dark mode is on the roadmap.
- **Containerisation** — Docker Compose stacks for dev and prod live in both repos (`docker-compose.dev.yml`, `docker-compose.production.yml`). - **Containerisation** — Docker Compose stacks for dev and prod live in both repos (`docker-compose.dev.yml`, `docker-compose.production.yml`).

View File

@@ -129,7 +129,7 @@ The backend is `amn-backend@2.6.3-beta`, an Express 5 server in TypeScript backe
| body-parser | ^2.2.0 | Body parsing (legacy fallback) | Body middleware | | body-parser | ^2.2.0 | Body parsing (legacy fallback) | Body middleware |
| helmet | ^8.1.0 | HTTP security headers | `app.ts:189` | | helmet | ^8.1.0 | HTTP security headers | `app.ts:189` |
| cors | ^2.8.5 | Cross-origin policy | `app.ts:194` | | cors | ^2.8.5 | Cross-origin policy | `app.ts:194` |
| express-rate-limit | ^8.0.1 | Rate-limit middleware (currently off) | Plumbed in | | express-rate-limit | ^8.0.1 | Rate-limit middleware | Active — auth 10/15min, payment 30/15min, AI 20/15min, global 100/15min |
| express-validator | ^7.2.1 | Request validation | Auth, marketplace | | express-validator | ^7.2.1 | Request validation | Auth, marketplace |
| multer | ^2.0.2 | Multipart file uploads | `services/file/` | | multer | ^2.0.2 | Multipart file uploads | `services/file/` |
| sharp | ^0.34.3 | Image resizing / format conversion | Upload pipeline | | sharp | ^0.34.3 | Image resizing / format conversion | Upload pipeline |
@@ -211,10 +211,12 @@ The backend is `amn-backend@2.6.3-beta`, an Express 5 server in TypeScript backe
| Service | Purpose | Touchpoint in code | | Service | Purpose | Touchpoint in code |
|---|---|---| |---|---|---|
| **SHKeeper** | Self-hosted crypto payment processor — issues wallets, watches for incoming USDT, pays out | `backend/src/services/payment/shkeeper/` | | **SHKeeper** | Self-hosted crypto payment processor — issues wallets, watches for incoming USDT, pays out | `backend/src/services/payment/shkeeper/` |
| **Request Network** | On-chain payment request protocol — creates invoices, generates Secure Payment Pages, signs webhooks | `backend/src/services/payment/requestNetwork/` + adapters |
| **DePay** | Drop-in Web3 widget for wallet-to-wallet payment | `@depay/widgets` on frontend | | **DePay** | Drop-in Web3 widget for wallet-to-wallet payment | `@depay/widgets` on frontend |
| **EVM chains** (BSC, Ethereum mainnet, Sepolia, Polygon) | Settlement layer for stablecoin transfers | `frontend/src/web3/config.ts`, backend `blockchain/` | | **EVM chains** (BSC, Ethereum mainnet, Sepolia, Polygon) | Settlement layer for stablecoin transfers | `frontend/src/web3/config.ts`, backend `blockchain/` |
| **Alchemy RPC** | Hosted EVM RPC + transaction lookup | Frontend `alchemy-sdk`, backend `blockchainTxFetcher.ts` | | **Alchemy RPC** | Hosted EVM RPC + transaction lookup | Frontend `alchemy-sdk`, backend `blockchainTxFetcher.ts` |
| **MetaMask / WalletConnect** | Wallet connectors via Wagmi | `web3/config.ts` (WalletConnect commented out pending SSR fix) | | **MetaMask / WalletConnect** | Wallet connectors via Wagmi | `web3/config.ts` (WalletConnect commented out pending SSR fix) |
| **Telegram Bot API + Mini App** | Bot commands, inline keyboards, webhook updates, Mini App launch surface, Login Widget | `backend/src/services/telegram/`, `frontend/src/app/telegram/`, `frontend/src/utils/telegram-webapp.ts` |
| **OpenAI** | LLM for drafting / summarising | `backend/src/services/ai/` | | **OpenAI** | LLM for drafting / summarising | `backend/src/services/ai/` |
| **Google OAuth** | Federated login | `googleOAuthService.ts` | | **Google OAuth** | Federated login | `googleOAuthService.ts` |
| **SMTP** (provider configured per env) | Transactional email | `services/email/` | | **SMTP** (provider configured per env) | Transactional email | `services/email/` |

View File

@@ -37,10 +37,17 @@ backend/src/
│ ├── file/ # Multer uploads, MIME validation │ ├── file/ # Multer uploads, MIME validation
│ ├── marketplace/ # PurchaseRequest, SellerOffer, Template, Shop │ ├── marketplace/ # PurchaseRequest, SellerOffer, Template, Shop
│ ├── notification/ # Templates, delivery, mark-as-read │ ├── notification/ # Templates, delivery, mark-as-read
│ ├── payment/ # Payment orchestration + shkeeper/ subdir │ ├── payment/ # Payment orchestration + provider adapters + ledger
│ │ ├── adapters/ # Provider-neutral adapter interface + registry
│ │ ├── ledger/ # Internal funds ledger (available / held / releasable)
│ │ ├── reconciliation/ # Webhook + status reconciliation per provider
│ │ ├── migration/ # Legacy data backfill utilities
│ │ ├── observability/ # Logging and incident controls
│ │ ├── requestNetwork/ # Request Network pay-in, routes, webhook signature
│ │ └── shkeeper/ # SHKeeper API, webhook, payout │ │ └── shkeeper/ # SHKeeper API, webhook, payout
│ ├── points/ # Loyalty points, levels, redemption │ ├── points/ # Loyalty points, levels, redemption
│ ├── redis/ # Redis client, cache helpers │ ├── redis/ # Redis client, cache helpers
│ ├── telegram/ # Bot webhook, Mini App session, identity linking, notifications
│ ├── user/ # Profile, preferences, addresses │ ├── user/ # Profile, preferences, addresses
│ ├── admin/ # Admin-only operations │ ├── admin/ # Admin-only operations
│ └── email/ # Nodemailer transport + templates │ └── email/ # Nodemailer transport + templates
@@ -98,8 +105,8 @@ The bootstrap is intentionally linear and easy to audit. Execution order:
| 10 | `notFound` | tail | Returns 404 envelope for unmatched routes. | | 10 | `notFound` | tail | Returns 404 envelope for unmatched routes. |
| 11 | `errorHandler` | tail | Catches thrown errors, formats response. | | 11 | `errorHandler` | tail | Catches thrown errors, formats response. |
> [!warning] > [!note]
> Rate-limit middleware is **disabled by default for personal use** (see `app.ts:227` cited in the architecture review). Enable before any real public traffic — `express-rate-limit` is already a dependency. > Rate-limit middleware is **active** as of 2026-05-24: auth 10 req/15 min, payment 30/15 min, AI 20/15 min, global 100/15 min. Request Network and Telegram webhooks are exempt from the global limiter. Counters are in-memory — a Redis adapter is planned for distributed deployments.
--- ---
@@ -117,17 +124,23 @@ The full route table mounted by `app.ts`:
| `/api/marketplace/templates` | `services/marketplace/controllerRoutes.ts` | JWT (seller) | RequestTemplate CRUD | | `/api/marketplace/templates` | `services/marketplace/controllerRoutes.ts` | JWT (seller) | RequestTemplate CRUD |
| `/api/marketplace/categories` | `services/marketplace/controllerRoutes.ts` | public read | Category list | | `/api/marketplace/categories` | `services/marketplace/controllerRoutes.ts` | public read | Category list |
| `/api/marketplace/shop-settings` | `services/marketplace/shopSettingsController.ts` | JWT (seller) | Shop profile | | `/api/marketplace/shop-settings` | `services/marketplace/shopSettingsController.ts` | JWT (seller) | Shop profile |
| `/api/payment` | `services/payment/paymentRoutes.ts` | JWT | Payment intent, status | | `/api/payment` | `services/payment/paymentControllerRoutes.ts` + `paymentRoutes.ts` | JWT | Payment CRUD, health, export |
| `/api/payment/shkeeper/webhook` | `services/payment/shkeeper/shkeeperWebhook.ts` | HMAC | Inbound from gateway | | `/api/payment/decentralized` | `services/payment/decentralizedPaymentRoutes.ts` | mixed | Web3 save, verify, receiver |
| `/api/payment/payout` | `services/payment/shkeeper/shkeeperPayoutService.ts` | JWT (seller/admin) | Withdraw to wallet | | `/api/payment/shkeeper` | `services/payment/shkeeper/shkeeperRoutes.ts` | mixed | Intents, webhook, release, refund, config |
| `/api/payment/shkeeper/payout` | `services/payment/shkeeper/shkeeperPayoutRoutes.ts` | JWT (seller/admin) | Withdraw to wallet |
| `/api/payment/request-network` | `services/payment/requestNetwork/requestNetworkRoutes.ts` | HMAC sig | Request Network pay-in creation, Secure Payment Page, webhooks |
| `/api/telegram` | `services/telegram/telegramRoutes.ts` | mixed (some JWT, webhook uses secret-token) | Mini App verify/session, identity link/unlink, bot webhook |
| `/api/chat` | `services/chat/chatRoutes.ts` | JWT | Conversations, messages | | `/api/chat` | `services/chat/chatRoutes.ts` | JWT | Conversations, messages |
| `/api/notification` | `services/notification/notificationRoutes.ts` | JWT | List, mark read | | `/api/notification` | `services/notification/notificationRoutes.ts` + `notificationControllerRouter` | JWT | List, mark read |
| `/api/dispute` | `services/dispute/disputeRoutes.ts` | JWT | Open, evidence, resolve | | `/api/dispute` | `services/dispute/disputeRoutes.ts` | JWT | **Not implemented** — planned |
| `/api/blog` | `services/blog/blogRoutes.ts` | mixed | Public read, admin write | | `/api/blog` | `services/blog/blogRoutes.ts` | mixed | **Not implemented** — planned |
| `/api/admin` | `services/admin/adminRoutes.ts` | JWT (admin) | Mod operations | | `/api/admin` | `services/admin/adminRoutes.ts` | JWT (admin) | **Not implemented** — planned |
| `/api/points` | `services/points/pointsRoutes.ts` | JWT | Balance, redemption | | `/api/points` | `services/points/pointsRoutes.ts` | JWT | **Not implemented** — planned |
| `/api/ai` | `services/ai/aiRoutes.ts` | JWT | OpenAI-backed helpers | | `/api/ai` | `services/ai/aiRoutes.ts` | JWT | OpenAI-backed helpers |
| `/api/file` | `services/file/fileRoutes.ts` | JWT | Multipart upload | | `/api/files` | `services/file/fileRoutes.ts` | JWT | Multipart upload |
| `/api/email` | `services/email/emailRoutes.ts` | JWT | Email dispatch |
| `/api/trezor` | `services/trezor/trezorRoutes.ts` | JWT | Trezor hardware-wallet ops |
| `/api/users` | `services/user/userRoutes.ts` | JWT | Legacy user profile routes |
Full per-endpoint details → [[03 - API Reference/API Overview]] and the service-specific reference docs. Full per-endpoint details → [[03 - API Reference/API Overview]] and the service-specific reference docs.
@@ -170,19 +183,23 @@ flowchart TB
file[file] file[file]
email[email] email[email]
socket[socket] socket[socket]
telegram[telegram]
auth --> user auth --> user
auth --> notify auth --> notify
auth --> telegram
market --> notify market --> notify
market --> chat market --> chat
market --> file market --> file
pay --> market pay --> market
pay --> notify pay --> notify
pay --> socket pay --> socket
dispute --> market telegram --> notify
dispute --> chat telegram --> auth
dispute --> notify dispute -.-> market
points --> notify dispute -.-> chat
dispute -.-> notify
points -.-> notify
notify --> socket notify --> socket
notify --> email notify --> email
``` ```

View File

@@ -17,17 +17,20 @@ How identity, authorization, transport, and integrity are handled across the pla
| Threat | Mitigation | | Threat | Mitigation |
|---|---| |---|---|
| Credential stuffing | bcrypt 12-round hashing + account lockout + rate-limit (when enabled) | | Credential stuffing | bcrypt 12-round hashing + account lockout + rate-limit |
| Session hijacking | Short-lived JWTs (7d), opaque refresh tokens (30d), token rotation | | Session hijacking | Short-lived JWTs (7d), opaque refresh tokens (30d), token rotation |
| CSRF | JWT in `Authorization` header (not cookie), CORS allow-list | | CSRF | JWT in `Authorization` header (not cookie), CORS allow-list |
| XSS | Helmet CSP, React auto-escaping, sanitize HTML before storage | | XSS | Helmet CSP, React auto-escaping, sanitize HTML before storage |
| SQL/NoSQL injection | Mongoose parameterized queries, no `$where` strings, schema validation | | SQL/NoSQL injection | Mongoose parameterized queries, no `$where` strings, schema validation |
| Webhook spoofing | HMAC SHA-256 over body + secret, constant-time compare | | Webhook spoofing | HMAC SHA-256 over body + secret (SHKeeper, Request Network, Telegram), constant-time compare |
| File upload abuse | Multer MIME validation, 5 MB cap, non-executable storage, served by Nginx not Node | | File upload abuse | Multer MIME validation, 5 MB cap, non-executable storage, served by Nginx not Node |
| Replay attacks | Per-payment idempotency on `providerPaymentId`, per-request `X-Request-Id` | | Replay attacks | Per-payment idempotency on `providerPaymentId`; Telegram initData in-memory replay map; per-request `X-Request-Id` |
| Account takeover | Email verification required, password reset code expiry (1h), passkey support | | Account takeover | Email verification required, password reset code expiry (1h), passkey support |
| Phishing | Passkey origin binding (`NEXT_PUBLIC_PASSKEY_ORIGIN`), email domain pinning | | Phishing | Passkey origin binding (`NEXT_PUBLIC_PASSKEY_ORIGIN`), email domain pinning |
| Data leakage | Role-gated endpoints, field-level projection (`select: false` on password), redacted logs | | Data leakage | Role-gated endpoints, field-level projection (`select: false` on password), redacted logs |
| Forged Telegram identity | `initData` HMAC-SHA256 verified server-side; `initDataUnsafe` never trusted; auth_date max-age enforced |
| Telegram callback replay | In-memory replay map with configurable window in `telegramService.ts` |
| Blocked Telegram user bypass | `TelegramLink.status === 'blocked'` returns 403 regardless of unlink/re-link attempts |
--- ---
@@ -79,7 +82,38 @@ sequenceDiagram
> [!warning] > [!warning]
> Dev env files ship `NEXT_PUBLIC_PASSKEY_RP_ID=localhost`. In production this MUST be the actual eTLD+1 domain (e.g., `amn.gg`) — passkeys are scoped to the RP ID and can't be transferred. > Dev env files ship `NEXT_PUBLIC_PASSKEY_RP_ID=localhost`. In production this MUST be the actual eTLD+1 domain (e.g., `amn.gg`) — passkeys are scoped to the RP ID and can't be transferred.
### 2.4 Refresh-token rotation ### 2.4 Telegram (first-class auth provider)
```mermaid
sequenceDiagram
actor U as User (Telegram)
participant FE as Frontend / Mini App
participant BE as Backend
participant DB as MongoDB
U->>FE: opens Mini App (initData available) or clicks Login Widget
FE->>BE: POST /api/auth/telegram { initData | loginWidget }
BE->>BE: verifyMiniAppInitData() or verifyTelegramLoginWidget()
BE->>BE: reject if auth_date stale / replay / bot account
BE->>DB: TelegramLink.findOne({ telegramUserId })
alt link exists and active
BE->>DB: load linked User
else no link — auto-provision
BE->>DB: User.create({ authProvider:"telegram", telegramVerified:true, email:null })
BE->>DB: TelegramLink.create(...)
end
BE->>DB: upsert TelegramLink.lastSeenAt
BE->>BE: generateToken() + generateRefreshToken()
BE-->>FE: 200 { user, tokens, isNewUser }
```
- Backend source: `backend/src/services/auth/authController.ts` (`telegramAuth`) and `backend/src/services/telegram/telegramService.ts`.
- Mini App path uses `HMAC-SHA256("WebAppData", BOT_TOKEN)` per Telegram spec; Login Widget path uses `HMAC-SHA256(data_check_string, SHA256(BOT_TOKEN))`.
- In-memory replay maps guard against duplicate `initData` submissions within a configurable window.
- Blocked `TelegramLink` records return `403` — users cannot circumvent by unlinking and re-linking.
- Users with `authProvider: "telegram"` have nullable email; email-based operations (password reset) are not applicable to them.
- See [[Authentication Flow#Telegram first-class auth flow]].
### 2.5 Refresh-token rotation
- On `POST /api/auth/refresh`, the backend: - On `POST /api/auth/refresh`, the backend:
- Verifies the supplied refresh token. - Verifies the supplied refresh token.
@@ -119,7 +153,9 @@ A single User may be `buyer` and `seller` simultaneously (combined role).
--- ---
## 5. Webhook integrity (SHKeeper) ## 5. Webhook integrity
### 5.1 SHKeeper
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
@@ -136,11 +172,26 @@ sequenceDiagram
end end
``` ```
- Body must be the raw bytes used for HMAC — apply `express.raw({ type: 'application/json' })` on the webhook route ONLY (the rest of the app uses parsed JSON). - Raw body must be used for HMAC — `express.raw({ type: 'application/json' })` is mounted on this route only (before the global `express.json()` parser).
- In dev (`NODE_ENV === 'development'`) signature verification can be bypassed for local testing — confirm this is gated and never reachable in prod. - In dev (`NODE_ENV === 'development'`) signature verification can be bypassed for local testing — confirm this is gated and never reachable in prod.
- Idempotency: identical webhook delivered twice should be no-op. Check by `(providerPaymentId, status)` tuple before mutating. - Idempotency: identical webhook delivered twice should be no-op. Check by `(providerPaymentId, status)` tuple before mutating.
See [[Payment Flow - SHKeeper]] for the full flow. ### 5.2 Request Network
- Webhooks arrive at `/api/payment/request-network/webhook` with an `x-request-network-signature` header.
- The backend verifies the signature using `backend/src/services/payment/requestNetwork/signature.ts` before any state mutation.
- The route is mounted **before** the global `express.json()` body parser so raw body bytes are available for signature computation.
- The global rate-limit middleware is configured to skip this path to avoid blocking high-frequency payment events.
- Reconciliation service (`requestNetworkReconciliationService.ts`) handles replayed or out-of-order webhooks idempotently.
### 5.3 Telegram Bot webhook
- Webhooks arrive at `/api/telegram/webhook` with an `x-telegram-bot-api-secret-token` header.
- The backend verifies the token with `verifyTelegramWebhookSecret()` (`telegramService.ts`) before processing updates.
- A per-update-id in-memory replay map prevents duplicate processing within the configured window.
- The global rate-limit middleware is configured to skip this path.
See [[Payment Flow - SHKeeper]] for the SHKeeper full flow.
--- ---
@@ -176,12 +227,13 @@ See [[Environment Variables]] for the catalog.
## 9. Rate limiting & abuse ## 9. Rate limiting & abuse
- Backend has `express-rate-limit` ready but currently disabled (`app.ts:227`). - Rate limiting is **enabled** as of 2026-05-24 (`app.ts`).
- Recommended pre-launch settings: - Active tiers:
- `/api/auth/*` — 10 req / 5 min / IP - `/api/auth/*` — 10 req / 15 min / IP
- `/api/auth/login`5 req / 5 min / IP **and** /email - `/api/payment/*`30 req / 15 min / IP
- global API100 req / 15 min / IP (current default constants) - `/api/ai/*`20 req / 15 min / IP
- Counters stored in Redis when enabled. - global API — 100 req / 15 min / IP (skips `/health` and Request-Network webhooks)
- Counters are in-memory (Redis adapter planned for distributed deploys).
- For chat and notifications, debounce at the client to avoid spamming legitimate emits. - For chat and notifications, debounce at the client to avoid spamming legitimate emits.
--- ---
@@ -206,7 +258,8 @@ The codebase currently uses `morgan` (HTTP access logs) and ad-hoc `logger.info/
## 12. Hardening checklist (pre-launch) ## 12. Hardening checklist (pre-launch)
- [ ] Enable rate-limit middleware - [x] Enable rate-limit middleware (done 2026-05-24)
- [x] Enforce Socket.IO JWT authentication (done 2026-05-24)
- [ ] Promote refresh tokens to `httpOnly` cookies - [ ] Promote refresh tokens to `httpOnly` cookies
- [ ] Replace `localhost` passkey RP ID with production domain - [ ] Replace `localhost` passkey RP ID with production domain
- [ ] Disable `NEXT_PUBLIC_IS_DEVELOPMENT=true` and `ENABLE_DEBUG=true` in prod build - [ ] Disable `NEXT_PUBLIC_IS_DEVELOPMENT=true` and `ENABLE_DEBUG=true` in prod build
@@ -222,7 +275,7 @@ The codebase currently uses `morgan` (HTTP access logs) and ad-hoc `logger.info/
## Related ## Related
- [[Authentication Flow]] · [[Google OAuth Flow]] · [[Passkey (WebAuthn) Flow]] · [[Password Reset Flow]] - [[Authentication Flow]] (includes Telegram first-class auth flow) · [[Google OAuth Flow]] · [[Passkey (WebAuthn) Flow]] · [[Password Reset Flow]]
- [[Backend Architecture]] · [[Frontend Architecture]] · [[Real-time Layer]] - [[Backend Architecture]] · [[Frontend Architecture]] · [[Real-time Layer]]
- [[Payment Flow - SHKeeper]] — webhook HMAC details - [[Payment Flow - SHKeeper]] — webhook HMAC details
- [[Environment Variables]] — secret catalog - [[Environment Variables]] — secret catalog

View File

@@ -9,7 +9,19 @@ aliases: [Models Index, Schema Overview]
This section documents every Mongoose model that backs the marketplace. The persistence layer lives in `backend/src/models/` and is exported through a single barrel file at `backend/src/models/index.ts`. All models target MongoDB via Mongoose, lean on `timestamps: true` for `createdAt` / `updatedAt`, and follow a consistent pattern: one schema per file, an exported `I<Name>` TypeScript interface, and named exports for the compiled model. This section documents every Mongoose model that backs the marketplace. The persistence layer lives in `backend/src/models/` and is exported through a single barrel file at `backend/src/models/index.ts`. All models target MongoDB via Mongoose, lean on `timestamps: true` for `createdAt` / `updatedAt`, and follow a consistent pattern: one schema per file, an exported `I<Name>` TypeScript interface, and named exports for the compiled model.
> [!note] Scope > [!note] Scope
> Sixteen models are documented here. The "File" concept exists only at the service layer (`backend/src/services/file/`) and is not persisted as its own Mongoose collection, so it is not listed below. > Eighteen models are documented here. The "File" concept exists only at the service layer (`backend/src/services/file/`) and is not persisted as its own Mongoose collection, so it is not listed below.
>
> [!warning] Implementation gap
> As of the 2026-05-24 audit, the following documented models **do not yet have Mongoose schema files** in `backend/src/models/`:
> - [[Dispute]]
> - [[BlogPost]]
> - [[Review]]
> - [[PointTransaction]]
> - [[LevelConfig]]
> - [[ShopSettings]]
> The following *are* implemented in code and are documented accurately:
> - [[User]], [[PurchaseRequest]], [[SellerOffer]], [[Payment]], [[Chat]], [[Notification]], [[RequestTemplate]], [[Address]], [[Category]], [[TempVerification]], [[TelegramLink]], [[TelegramSession]]
> Additionally, `FundsLedgerEntry.ts` and `TrezorAccount.ts` exist in `backend/src/models/` but are not yet documented in this vault.
## Index of Models ## Index of Models
@@ -29,6 +41,8 @@ This section documents every Mongoose model that backs the marketplace. The pers
- [[LevelConfig]] — Static configuration of loyalty tiers (level number, point thresholds, benefits, icon, color). Driven by admins; consumed by the [[User]].points.level field. - [[LevelConfig]] — Static configuration of loyalty tiers (level number, point thresholds, benefits, icon, color). Driven by admins; consumed by the [[User]].points.level field.
- [[ShopSettings]] — One-to-one storefront branding for a seller: name, description, avatar, cover image, review toggles, and social links. - [[ShopSettings]] — One-to-one storefront branding for a seller: name, description, avatar, cover image, review toggles, and social links.
- [[TempVerification]] — Short-lived signup record that holds candidate user data and a verification code. Auto-purges via TTL when `emailVerificationCodeExpires` passes. - [[TempVerification]] — Short-lived signup record that holds candidate user data and a verification code. Auto-purges via TTL when `emailVerificationCodeExpires` passes.
- [[TelegramLink]] — Permanent auditable association between a Telegram user ID and an Amanat [[User]]. Stores Telegram profile metadata, link source (`miniapp` / `bot` / `login_widget`), status (`active` / `blocked`), and last-seen timestamp. One per Telegram user (unique on both `userId` and `telegramUserId`).
- [[TelegramSession]] — Short-lived Telegram Mini App session token issued when `initData` is verified. Carries the `initDataFingerprint` for replay protection and auto-expires via a MongoDB TTL index on `expiresAt`.
## Relationship Diagram ## Relationship Diagram
@@ -71,6 +85,10 @@ erDiagram
LEVEL_CONFIG ||..|| USER : "level lookup" LEVEL_CONFIG ||..|| USER : "level lookup"
TEMP_VERIFICATION ||..|| USER : "promoted to" TEMP_VERIFICATION ||..|| USER : "promoted to"
TELEGRAM_LINK }o--|| USER : "links identity"
TELEGRAM_SESSION }o--o| USER : "session for"
TELEGRAM_SESSION }o--|| TELEGRAM_LINK : "matches"
``` ```
## Conventions Across All Models ## Conventions Across All Models
@@ -95,7 +113,7 @@ The dominant happy-path flow exercises five collections in order:
4. The seller marks the request `delivery``delivered`; the buyer confirms with the 6-digit `deliveryCode` and the request becomes `completed`. 4. The seller marks the request `delivery``delivered`; the buyer confirms with the 6-digit `deliveryCode` and the request becomes `completed`.
5. The escrow `Payment` flips to `released` and a payout `Payment` (`direction: 'out'`) is issued. Optionally the buyer writes a `Review` and earns a `PointTransaction`. 5. The escrow `Payment` flips to `released` and a payout `Payment` (`direction: 'out'`) is issued. Optionally the buyer writes a `Review` and earns a `PointTransaction`.
If anything goes sideways, the buyer can open a `Dispute`, which freezes the flow until an admin resolves it (refund, replacement, compensation, or no-action). If anything goes sideways, the buyer can open a `Dispute` (planned but not yet implemented), which would freeze the flow until an admin resolves it (refund, replacement, compensation, or no-action).
## How to Navigate ## How to Navigate

View File

@@ -8,8 +8,10 @@ aliases: [Complaint, IDispute]
Buyer-raised complaint tied to a [[PurchaseRequest]]. Captures the reason, priority, category, an array of evidence uploads, a chronological `timeline` of actions, an optional resolution, and SLA deadlines. An admin (`adminId`) is assigned during triage and resolves the dispute with a structured action (`refund`, `replacement`, `compensation`, `warning_seller`, `ban_seller`, or `no_action`). Buyer-raised complaint tied to a [[PurchaseRequest]]. Captures the reason, priority, category, an array of evidence uploads, a chronological `timeline` of actions, an optional resolution, and SLA deadlines. An admin (`adminId`) is assigned during triage and resolves the dispute with a structured action (`refund`, `replacement`, `compensation`, `warning_seller`, `ban_seller`, or `no_action`).
> [!note] Source > [!warning] Missing model
> `backend/src/models/Dispute.ts:69` — schema definition > **`backend/src/models/Dispute.ts` does not exist** as of the 2026-05-24 audit. The `Dispute` model, service layer, and API routes are **documented but not yet implemented** in the backend. The schema below reflects the *intended* design only.
>
> Source (intended): `backend/src/models/Dispute.ts:69` — schema definition
> `backend/src/models/Dispute.ts:238` — model export > `backend/src/models/Dispute.ts:238` — model export
## Schema ## Schema
@@ -57,7 +59,7 @@ None defined.
## Indexes ## Indexes
Defined at `backend/src/models/Dispute.ts:212-223`: Defined at `backend/src/models/Dispute.ts:212-223` *(intended)*:
- `{ purchaseRequestId: 1 }` - `{ purchaseRequestId: 1 }`
- `{ buyerId: 1 }` - `{ buyerId: 1 }`
@@ -74,7 +76,7 @@ Defined at `backend/src/models/Dispute.ts:212-223`:
| Hook | Behaviour | | Hook | Behaviour |
| --- | --- | | --- | --- |
| `pre('save')` (`backend/src/models/Dispute.ts:226`) | On new documents pushes a `dispute_created` entry into `timeline` attributed to `buyerId`. | | `pre('save')` (`backend/src/models/Dispute.ts:226` *(intended)*) | On new documents pushes a `dispute_created` entry into `timeline` attributed to `buyerId`. |
## Instance Methods ## Instance Methods

View File

@@ -25,7 +25,7 @@ Records every monetary movement in the marketplace: buyer pay-ins, seller payout
| `sellerId` | Mixed (ObjectId or String) | yes | — | — | yes (compound) | Seller receiving (or template seller). | | `sellerId` | Mixed (ObjectId or String) | yes | — | — | yes (compound) | Seller receiving (or template seller). |
| `amount.amount` | Number | yes | — | — | — | Numeric amount. | | `amount.amount` | Number | yes | — | — | — | Numeric amount. |
| `amount.currency` | String | yes | `USDT` | — | — | Settlement currency. | | `amount.currency` | String | yes | `USDT` | — | — | Settlement currency. |
| `provider` | String | no | `shkeeper` | enum: `shkeeper` / `other` | yes (compound, partial) | Payment processor. | | `provider` | String | no | `shkeeper` | enum: `shkeeper` / `request.network` / `request-network` / `other` | yes (compound, partial) | Payment processor. |
| `direction` | String | no | `in` | enum: `in` / `out` / `refund` | yes (compound, partial) | Flow direction. | | `direction` | String | no | `in` | enum: `in` / `out` / `refund` | yes (compound, partial) | Flow direction. |
| `blockchain.network` | String | no | — | — | — | Network identifier. | | `blockchain.network` | String | no | — | — | — | Network identifier. |
| `blockchain.transactionHash` | String | no | — | — | yes (sparse) | On-chain tx hash. | | `blockchain.transactionHash` | String | no | — | — | yes (sparse) | On-chain tx hash. |
@@ -36,7 +36,7 @@ Records every monetary movement in the marketplace: buyer pay-ins, seller payout
| `blockchain.confirmedAt` | Date | no | — | — | — | When tx confirmed. | | `blockchain.confirmedAt` | Date | no | — | — | — | When tx confirmed. |
| `blockchain.confirmations` | Number | no | `0` | — | — | Confirmation count. | | `blockchain.confirmations` | Number | no | `0` | — | — | Confirmation count. |
| `status` | String | no | `pending` | enum: `pending` / `processing` / `confirmed` / `completed` / `failed` / `cancelled` / `refunded` | yes (compound) | Lifecycle status. | | `status` | String | no | `pending` | enum: `pending` / `processing` / `confirmed` / `completed` / `failed` / `cancelled` / `refunded` | yes (compound) | Lifecycle status. |
| `escrowState` | String | no | — | enum: `funded` / `releasable` / `released` / `refunded` / `releasing` / `failed` | — | Escrow lifecycle. | | `escrowState` | String | no | — | enum: `funded` / `releasable` / `released` / `refunded` / `releasing` / `failed` / `cancelled` / `partial` | — | Escrow lifecycle. |
| `providerPaymentId` | String | no | — | — | yes (sparse) | External provider id for idempotency. | | `providerPaymentId` | String | no | — | — | yes (sparse) | External provider id for idempotency. |
| `metadata.userAgent` | String | no | — | — | — | Browser UA. | | `metadata.userAgent` | String | no | — | — | — | Browser UA. |
| `metadata.ipAddress` | String | no | — | — | — | Client IP. | | `metadata.ipAddress` | String | no | — | — | — | Client IP. |
@@ -51,6 +51,10 @@ Records every monetary movement in the marketplace: buyer pay-ins, seller payout
| `metadata.cryptoName` | String | no | — | — | — | Crypto label. | | `metadata.cryptoName` | String | no | — | — | — | Crypto label. |
| `metadata.walletAddress` | String | no | — | — | — | Wallet address. | | `metadata.walletAddress` | String | no | — | — | — | Wallet address. |
| `metadata.shkeeperTaskId` | String | no | — | — | — | Payout task id. | | `metadata.shkeeperTaskId` | String | no | — | — | — | Payout task id. |
| `metadata.requestNetworkRequestId` | String | no | — | — | — | Request Network request id. |
| `metadata.requestNetworkPaymentReference` | String | no | — | — | — | Request Network payment reference. |
| `metadata.requestNetworkSecurePaymentUrl` | String | no | — | — | — | Request Network secure payment URL. |
| `metadata.requestNetworkData` | Mixed | no | — | — | — | Raw Request Network payload. |
| `metadata.lastWebhookAt` | Date | no | — | — | — | Last webhook timestamp. | | `metadata.lastWebhookAt` | Date | no | — | — | — | Last webhook timestamp. |
| `metadata.webhookPayload` | Mixed | no | — | — | — | Last webhook body. | | `metadata.webhookPayload` | Mixed | no | — | — | — | Last webhook body. |
| `metadata.createdVia` | String | no | — | — | — | Origin marker. | | `metadata.createdVia` | String | no | — | — | — | Origin marker. |

View File

@@ -16,12 +16,14 @@ The core identity document for every actor in the marketplace: buyers, sellers,
| Field | Type | Required | Default | Validation | Index | Description | | Field | Type | Required | Default | Validation | Index | Description |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| `email` | String | yes | — | lowercase, trim | unique | Primary login identifier. | | `email` | String | no | — | lowercase, trim | unique, sparse | Primary email login identifier. Nullable for Telegram-only accounts. |
| `password` | String | no | — | minlength 6 | — | Hashed password. Optional to support passkey-only accounts. | | `password` | String | no | — | minlength 6 | — | Hashed password. Optional to support passkey-only, Google, and Telegram accounts. |
| `firstName` | String | no | `"کاربر"` | trim | — | Persian default ("user"). | | `firstName` | String | no | `"کاربر"` | trim | — | Persian default ("user"). |
| `lastName` | String | no | `"جدید"` | trim | — | Persian default ("new"). | | `lastName` | String | no | `"جدید"` | trim | — | Persian default ("new"). |
| `role` | String | yes | `"buyer"` | enum: `admin` / `buyer` / `seller` | yes | Authorisation tier. | | `role` | String | yes | `"buyer"` | enum: `admin` / `buyer` / `seller` | yes | Authorisation tier. |
| `isEmailVerified` | Boolean | no | `false` | — | — | Set to true after [[TempVerification]] is consumed. | | `isEmailVerified` | Boolean | no | `false` | — | — | Set to true after [[TempVerification]] is consumed. |
| `authProvider` | String | yes | `"email"` | enum: `email` / `google` / `telegram` | yes | Provider used to create the account. Existing email/password accounts remain `email`; Telegram-only users are `telegram`. |
| `telegramVerified` | Boolean | no | `false` | — | — | Set when Telegram identity has been signature-verified and linked through `TelegramLink`. |
| `emailVerificationToken` | String | no | — | — | — | Legacy token-based email verification. | | `emailVerificationToken` | String | no | — | — | — | Legacy token-based email verification. |
| `emailVerificationCode` | String | no | — | — | — | OTP code for email verification. | | `emailVerificationCode` | String | no | — | — | — | OTP code for email verification. |
| `emailVerificationCodeExpires` | Date | no | — | — | — | Expiry for `emailVerificationCode`. | | `emailVerificationCodeExpires` | Date | no | — | — | — | Expiry for `emailVerificationCode`. |
@@ -56,15 +58,15 @@ The core identity document for every actor in the marketplace: buyers, sellers,
| `status` | String | no | `"active"` | enum: `active` / `suspended` / `deleted` | yes | Soft-delete and moderation flag. | | `status` | String | no | `"active"` | enum: `active` / `suspended` / `deleted` | yes | Soft-delete and moderation flag. |
| `lastLoginAt` | Date | no | — | — | — | Updated by auth middleware. | | `lastLoginAt` | Date | no | — | — | — | Updated by auth middleware. |
| `refreshTokens[]` | String[] | no | `[]` | — | — | Outstanding JWT refresh tokens. | | `refreshTokens[]` | String[] | no | `[]` | — | — | Outstanding JWT refresh tokens. |
| `referralCode` | String | no | — | — | unique, sparse | Personal invite code. | | `referralCode` | String | no | — | — | unique, sparse | **Not yet implemented** in `User.ts` — planned for referral programme. |
| `referredBy` | ObjectId → User | no | — | — | yes | Who invited this user. | | `referredBy` | ObjectId → User | no | — | — | yes | **Not yet implemented** in `User.ts` — planned for referral programme. |
| `points.total` | Number | no | `0` | — | — | Lifetime points earned. | | `points.total` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts` — planned for loyalty system. |
| `points.available` | Number | no | `0` | — | — | Currently spendable. | | `points.available` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
| `points.used` | Number | no | `0` | — | — | Cumulative spent. | | `points.used` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
| `points.level` | Number | no | `1` | — | yes (`points.level`) | Resolved against [[LevelConfig]]. | | `points.level` | Number | no | `1` | — | yes (`points.level`) | **Not yet implemented** in `User.ts` — planned for [[LevelConfig]] lookup. |
| `referralStats.totalReferrals` | Number | no | `0` | — | — | Count of invited users. | | `referralStats.totalReferrals` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
| `referralStats.activeReferrals` | Number | no | `0` | — | — | Subset that became active buyers. | | `referralStats.activeReferrals` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
| `referralStats.totalEarned` | Number | no | `0` | — | — | Cumulative reward earnings. | | `referralStats.totalEarned` | Number | no | `0` | — | — | **Not yet implemented** in `User.ts`. |
| `createdAt` | Date | auto | — | — | — | Mongoose timestamp. | | `createdAt` | Date | auto | — | — | — | Mongoose timestamp. |
| `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. | | `updatedAt` | Date | auto | — | — | — | Mongoose timestamp. |
@@ -76,13 +78,15 @@ The core identity document for every actor in the marketplace: buyers, sellers,
## Indexes ## Indexes
Defined explicitly (in addition to the implicit `email` unique index): Defined explicitly:
- `{ role: 1 }` `backend/src/models/User.ts:231` - `{ email: 1 }` unique sparse — allows multiple Telegram-only users without email while preserving uniqueness for email-bearing users.
- `{ status: 1 }``backend/src/models/User.ts:232` - `{ role: 1 }``backend/src/models/User.ts:178`
- `{ referralCode: 1 }``backend/src/models/User.ts:233` - `{ status: 1 }``backend/src/models/User.ts:179`
- `{ referredBy: 1 }``backend/src/models/User.ts:234` - `{ authProvider: 1 }` — supports provider-level account reporting and cleanup.
- `{ 'points.level': 1 }``backend/src/models/User.ts:235`
> [!warning] Missing indexes
> The schema currently defines only `role` and `status` indexes. The `referralCode`, `referredBy`, and `points.level` indexes documented below are **not yet present** in `User.ts`:
## Pre/Post Hooks ## Pre/Post Hooks

View File

@@ -15,10 +15,10 @@ This page is the entry point for the API. See the individual service pages for e
- [[Payment API]] - SHKeeper, Web3, DePay, payouts - [[Payment API]] - SHKeeper, Web3, DePay, payouts
- [[Chat API]] - conversations and messages - [[Chat API]] - conversations and messages
- [[Notification API]] - in-app notifications - [[Notification API]] - in-app notifications
- [[Dispute API]] - dispute resolution - [[Dispute API]] - dispute resolution *(planned, not yet implemented)*
- [[Blog API]] - blog posts (admin-managed) - [[Blog API]] - blog posts *(planned, not yet implemented)*
- [[Admin API]] - user management, data cleanup, manual ops - [[Admin API]] - user management, data cleanup *(planned, not yet implemented)*
- [[Points API]] - loyalty points, levels, referrals - [[Points API]] - loyalty points, levels, referrals *(planned, not yet implemented)*
- [[AI API]] - OpenAI-backed text endpoints - [[AI API]] - OpenAI-backed text endpoints
- [[File API]] - upload, delete, serve - [[File API]] - upload, delete, serve
- [[Socket Events]] - real-time events - [[Socket Events]] - real-time events
@@ -133,9 +133,16 @@ Sort parameters: list endpoints commonly accept `sortBy` (default `createdAt`) a
## Rate limiting ## Rate limiting
Rate limiting is currently **disabled** in the deployed code (`app.ts` logs `🔓 Rate limiting COMPLETELY DISABLED for personal use`). The intended policy when re-enabled is **100 requests per 15-minute window per IP**, applied per `/api/*` route. The Express `trust proxy` setting is enabled in production so the real client IP is read from `X-Forwarded-For` (Nginx terminator). Rate limiting is **enabled** as of the 2026-05-24 remediation. Four tiers are active in `backend/src/app.ts`:
Redis-backed rate limiting helpers exist in `src/services/redis/rateLimitService.ts` and are used by sensitive auth flows (password reset, email verification) even with the global limiter off. | Tier | Scope | Limit | Window |
| --- | --- | --- | --- |
| Global | All `/api/*` routes (except `/health` and Request-Network webhooks) | 100 req | 15 min per IP |
| Auth | `/api/auth/*` | 10 req | 15 min per IP |
| Payment | `/api/payment/*` | 30 req | 15 min per IP |
| AI | `/api/ai/*` | 20 req | 15 min per IP |
The Express `trust proxy` setting is enabled in production so the real client IP is read from `X-Forwarded-For` (Nginx terminator).
## CORS ## CORS
@@ -156,7 +163,14 @@ Uploaded files served from `/uploads/*` use `helmet({ crossOriginResourcePolicy:
## Real-time channel ## Real-time channel
Socket.IO runs on the same HTTP server. The frontend should connect to the API origin with credentials. Clients join rooms via `join-user-room`, `join-request-room`, `join-seller-room`, `join-buyer-room`, `join-chat-room`. Full catalog: [[Socket Events]]. Socket.IO runs on the same HTTP server. **All connections require a valid JWT** passed in `socket.auth.token` (or `socket.handshake.query.token`). The server verifies the token with `jwt.verify(config.jwtSecret)` before allowing the connection. Room-join events enforce strict membership checks:
- `join-user-room` — only the authenticated user may join their own room.
- `join-request-room` — buyer, preferred seller, or offer submitter only.
- `join-seller-room` / `join-buyer-room` — only the matching user id.
- `join-chat-room` — active participant only.
Clients join rooms via `join-user-room`, `join-request-room`, `join-seller-room`, `join-buyer-room`, `join-chat-room`. Full catalog: [[Socket Events]].
## Standard HTTP status codes ## Standard HTTP status codes

View File

@@ -121,6 +121,37 @@ Two distinct identities are involved: a [[User]] (`models/User.ts`) and a [[Temp
- Pushes refresh token onto `user.refreshTokens`. - Pushes refresh token onto `user.refreshTokens`.
- Redis session start via `sessionService`. - Redis session start via `sessionService`.
### POST /api/auth/telegram
**Description:** First-class Telegram authentication. Accepts Telegram Mini App `initData` or a Telegram Login Widget payload, verifies the Telegram signature server-side, and signs the user into Amanat without requiring email or password.
**Auth required:** No
**Request body:**
```ts
// Mini App
{ initData: string; role?: "buyer" | "seller" }
// Login Widget
{ loginWidget: { id: string; first_name?: string; username?: string; auth_date: string; hash: string }; role?: "buyer" | "seller" }
```
**Response 200/201:**
```json
{
"success": true,
"data": {
"user": { "_id": "...", "authProvider": "telegram", "telegramVerified": true },
"tokens": { "accessToken": "...", "refreshToken": "..." },
"isNewUser": true,
"telegram": { "userId": "10001", "username": "alice", "source": "miniapp" }
}
}
```
**Errors:** `400` missing payload, `401` invalid/stale signature, `403` blocked Telegram account or inactive Amanat account, `409 TELEGRAM_REPLAY` reused Mini App `initData`, `429` rate-limited.
**Side effects:**
- Creates a Telegram-only [[User]] when no active `TelegramLink` exists. The user has no email, `authProvider: "telegram"`, and `telegramVerified: true`.
- Upserts `TelegramLink` for the Telegram ID and updates last-seen metadata.
- Stores the refresh token on the user document.
- Does not expose phone numbers; Telegram phone data is not requested or persisted.
### POST /api/auth/refresh-token ### POST /api/auth/refresh-token
**Description:** Exchanges a refresh token for a new access token. Rotates the refresh token. **Description:** Exchanges a refresh token for a new access token. Rotates the refresh token.
@@ -297,9 +328,11 @@ Routes are nested under `/api/auth/` via `passkeyRoutes`. Service: `passkeyServi
| HTTP | App code | Meaning | | HTTP | App code | Meaning |
| --- | --- | --- | | --- | --- | --- |
| 400 | `Validation Error` | `express-validator` rejected the body | | 400 | `Validation Error` | `express-validator` rejected the body |
| 401 | — | Bad credentials / missing token | | 401 | — | Bad credentials / missing token / invalid Telegram signature |
| 403 | — | Email not verified or insufficient role | | 403 | — | Email not verified, insufficient role, or blocked Telegram account |
| 409 | `USER_EXISTS` | Email already in use | | 409 | `USER_EXISTS` | Email already in use |
| 409 | `TELEGRAM_REPLAY` | Reused Telegram Mini App `initData` (replay protection) |
| 423 | — | Account temporarily locked after failed logins | | 423 | — | Account temporarily locked after failed logins |
| 429 | — | Rate-limited (auth tier: 10 req / 15 min / IP) |
See [[Error Codes]] for the global error shape. See [[Error Codes]] for the global error shape.

View File

@@ -5,7 +5,10 @@ tags: [api, dispute, reference]
# Dispute API # Dispute API
Endpoints live under `/api/disputes/*`. The router is [`backend/src/routes/disputeRoutes.ts`](../../backend/src/routes/disputeRoutes.ts) and delegates to `DisputeController` ([`backend/src/controllers/disputeController.ts`](../../backend/src/controllers/disputeController.ts)). The router applies `authenticateToken` globally — every endpoint requires `Bearer JWT`. > [!warning] Not implemented
> The Dispute module is **documented but not yet implemented** in the backend. There is no `backend/src/services/dispute/` directory, no `backend/src/routes/disputeRoutes.ts`, and no `/api/disputes` mount in `app.ts`. The API specification below reflects the *intended* design only.
Endpoints are planned to live under `/api/disputes/*`. The router would be `backend/src/routes/disputeRoutes.ts` and delegate to `DisputeController` (`backend/src/controllers/disputeController.ts`). The router would apply `authenticateToken` globally — every endpoint requires `Bearer JWT`.
Model: [[Dispute]]. A dispute references a [[PurchaseRequest]] plus optional [[Payment]] and is the input to the mediation workflow that ends in either a `resolved_buyer` or `resolved_seller` decision and triggers an escrow release or refund via the [[Payment API]]. Model: [[Dispute]]. A dispute references a [[PurchaseRequest]] plus optional [[Payment]] and is the input to the mediation workflow that ends in either a `resolved_buyer` or `resolved_seller` decision and triggers an escrow release or refund via the [[Payment API]].

View File

@@ -301,7 +301,7 @@ Payouts are SHKeeper-side outbound transfers (admin pays the seller from a hot w
### POST /api/payment/decentralized/save ### POST /api/payment/decentralized/save
**Description:** Persists a Web3-initiated payment record. **Description:** Persists a Web3-initiated payment record.
**Auth required:** No **Auth required:** Bearer JWT (enforces `userId` ownership match)
**Request body:** **Request body:**
```ts ```ts
{ {
@@ -326,7 +326,7 @@ Payouts are SHKeeper-side outbound transfers (admin pays the seller from a hot w
### PUT /api/payment/decentralized/update ### PUT /api/payment/decentralized/update
**Description:** Update a decentralized payment's status / confirmations. **Description:** Update a decentralized payment's status / confirmations.
**Auth required:** No **Auth required:** Bearer JWT (owner or admin)
**Request body:** `{ paymentId, status, confirmations? }` **Request body:** `{ paymentId, status, confirmations? }`
### GET /api/payment/decentralized/receiver ### GET /api/payment/decentralized/receiver
@@ -337,17 +337,17 @@ Payouts are SHKeeper-side outbound transfers (admin pays the seller from a hot w
### GET /api/payment/decentralized/history/:userId ### GET /api/payment/decentralized/history/:userId
**Description:** Decentralized payment history for a user. **Description:** Decentralized payment history for a user.
**Auth required:** No **Auth required:** Bearer JWT (self or admin)
### POST /api/payment/decentralized/verify/:paymentId ### POST /api/payment/decentralized/verify/:paymentId
**Description:** Re-verifies a single decentralized payment against the chain. **Description:** Re-verifies a single decentralized payment against the chain.
**Auth required:** No **Auth required:** Bearer JWT (owner or admin)
### POST /api/payment/decentralized/verify-all-pending ### POST /api/payment/decentralized/verify-all-pending
**Description:** Iterates all `pending` decentralized payments and re-verifies them. **Description:** Iterates all `pending` decentralized payments and re-verifies them.
**Auth required:** No (typically called by a cron) **Auth required:** Bearer JWT (admin only)
### POST /api/payment/decentralized/admin-payout ### POST /api/payment/decentralized/admin-payout
@@ -371,6 +371,7 @@ Payouts are SHKeeper-side outbound transfers (admin pays the seller from a hot w
- `pending` - intent created, awaiting on-chain settlement - `pending` - intent created, awaiting on-chain settlement
- `processing` - settlement seen, awaiting confirmations - `processing` - settlement seen, awaiting confirmations
- `confirmed` - fully credited (intermediate; sometimes skipped)
- `completed` - confirmed, escrow funded - `completed` - confirmed, escrow funded
- `failed` - intentionally failed (expired, declined, refused) - `failed` - intentionally failed (expired, declined, refused)
- `cancelled` - cancelled by user/admin - `cancelled` - cancelled by user/admin

View File

@@ -0,0 +1,187 @@
---
title: Trezor API
tags: [api, payments, trezor, safekeeping]
---
# Trezor API
The Trezor API is mounted at `/api/trezor`. It is optional support for hardware-backed safekeeping and does not replace SHKeeper or Request Network.
Enforcement is controlled by:
```env
TREZOR_SAFEKEEPING_REQUIRED=false
```
Only the literal value `true` makes Trezor proof mandatory during release/refund confirmation. When unset or `false`, release/refund flows continue without Trezor proof.
## GET /api/trezor/registration-message
Builds the exact message the user must sign to register a Trezor xpub.
Auth: bearer JWT
Query:
```text
xpub=<extended public key>
registrationAddress=<first derived address>
```
Response:
```json
{
"success": true,
"data": {
"message": "Amanat escrow Trezor registration\n..."
}
}
```
## POST /api/trezor/register
Registers a Trezor xpub after the first derived address signs the registration challenge.
Auth: bearer JWT
Body:
```json
{
"xpub": "xpub...",
"registrationAddress": "0x...",
"proofMessage": "Amanat escrow Trezor registration\n...",
"proofSignature": "0x...",
"basePath": "m/44'/60'/0'",
"deviceLabel": "Office Trezor"
}
```
Validation:
- Rejects private extended keys (`xprv`, `tprv`).
- Requires `registrationAddress` to equal xpub-derived `m/44'/60'/0'/0/0`.
- Requires `proofSignature` to recover `registrationAddress`.
Response:
```json
{
"success": true,
"data": {
"xpubFingerprint": "0x...",
"registrationAddress": "0x...",
"basePath": "m/44'/60'/0'",
"nextAddressIndex": 1
}
}
```
## GET /api/trezor/account
Returns the caller's active Trezor registration summary.
Auth: bearer JWT
Response when absent:
```json
{
"success": true,
"data": {
"registered": false
}
}
```
## POST /api/trezor/addresses/next
Allocates or returns a deterministic receive address from the registered xpub.
Auth: bearer JWT
Body:
```json
{
"purpose": "deposit",
"paymentId": "..."
}
```
If `paymentId` already has an assigned address, the same address is returned. Otherwise the backend derives:
```text
m/44'/60'/0'/0/{nextAddressIndex}
```
## POST /api/trezor/operation-message
Builds the exact transaction-intent message an admin must sign when Trezor safekeeping is enabled.
Auth: bearer JWT, admin
Body:
```json
{
"operation": "release",
"paymentId": "...",
"transactionHash": "0x...",
"amount": 100,
"currency": "USDT",
"provider": "request.network"
}
```
Response:
```json
{
"success": true,
"data": {
"message": "Amanat escrow Trezor transaction approval\n..."
}
}
```
## POST /api/trezor/verify-operation
Verifies a signed operation intent against the admin's registered Trezor safekeeping address.
Auth: bearer JWT, admin
Body:
```json
{
"payload": {
"operation": "release",
"paymentId": "...",
"transactionHash": "0x...",
"amount": 100,
"currency": "USDT",
"provider": "request.network"
},
"message": "Amanat escrow Trezor transaction approval\n...",
"signature": "0x..."
}
```
## Release / Refund Integration
When `TREZOR_SAFEKEEPING_REQUIRED=true`, release/refund confirmation bodies must include the Trezor proof:
```json
{
"txHash": "0x...",
"amount": 100,
"trezor": {
"message": "Amanat escrow Trezor transaction approval\n...",
"signature": "0x..."
}
}
```
This proof is optional when enforcement is disabled.

View File

@@ -95,10 +95,27 @@ sequenceDiagram
| Method | Endpoint | Source | | Method | Endpoint | Source |
|---|---|---| |---|---|---|
| `POST` | `/api/auth/login` | `backend/src/services/auth/authRoutes.ts:22``authController.login` | | `POST` | `/api/auth/login` | `backend/src/services/auth/authRoutes.ts:22``authController.login` |
| `POST` | `/api/auth/telegram` | `authRoutes.ts``authController.telegramAuth` |
| `POST` | `/api/auth/refresh-token` | `authRoutes.ts:24-27``authController.refreshToken` | | `POST` | `/api/auth/refresh-token` | `authRoutes.ts:24-27``authController.refreshToken` |
| `POST` | `/api/auth/logout` | `authRoutes.ts:68``authController.logout` (protected) | | `POST` | `/api/auth/logout` | `authRoutes.ts:68``authController.logout` (protected) |
| `GET` | `/api/auth/profile` | `authRoutes.ts:69``authController.getProfile` | | `GET` | `/api/auth/profile` | `authRoutes.ts:69``authController.getProfile` |
## Telegram first-class auth flow
Telegram is now a peer auth provider alongside email/password, Google, and passkeys.
1. The Telegram Mini App shell reads raw signed launch data from `window.Telegram.WebApp.initData`; browser login can submit a Telegram Login Widget payload.
2. The frontend posts the raw signed payload to `POST /api/auth/telegram`; it never trusts `initDataUnsafe` for authentication.
3. Backend verifies the Telegram signature:
- Mini App uses `verifyMiniAppInitData()` with the Telegram WebAppData HMAC algorithm.
- Login Widget uses `verifyTelegramLoginWidget()` with the Telegram Login Widget HMAC algorithm.
4. Backend rejects stale `auth_date`, bot accounts, replayed Mini App `initData`, and blocked `TelegramLink` records.
5. If an active `TelegramLink` exists, backend signs in that Amanat user. If no link exists, backend creates a Telegram-only user with nullable email, `authProvider: "telegram"`, `telegramVerified: true`, then creates the link.
6. Backend issues the standard access token and refresh token pair. The frontend stores them in `localStorage` under `accessToken` and `refreshToken`, then hydrates the current session.
7. If `isNewUser=true`, the Mini App opens a lightweight onboarding overlay and points the user to account settings for optional email, language, currency, and wallet details.
High-risk actions are unchanged: escrow release, refund, dispute-sensitive, and wallet-sensitive operations still use the existing protected backend authorization and step-up gates. Telegram auth only establishes the user session.
## Database writes ## Database writes
- **`users` collection**: `lastLoginAt` updated; `refreshTokens` array gains one entry per successful login or refresh. - **`users` collection**: `lastLoginAt` updated; `refreshTokens` array gains one entry per successful login or refresh.

View File

@@ -15,7 +15,9 @@ When something goes wrong (item not delivered, wrong item, fraud), either party
- **Seller** — party against whom the dispute is raised (or in rarer cases, initiator). - **Seller** — party against whom the dispute is raised (or in rarer cases, initiator).
- **Admin / Mediator** — assigned to investigate. - **Admin / Mediator** — assigned to investigate.
- **Frontend** — buyer/seller "Report issue" buttons in the request detail view; admin dispute dashboard. - **Frontend** — buyer/seller "Report issue" buttons in the request detail view; admin dispute dashboard.
- **Backend** — `DisputeService` (`backend/src/services/dispute/DisputeService.ts`), `DisputeController` (`backend/src/controllers/disputeController.ts`), routes at `backend/src/routes/disputeRoutes.ts`. - **Backend** — `DisputeService` (`backend/src/services/dispute/DisputeService.ts` *(planned)*), `DisputeController` (`backend/src/controllers/disputeController.ts` *(planned)*), routes at `backend/src/routes/disputeRoutes.ts` *(planned)*.
> [!warning] Not implemented
> None of these files exist as of 2026-05-24. The dispute module is planned but not yet built.
- **MongoDB** — `disputes`, `chats`, `purchaserequests`, `payments`. - **MongoDB** — `disputes`, `chats`, `purchaserequests`, `payments`.
- **Socket.IO** — `new-notification`, `new-message`, `dispute-updated` (planned). - **Socket.IO** — `new-notification`, `new-message`, `dispute-updated` (planned).
@@ -38,7 +40,7 @@ stateDiagram-v2
closed --> [*] closed --> [*]
``` ```
Resolution actions (from `Dispute.resolution.action` enum, see `Dispute.ts`): `refund`, `partial`, `release`, `reject` (the wording differs slightly in the model — verify with `backend/src/models/Dispute.ts`). Resolution actions (from `Dispute.resolution.action` enum, see `Dispute.ts` *(intended design)*): `refund`, `partial`, `release`, `reject`.
## Step-by-step narrative ## Step-by-step narrative
@@ -193,9 +195,12 @@ All require `authenticateToken` (router-level middleware).
## Source files ## Source files
- Backend: `backend/src/services/dispute/DisputeService.ts` > [!warning] Not implemented
- Backend: `backend/src/controllers/disputeController.ts` > None of the backend files below exist as of 2026-05-24. The dispute module is planned but not yet built.
- Backend: `backend/src/routes/disputeRoutes.ts`
- Backend: `backend/src/models/Dispute.ts` - Backend: `backend/src/services/dispute/DisputeService.ts` *(planned)*
- Backend: `backend/src/controllers/disputeController.ts` *(planned)*
- Backend: `backend/src/routes/disputeRoutes.ts` *(planned)*
- Backend: `backend/src/models/Dispute.ts` *(planned)*
- Frontend: `frontend/src/sections/request/components/report-problem-to-admin.tsx` - Frontend: `frontend/src/sections/request/components/report-problem-to-admin.tsx`
- Frontend: admin dispute dashboard under `frontend/src/sections/admin/` (subject to organisation) - Frontend: admin dispute dashboard under `frontend/src/sections/admin/` (subject to organisation)

View File

@@ -19,7 +19,7 @@ The escrow is not a separate smart contract — it is a **state machine on the `
## Escrow state machine (`Payment.escrowState`) ## Escrow state machine (`Payment.escrowState`)
Enum from `Payment.ts:112-115`: `funded | releasable | released | refunded | releasing | failed`. Enum from `Payment.ts:112-115`: `funded | releasable | released | refunded | releasing | failed | cancelled | partial`.
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
@@ -33,9 +33,12 @@ stateDiagram-v2
Releasing --> Failed: payout tx reverted\nescrowState="failed" Releasing --> Failed: payout tx reverted\nescrowState="failed"
Funded --> Refunded: dispute resolution = refund\nescrowState="refunded" Funded --> Refunded: dispute resolution = refund\nescrowState="refunded"
Funded --> Refunded: order cancelled\npre-shipment Funded --> Refunded: order cancelled\npre-shipment
Pending --> Cancelled: webhook EXPIRED/CANCELLED
escrowState="cancelled"
Failed --> Releasing: admin retries Failed --> Releasing: admin retries
Released --> [*] Released --> [*]
Refunded --> [*] Refunded --> [*]
Cancelled --> [*]
``` ```
`Payment.status` mirrors a coarser business state: `Payment.status` mirrors a coarser business state:

View File

@@ -35,7 +35,7 @@ Alternative pay-in path: instead of routing through [[Payment Flow - SHKeeper]],
### Phase 2 — Create intent on backend ### Phase 2 — Create intent on backend
4. Frontend POSTs `POST /api/payment/decentralized/create` with `{ purchaseRequestId, sellerOfferId, amount, fromAddress: wallet.address, token: 'USDT', network: 'bsc' }`. The backend records a `Payment` with `provider: 'other'` (or `'decentralized'` depending on enum extension), `direction: 'in'`, `status: 'pending'`, `blockchain.{network, token, sender, receiver: ESCROW_WALLET_ADDRESS}`. 4. Frontend POSTs `POST /api/payment/decentralized/save` with `{ purchaseRequestId, sellerOfferId, amount, fromAddress: wallet.address, token: 'USDT', network: 'bsc' }`. The backend records a `Payment` with `provider: 'other'` (or `'decentralized'` depending on enum extension), `direction: 'in'`, `status: 'pending'`, `blockchain.{network, token, sender, receiver: ESCROW_WALLET_ADDRESS}`. **Auth:** Bearer JWT required.
5. Response includes the **escrow wallet address** and the exact token amount (in decimals — for USDT-BEP20 that's 18 decimals; the helper `convertPaymentAmountForShkeeper` is shared from `currencyUtils.ts`). 5. Response includes the **escrow wallet address** and the exact token amount (in decimals — for USDT-BEP20 that's 18 decimals; the helper `convertPaymentAmountForShkeeper` is shared from `currencyUtils.ts`).
### Phase 3 — Token approval (ERC-20 / BEP-20) ### Phase 3 — Token approval (ERC-20 / BEP-20)
@@ -52,7 +52,7 @@ Alternative pay-in path: instead of routing through [[Payment Flow - SHKeeper]],
### Phase 5 — Backend verification ### Phase 5 — Backend verification
11. Frontend POSTs `POST /api/payment/decentralized/verify` with `{ paymentId, transactionHash }`. 11. Frontend POSTs `POST /api/payment/decentralized/verify/:paymentId` with `{ transactionHash }`. **Auth:** Bearer JWT required (owner or admin).
12. Backend `BSCTransactionVerifier.verifyTransaction(txHash)` (`decentralizedPaymentService.ts`): 12. Backend `BSCTransactionVerifier.verifyTransaction(txHash)` (`decentralizedPaymentService.ts`):
- JSON-RPC `eth_getTransactionReceipt` against `bsc-dataseed.binance.org`. - JSON-RPC `eth_getTransactionReceipt` against `bsc-dataseed.binance.org`.
- Confirms `receipt.status === '0x1'` (success). - Confirms `receipt.status === '0x1'` (success).

View File

@@ -179,11 +179,14 @@ sequenceDiagram
## Linked flows ## Linked flows
- [[Authentication Flow]] — the next time the user signs in. - [[Authentication Flow]] — the next time the user signs in (includes the Telegram first-class auth section).
- [[Referral Flow]] — full points-awarding mechanics triggered here. - [[Referral Flow]] — full points-awarding mechanics triggered here.
- [[Google OAuth Flow]] — alternative path that bypasses `TempVerification` (Google identities are pre-verified). - [[Google OAuth Flow]] — alternative path that bypasses `TempVerification` (Google identities are pre-verified).
- [[Password Reset Flow]] — if the user forgets the password they set during verification. - [[Password Reset Flow]] — if the user forgets the password they set during verification.
> [!tip] Telegram — zero-step registration
> Users who open the Amanat Telegram Mini App do **not** go through this flow at all. `POST /api/auth/telegram` verifies the Telegram-signed `initData` and auto-provisions a new `User` (no email, `authProvider: "telegram"`) in a single round-trip. The `TempVerification` + email code cycle only applies to email-based sign-ups. See [[Authentication Flow#Telegram first-class auth flow]].
## Source files ## Source files
- Backend: `backend/src/services/auth/authController.ts:33-158` (register), `:364-469` (verify), `:498-539` (resend) - Backend: `backend/src/services/auth/authController.ts:33-158` (register), `:364-469` (verify), `:498-539` (resend)

View File

@@ -0,0 +1,119 @@
# Trezor Safekeeping Flow
This flow adds hardware-backed custody controls without replacing the current payment model. The backend never stores private keys. Trezor support starts as a single hardware signer and is designed to upgrade to multisig later.
Default mode: optional. Existing release/refund flows do not require Trezor proof unless `TREZOR_SAFEKEEPING_REQUIRED=true`.
## Goals
- Generate a fresh receive address per user/payment from a registered Trezor xpub.
- Require a Trezor-produced signature before release/refund confirmation when safekeeping enforcement is enabled.
- Keep SHKeeper and Request Network optional provider paths intact.
- Preserve the existing `Payment` model and orchestration surface.
## Registration
1. User connects a Trezor in the frontend and exports an Ethereum account xpub, for example `m/44'/60'/0'`.
2. Backend builds a registration challenge:
- `GET /api/trezor/registration-message?xpub=...&registrationAddress=...`
3. The registration address must be the first derived address from the xpub:
- `m/44'/60'/0'/0/0`
4. User signs the challenge with that Trezor address.
5. Frontend submits:
- `POST /api/trezor/register`
- `xpub`
- `registrationAddress`
- `proofMessage`
- `proofSignature`
- optional `basePath`, `deviceLabel`
6. Backend verifies:
- xpub is public, not private.
- registration address matches xpub-derived index `0`.
- signature recovers the registration address.
7. Backend stores only:
- `userId`
- xpub fingerprint
- xpub
- base derivation path
- registration address
- next address index
- issued address records
## Address Generation
To issue the next payment address:
```http
POST /api/trezor/addresses/next
{
"purpose": "deposit",
"paymentId": "..."
}
```
The backend derives non-hardened receive addresses from the registered xpub:
```text
m/44'/60'/0'/0/{index}
```
If a `paymentId` already has an address, the endpoint returns the same address instead of incrementing the index.
## Transaction Approval
Before a release/refund confirmation, the admin asks the backend for the exact operation message:
```http
POST /api/trezor/operation-message
{
"operation": "release",
"paymentId": "...",
"transactionHash": "0x...",
"amount": 100,
"currency": "USDT",
"provider": "request.network"
}
```
The Trezor signs that message. Release/refund confirmation then includes:
```json
{
"txHash": "0x...",
"trezor": {
"message": "Amanat escrow Trezor transaction approval\n...",
"signature": "0x..."
}
}
```
When `TREZOR_SAFEKEEPING_REQUIRED=true`, `confirmReleaseRefundInstruction` verifies the signature before calling the payment adapter confirmation path.
## Enforcement Flag
```env
TREZOR_SAFEKEEPING_REQUIRED=false
```
Default is permissive so existing SHKeeper and Request Network flows continue to work. Set it to `true` only after registering the operating admin's Trezor account and testing the signing path. Any value other than the literal string `true` is treated as disabled.
## Safety Rules
- Never store Trezor seed words, private keys, or xprv/tprv values.
- Reject private extended keys at registration.
- Verify every signature locally before accepting it.
- Use exact transaction-intent messages; do not accept free-form signatures.
- Treat generated deposit addresses as public routing metadata, not as proof of payment.
- Keep ledger availability checks enabled for release/refund accounting.
## Upgrade Path To Multisig
The current design stores a single `trezor-eoa` signer. Later, replace the signer policy with:
- `addressType: safe-multisig`
- a Safe address per tenant/admin group
- threshold policy, such as `2-of-3`
- Trezor owners as Safe signers
- release/refund flow creates a Safe transaction and records collected signatures before execution
The payment orchestration API should stay the same: build instruction, collect hardware-backed approval, confirm release/refund, append ledger entry.

View File

@@ -102,6 +102,23 @@ SHKeeper is the crypto payment gateway. See [[Payment Flow]] and [[SHKeeper Inte
--- ---
## Payments — Provider Selection
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `PAYMENT_PROVIDER` | backend | optional | `shkeeper` | `request.network` | Active provider for new payment intents |
| `PAYMENT_DEFAULT_PROVIDER` | backend | optional | `shkeeper` | `shkeeper` | Fallback alias when `PAYMENT_PROVIDER` is unset |
| `PAYMENT_ENABLED_PROVIDERS` | backend | optional | `shkeeper` | `shkeeper,request.network` | Comma-separated providers allowed at runtime |
| `PAYMENT_ROLLBACK_PROVIDER` | backend | optional | `shkeeper` | `shkeeper` | Provider used when selected provider is not enabled |
| `PAYMENT_PROVIDER_MODE` | backend | optional | `live` | `dry-run` | Provider mode: `live`, `dry-run`, or `read-only` |
| `REQUEST_NETWORK_ENABLED` | backend | optional | `false` | `true` | Adds `request.network` to enabled providers when no explicit list is set |
| `PAYMENT_REQUEST_NETWORK_COHORT_PERCENT` | backend | optional | `0` | `10` | Percent of new checkout cohort eligible for Request Network |
| `PAYMENT_LEDGER_ENFORCEMENT` | backend | optional | `false` | `true` | Enforce ledger gates for release/refund |
| `PAYMENT_RECONCILIATION_ENABLED` | backend | optional | `false` | `true` | Enable scheduled provider reconciliation jobs |
| `TREZOR_SAFEKEEPING_REQUIRED` | backend | optional | `false` | `true` | Optional hardware-signature gate for release/refund confirmation. Only the literal value `true` enforces Trezor proof. |
---
## Payments — DePay / Web3 (frontend) ## Payments — DePay / Web3 (frontend)
| Name | Repo | Required | Default | Example | Purpose | | Name | Repo | Required | Default | Example | Purpose |
@@ -249,6 +266,13 @@ SMTP_PASS=
SMTP_FROM="AMN <no-reply@amn.gg>" SMTP_FROM="AMN <no-reply@amn.gg>"
# SHKeeper (set when ready) # SHKeeper (set when ready)
PAYMENT_PROVIDER=shkeeper
PAYMENT_ENABLED_PROVIDERS=shkeeper
PAYMENT_ROLLBACK_PROVIDER=shkeeper
REQUEST_NETWORK_ENABLED=false
PAYMENT_LEDGER_ENFORCEMENT=false
PAYMENT_RECONCILIATION_ENABLED=false
TREZOR_SAFEKEEPING_REQUIRED=false
SHKEEPER_BASE_URL= SHKEEPER_BASE_URL=
SHKEEPER_API_URL= SHKEEPER_API_URL=
SHKEEPER_API_KEY= SHKEEPER_API_KEY=

View File

@@ -0,0 +1,311 @@
---
title: PRD — TON Wallet Ownership Proof
tags: [prd, ton, wallet, security, telegram]
created: 2026-05-24
status: backend-implemented
priority: high
---
# PRD — TON Wallet Ownership Proof
## Background
TON wallet connect was shipped as a first-pass integration: the backend validates address format and stores it, but does not require the user to prove they control the private key behind the address. Any user can submit any valid-looking TON address and it will be saved to their profile.
This matters for:
- **Seller payout routing** — if a seller's wallet address was spoofed, funds go to the wrong address.
- **On-chain proof of identity** — downstream features (NFT-gated access, on-chain reputation) require a real ownership proof.
- **Compliance** — KYC/AML flows that tie a real-world identity to a wallet need proof, not self-report.
Until proof is implemented, saved TON wallet addresses are self-reported metadata only and must not be used for automatic payout routing.
---
## Goal
Replace the format-only TON wallet save with a full TonConnect ownership proof flow:
- Backend issues a challenge nonce scoped to the user session.
- Frontend passes the challenge to TonConnect UI, which asks the user to sign it.
- Backend verifies the returned proof against the wallet's public key.
- On success, stores `walletProofVerified: true`, proof timestamp, and wallet provider.
---
## Non-Goals
- On-chain transaction signing or payment initiation (separate feature).
- Requiring proof for EVM wallets (already covered by `ethers.verifyMessage`).
- TON DNS or NFT resolution.
- Mandatory wallet for all users (wallet remains optional profile metadata).
---
## User Stories
| As a… | I want to… | So that… |
|-------|-----------|----------|
| Seller | Connect my Telegram Wallet with proof | Payments route to the right address |
| Buyer | Know my counterparty's wallet is verified | I trust the seller's payout address |
| Admin | See `walletProofVerified` on user profiles | I can audit wallet verification status |
| Developer | Have a clear nonce/challenge API | I can build additional proof-gated features |
---
## Flow
```
Frontend Backend TonConnect
| | |
|-- POST /wallet-address/ton-proof/challenge --> |
| |-- generate nonce |
| |-- store nonce (TTL 5 min) |
|<-- { payload, domain, timestamp } -- |
| | |
|-- tonConnectUI.connectWallet({ tonProof: payload }) ---------->|
| | |-- user signs proof
|<-- proof object (wallet, stateInit, signature) ---------------|
| | |
|-- PATCH /wallet-address { walletType:'ton', tonProof } --> |
| |-- verify proof |
| | - reconstruct message |
| | - verify against pubkey |
| | - check domain + nonce |
| |-- store wallet + proof meta |
|<-- { walletProofVerified: true } -- |
```
---
## Backend Changes
### 1. New endpoint: `POST /api/user/wallet-address/ton-proof/challenge`
**Auth:** Bearer JWT required.
**Request body:** none.
**Response:**
```json
{
"payload": "aGV4LWVuY29kZWQtbm9uY2U...",
"domain": "amn.gg",
"timestamp": 1716560000
}
```
**Implementation:**
- Generate 32-byte random nonce, hex-encode.
- Store in a TTL map (or Redis `SET NX EX 300`) keyed by `userId`.
- Return `{ payload: nonce, domain: "amn.gg", timestamp: now }`.
- One active challenge per user — issuing a new one invalidates the old.
---
### 2. Updated: `PATCH /api/user/wallet-address`
**When `walletType = 'ton'` and a `tonProof` object is present:**
Accept additional body field:
```ts
tonProof?: {
timestamp: number;
domain: { lengthBytes: number; value: string };
signature: string; // base64
payload: string; // the nonce from challenge
stateInit?: string; // base64, for wallets not yet deployed
}
```
**Verification logic (using `@ton/ton` and `@ton/crypto`):**
```ts
import { Address, Cell, beginCell, contractAddress } from '@ton/ton';
import nacl from 'tweetnacl';
// 1. Validate nonce matches stored challenge for this user
// 2. Validate timestamp is within 5-minute window
// 3. Validate domain === 'amn.gg'
// 4. Reconstruct the signed message:
const message = Buffer.concat([
Buffer.from('ton-proof-item-v2/'),
Buffer.from(new Uint32Array([domain.length]).buffer),
Buffer.from(domain, 'utf8'),
Buffer.from(new Uint64Array([timestamp]).buffer),
Buffer.from(payload, 'hex'),
]);
const hash = Buffer.from(sha256(Buffer.concat([
Buffer.from([0xff, 0xff]),
Buffer.from('ton-connect'),
sha256(message),
])));
// 5. Recover public key from stateInit if wallet not deployed,
// or fetch from blockchain (tonweb/lite-client) if deployed.
// 6. Verify: nacl.sign.detached.verify(hash, sig, pubkey)
// 7. Verify: Address.parse(walletAddress) matches derived address
```
**On success:**
- Store wallet fields: `profile.walletAddress`, `profile.walletType = 'ton'`, `profile.walletProvider`.
- Set new fields: `profile.walletProofVerified = true`, `profile.walletProofTimestamp = new Date()`.
- Clear the stored nonce.
**On failure:**
- Return `400 INVALID_TON_PROOF`.
- Do not update wallet fields.
- Do not clear the nonce (allow retry within TTL).
---
### 3. User model additions
```ts
// models/User.ts — inside profile
walletProofVerified?: boolean;
walletProofTimestamp?: Date;
```
---
### 4. Dependencies to add
```bash
yarn add @ton/ton @ton/crypto tweetnacl
```
`@ton/ton` is already a known dependency (the `@ton/core` frontend package is related). The backend needs `@ton/ton` for address parsing and cell operations, and `tweetnacl` for ed25519 verification.
---
## Frontend Changes
### 1. Fetch challenge before connecting
In `TonWalletProvider` (or the connect handler in `account-wallet-connection.tsx`):
```ts
// Before calling tonConnectUI.connectWallet()
const { payload } = await api.post('/api/user/wallet-address/ton-proof/challenge');
// Pass proof request to TonConnect
const proof: ConnectAdditionalRequest = {
tonProof: payload,
};
await tonConnectUI.connectWallet(proof);
```
### 2. Extract and send proof after connection
```ts
// After connection, wallet.connectItems.tonProof is populated
const wallet = tonConnectUI.wallet;
if (!wallet?.connectItems?.tonProof || 'error' in wallet.connectItems.tonProof) {
throw new Error('TON proof not returned by wallet');
}
await api.patch('/api/user/wallet-address', {
walletAddress: wallet.account.address,
walletType: 'ton',
walletProvider: wallet.device.appName,
tonProof: wallet.connectItems.tonProof.proof,
});
```
### 3. Show proof badge on wallet page
When `profile.walletProofVerified = true`, show a "Verified" badge next to the wallet address. When false (or absent), show "Unverified" with a "Verify ownership" button.
---
## Data Model
```ts
// Addition to profile sub-schema
walletProofVerified?: boolean; // true after successful TonConnect proof
walletProofTimestamp?: Date; // when proof was last verified
```
Existing fields retain their meaning:
- `walletAddress` — the TON address (friendly format)
- `walletType``'ton'` | `'evm'`
- `walletProvider` — e.g. `'Telegram Wallet'`, `'Tonkeeper'`
---
## API Reference
| Method | Path | Auth | Purpose |
|--------|------|------|---------|
| POST | `/api/user/wallet-address/ton-proof/challenge` | JWT | Get nonce for proof |
| PATCH | `/api/user/wallet-address` | JWT | Save wallet + verify proof |
---
## Tests
### Backend
- Challenge endpoint returns `{ payload, domain, timestamp }`.
- Challenge is bound to userId (different users get different nonces).
- Challenge expires: re-issuing invalidates previous.
- Wallet save with valid proof sets `walletProofVerified = true`.
- Wallet save with expired nonce returns 400.
- Wallet save with wrong domain returns 400.
- Wallet save with bad signature returns 400.
- Wallet save without `tonProof` object sets `walletProofVerified = false` (legacy format-only path).
### Frontend
- `connectWallet` is called with `tonProof: payload`.
- If proof is absent in response, shows error and does not call save endpoint.
- If backend returns 400, shows "Wallet verification failed" toast.
- Verified wallet shows badge; unverified shows "Verify" prompt.
---
## Migration
Existing users with a saved TON wallet address will have `walletProofVerified` absent (undefined / falsy). They should be prompted to re-verify when they next visit the wallet page. No automatic migration needed — the address is retained, only proof status changes.
---
## Implementation Status
### Backend — Complete (2026-05-24)
- `POST /api/user/wallet-address/ton-proof/challenge` implemented in `userController.ts`.
- Full ed25519 verification in `src/services/user/tonProofService.ts`:
- In-memory TTL challenge store (5-minute TTL per userId).
- Nonce cleared on success only (allows retry on failure).
- Verification: nonce match → expiry → domain → timestamp ±300s → stateInit hash matches address → extract pubkey → reconstruct signed message → `nacl.sign.detached.verify`.
- Public key extraction handles standard wallet v3/v4 data layout (skip 64 bits seqno+subwallet, read 32-byte pubkey).
- User model fields added: `profile.walletProofVerified`, `profile.walletProofTimestamp`.
- Route registered: `POST /api/user/wallet-address/ton-proof/challenge`.
- `@ton/ton@16.2.4`, `@ton/crypto@3.3.0`, `tweetnacl@1.0.3` added to backend `package.json`.
- 15 backend unit tests in `__tests__/ton-proof.test.ts` — all passing.
### Frontend — Pending
- Challenge fetch + `setConnectRequestParameters` + proof extraction not yet wired.
- Verified/unverified badge not yet rendered.
- EVM wallet stub card added (see below).
---
## Acceptance Criteria
- [x] `POST /api/user/wallet-address/ton-proof/challenge` returns a valid nonce object.
- [ ] `PATCH /api/user/wallet-address` with a real TonConnect proof from Telegram Wallet succeeds and sets `walletProofVerified: true`.
- [x] A replay of the same proof is rejected.
- [x] An expired nonce is rejected.
- [x] A proof with a mismatched domain is rejected.
- [ ] The wallet page shows "Verified" badge when `walletProofVerified = true`.
- [ ] The wallet page shows "Verify ownership" CTA when wallet is saved but unverified.
- [x] Backend tests cover all rejection cases with mocked proofs.
- [x] `@ton/ton` version is pinned in `package.json` (TON SDK breaks semver occasionally).
---
## Related
- [[Handoff - Telegram Mini App Debug - 2026-05-24]]
- [[Security Audit - 2026-05-24]] — TON wallet is currently self-reported metadata
- [[Data Model Overview]]
- TON Connect docs: https://docs.ton.org/develop/dapps/ton-connect/sign

View File

@@ -0,0 +1,197 @@
---
title: Backend Funds Migration and Operational Runbooks
tags: [operations, payments, migration, incidents]
created: 2026-05-24
---
# Backend Funds Migration and Operational Runbooks
These runbooks cover the selected backend/funds architecture defined in
[[Backend Core Stack Decision Record - 2026-05-24]].
## 1. Migration runbook (legacy + provider migration)
### 1.1 Preflight
1. Verify provider credentials and webhook secrets are present in production `.env`.
2. Confirm canonical state docs are published and approved:
- [[Funds Ledger and Escrow State Machine Specification]]
- [[Payment Provider Adapter Spec]]
- [[Webhook Security Spec]]
3. Run validation report:
- count of legacy `Payment` rows by provider,
- duplicate provider keys,
- webhook processing success/error by provider (24h),
- unresolved `pending`/`processing` payments older than 30m.
4. Run a staging reconciliation simulation against a read-only copy.
### 1.2 Rollout sequence
1. Keep `PAYMENT_MODE=read_only` and keep SHKeeper intent path active.
2. Enable legacy+new provider dual routing via `PAYMENT_ENABLED_PROVIDERS`.
3. Activate adapter contract metrics (`payment-provider-events` and webhook counters) before any write routing.
4. Set `PAYMENT_PROVIDER_MODE=standard` for a 1015% cohort.
5. Expand to full traffic only after two clean 24h windows.
### 1.3 Legacy read path and backfill
- Keep SHKeeper legacy read path enabled for at least one billing cycle after migration starts.
- Backfill task:
- map legacy SHKeeper records into `FundsAccount` references,
- write reconciliation notes into `metadata.providers`,
- leave source payment rows immutable.
- Do not delete legacy routing until backfill report is complete and reviewed.
### 1.4 Validation before enforcement
Before switching default provider:
- No critical webhook verification failures for 24h.
- DLQ volume stable for 2h.
- Provider status mismatch rate `< 0.5%` for 1000 samples.
- Manual reconciliation sample (`>= 10` rows) completed by two operators.
### 1.5 Rollback criteria
Rollback immediately if any of:
- payout/hold/release path returns invariant violation,
- unresolved webhook failure burst above threshold,
- chain reconciliation mismatch above threshold from runbook table.
Rollback command sequence:
1. `PAYMENT_ENABLED_PROVIDERS=shkeeper`
2. `PAYMENT_DEFAULT_PROVIDER=shkeeper`
3. `PAYMENT_PROVIDER_MODE=read_only` (pause new routing),
4. keep reconciliation active in dry-run mode,
5. post-incident note in this doc + [[Incident Response]].
### 1.6 Webhook cutoff and manual reconciliation
- Keep auto-accepting historical webhook traffic through cutover.
- Set explicit cutoff only if new provider writes must stop:
1. pause new pay-in intents on new provider,
2. continue `handleProviderWebhook` in `read_only` mode,
3. run manual reconciliation for open windows,
4. re-enable normal routing after backlog clear or explicit incident decision.
## 2. Scenario runbooks
Each incident scenario uses the same structure:
`owner`, `trigger`, `detection`, `immediate`, `recovery`, `post`.
### 2.1 Failed webhook
- **Owner:** BL + DL
- **Trigger:** `webhook_failures` alert > warning threshold (see [[Webhook Security Spec]]).
- **Detection:** spike in 5xx/400 from callback route, DLQ growth.
- **Immediate:** isolate by provider; keep `PAYMENT_PROVIDER_MODE=read_only` for provider if mismatch continues.
- **Recovery:** re-process DLQ entries after fix; verify idempotent behavior; compare reconciliation counters.
- **Post:** update webhook retry tuning and security thresholds.
### 2.2 Duplicate payment
- **Owner:** BL
- **Trigger:** duplicate webhook deliveries or idempotent suppression above normal baseline.
- **Detection:** `duplicate` counter above expected and no payment state change.
- **Immediate:** confirm normalized status transition map; suppress with `deliveryId`/`idempotency` checks.
- **Recovery:** rerun reconciliation for affected IDs and clear manual holds if ledger still consistent.
- **Post:** validate provider event parsing and storage hashing.
### 2.3 Missing payment
- **Owner:** BL + Operations
- **Trigger:** customer support report or reconciliation finding.
- **Detection:** provider reference exists, no canonical payment row or no final ledger balance.
- **Immediate:** freeze payouts/release for request ID; run `searchProviderPayments`.
- **Recovery:** create missing read-model entry or corrective ledger adjustment after evidence capture.
- **Post:** add missing-test case for same provider/providerReference combination.
### 2.4 Stuck release
- **Owner:** BL + Ops
- **Trigger:** `released` pending >30m.
- **Detection:** payout task with no terminal transition for `release`/`refund`.
- **Immediate:** confirm chain confirmation and provider payout status.
- **Recovery:** if confirmed on-chain but not reflected, run manual reconciliation + status repair; else reissue payout instruction.
- **Post:** update alerting and retry windows.
### 2.5 Disputed release attempt
- **Owner:** Security + BL + Admin
- **Trigger:** release/recover API invoked while dispute hold is active.
- **Detection:** `disputeId` active + attempted release/refund action.
- **Immediate:** block action, notify admin team and dispute owner.
- **Recovery:** clear dispute hold only by approved dispute resolution path.
- **Post:** enforce ownership check in orchestration and admin UI guardrail.
### 2.6 Compromised admin
- **Owner:** Security Lead
- **Trigger:** unexpected high-value payouts or admin credentials alert.
- **Detection:** step-up auth failures, role anomalies, IP anomalies.
- **Immediate:** suspend admin account, rotate sessions, set `PAYMENT_PROVIDER_MODE=read_only`.
- **Recovery:** review all recent ledger entries and release/refund actions; reverse via admin correction only after legal/ownership approval.
- **Post:** enforce 2-person approval for high-risk payout thresholds and step-up audit check.
### 2.7 Leaked API key / webhook secret
- **Owner:** Security + DevOps
- **Trigger:** secret scanner, log leak, or suspected rotation request.
- **Detection:** unauthorized provider 401s, unknown callbacks, or external exposure confirmation.
- **Immediate:** rotate affected key and disable old key immediately.
- **Recovery:** replay missed webhooks in dry-run mode after trust restored; compare reconciliation totals.
- **Post:** document incident and rotate staging/CI secret references.
### 2.8 Provider outage
- **Owner:** DevOps + BL
- **Trigger:** repeated provider 5xx/timeouts.
- **Detection:** error budget alert and queue growth.
- **Immediate:** switch `PAYMENT_ENABLED_PROVIDERS=shkeeper` or `read_only`, notify users of degraded mode.
- **Recovery:** drain queue and replay DLQ when provider recovers.
- **Post:** update provider outage SLA runbook and status page text.
### 2.9 Chain/RPC outage
- **Owner:** BL + DevOps
- **Trigger:** chain verification failures and elevated wallet lookup latency.
- **Detection:** verification timeout/error and `chain_verification_stale` trend.
- **Immediate:** pause non-critical chain-dependent actions; preserve pending proofs.
- **Recovery:** rerun verification jobs against fallback RPC endpoint.
- **Post:** update RPC provider fallback policy and retry tuning.
### 2.10 Suspicious payment proof
- **Owner:** Security + BL
- **Trigger:** tx hash proof with mismatched `to`, amount, or network.
- **Detection:** verify script rejects or manual proof review.
- **Immediate:** mark payment disputed, require operator review.
- **Recovery:** keep payment in `pending`, notify buyer and seller, trigger manual support workflow.
- **Post:** add automated proof assertions to webhook and verifier tests.
### 2.11 npm/package compromise
- **Owner:** CTO + Security
- **Trigger:** new high/critical advisory tied to runtime packages.
- **Detection:** audit alerts or security team notification.
- **Immediate:** pause releases in affected components; freeze `watchtower` auto-update if needed.
- **Recovery:** patch dependency and redeploy, then run smoke tests and transaction smoke path.
- **Post:** verify lockfile, provenance, and dependency policy compliance.
## 3. Incident owner roster
Production launch must replace these role owners with named responders in the
primary on-call schedule. Until then, escalation is by role:
- Payments: backend lead for payment/ledger services
- Security: security owner from [[Security Ownership and Launch Decision Criteria]]
- DevOps: deployment owner for production infrastructure
- Admin lead: operations lead for dispute and payout approval workflows
## 4. Post-incident mandatory actions
- incident log + root-cause and action items,
- metric threshold updates if needed,
- runbook improvements (this document),
- verification that [[Monitoring]] and [[Incident Response]] links are still accurate.
## Related
- [[Incident Response]]
- [[Monitoring]]
- [[Deployment]]
- [[Backup & Recovery]]
- [[Webhook Security Spec]]
- [[Payment Provider Adapter Spec]]
- [[Backend Core Stack Decision Record - 2026-05-24]]

View File

@@ -0,0 +1,539 @@
# Handoff - Telegram Mini App Debug - 2026-05-24
## Scope
This handoff covers the work done after bringing the app up under the consolidated `https://amn.gg` URL and debugging the Telegram Mini App flow.
Primary user-reported issues:
- The red `N` badge in the Telegram webview was interpreted as backend/socket failure.
- Telegram-created users without an existing Amanat account were blocked by the link/sign-in screen instead of being treated as buyer users.
- Wallet connect did not support Telegram Wallet / TON.
- Profile email updates for Telegram-created users did not persist and did not send confirmation email.
- The page needed visible debug statistics to identify which subsystem is working.
## Repositories Touched
Backend:
- `backend/src/models/User.ts`
- `backend/src/services/user/userController.ts`
- `backend/src/services/user/userControllerRoutes.ts`
- `backend/src/services/user/userRoutes.ts`
Frontend:
- `frontend/next.config.ts`
- `frontend/package.json`
- `frontend/yarn.lock`
- `frontend/public/tonconnect-manifest.json`
- `frontend/src/actions/account.ts`
- `frontend/src/app/layout.tsx`
- `frontend/src/auth/context/jwt/auth-provider.tsx`
- `frontend/src/auth/types.ts`
- `frontend/src/components/debug/telegram-debug-panel.tsx`
- `frontend/src/lib/axios.ts`
- `frontend/src/sections/account/account-general.tsx`
- `frontend/src/sections/account/account-wallet-connection.tsx`
- `frontend/src/types/user.ts`
- `frontend/src/web3/tonconnect-provider.tsx`
Vault:
- This file.
## Implemented
### Debug Statistics
Added `TelegramDebugPanel` to account/profile and wallet pages.
It reports:
- API URL
- Socket URL
- socket connected/disconnected state and socket id
- auth state
- user id, role, email, email verification state
- Telegram Mini App detection, platform, version, and initData presence
- saved wallet type/address
- last frontend account/wallet action and result
Visibility:
- Always visible in development.
- Visible inside Telegram Mini App.
- Visible in production browser with `?debug=1` or `localStorage.setItem('amn-debug', '1')`.
### Red `N` Badge
The red `N` indicator is the Next.js development indicator, not the backend/socket status.
Change:
- `frontend/next.config.ts` now sets `devIndicators: false`.
The actual socket status should be read from the debug panel or the app socket status component.
### Telegram User / Buyer Flow
Frontend auth mapping was changed so users without an email are not treated as email-unverified blockers:
- `isEmailVerified` is treated as true when the user has no email.
- If an email exists, normal verification state applies.
This supports Telegram-created buyer accounts without forcing immediate email collection.
### Profile Email Update And Verification
Backend profile update now accepts `email` and handles verification:
- Normalizes email to lowercase.
- Rejects duplicate email.
- Stores a six-digit verification code on the user.
- Sets `isEmailVerified=false` when email changes.
- Attempts to send a confirmation email.
- Returns `verificationEmailQueued`.
New authenticated endpoints:
- `POST /api/user/profile/email/resend-verification`
- `POST /api/user/profile/email/verify`
Frontend:
- `updateUserProfile` now unwraps the backend `{ user, verificationEmailQueued }` response correctly.
- Profile page shows a verification code input and resend/verify controls when a user has an unverified email.
- Debug events are emitted for profile update/resend/verify actions.
### Telegram Wallet / TON Support
Frontend:
- Added `@tonconnect/ui-react` and `@ton/core`.
- Added `TonWalletProvider` in the root layout.
- Added `public/tonconnect-manifest.json`.
- Wallet connection page now offers Telegram Wallet / TON connection inside Telegram Mini App.
- Connected TON wallet can be saved as `walletType: "ton"` with provider metadata.
Backend:
- User profile schema now supports:
- `profile.walletType: "evm" | "ton"`
- `profile.walletProvider`
- Current `/user/wallet-address` controller supports TON address validation.
- Legacy `/users/wallet-address` route also accepts TON.
- EVM still requires signature verification.
- TON currently validates address format only and skips EVM signature.
### Frontend Typecheck — All Pre-Existing Errors Resolved
All 13 pre-existing TypeScript errors in the frontend were fixed. `npx tsc --noEmit` now passes clean and can be used as a release gate.
Fixes applied:
- `src/web3/components/provider-payment.tsx` — changed invalid icon `solar:wallet-money-bold` to `solar:wallet-bold`.
- `src/components/payment/shkeeper-payment-widget.tsx` — moved `sellerId` from `metadata` to top-level payload (matches `IPaymentIntentPayload`).
- `src/sections/address/address-form.tsx` — added `as unknown as Resolver<AddressFormData>` double cast for Zod v4 + `@hookform/resolvers` v5 type mismatch.
- `src/sections/address/address-new-form.tsx` — same resolver cast fix.
- `src/sections/request-template/request-template-checkout-payment.tsx` — same resolver cast fix.
- `src/sections/request-template/request-template-new-edit-form.tsx` — added explicit `useForm<RequestTemplateSchemaType>` generic and resolver cast.
### Backend Tests for Email Verification and Wallet
Created `backend/__tests__/user-profile-email-wallet.test.ts` — 27 tests, all passing.
Coverage:
- Profile email update stores normalized email, sets `isEmailVerified=false`, returns `verificationEmailQueued`.
- Duplicate email rejected with 409.
- Resend verification: fails when no email, fails when already verified, succeeds otherwise.
- Verify email code: rejects malformed, rejects expired/wrong, sets `isEmailVerified=true` on correct code.
- TON wallet save: accepts valid friendly address, stores `walletType`, `walletAddress`, `walletProvider`.
- TON wallet save: rejects malformed address.
- EVM wallet save: rejects missing signature, rejects wrong signature.
`emailService` is mocked — no Resend calls are made during tests.
### Email Delivery Confirmed
Resend SMTP relay verified end-to-end. Configuration: port 465 SSL (`SMTP_SECURE=true`), sender `noreply@amn.gg`. Test email delivered successfully during session.
### Telegram Bot Command Handlers and Notification Foundation (Task 5.3)
Created `backend/src/services/telegram/botService.ts` (~480 lines).
Implemented:
- `sendBotMessage(chatId, text, options)` — raw Telegram Bot API call with inline keyboard support.
- `answerCallbackQuery(id, text?)` — acknowledges callback queries.
- `sendTelegramNotification(telegramUserId, text, options)` — looks up active `TelegramLink`, sends message, detects 403 (bot blocked by user) and marks link as `status: 'blocked'` to prevent future sends.
- `dispatchBotUpdate(update)` — routes incoming webhook updates to command handlers or callback handler.
- Command handlers: `/start` (welcome + Mini App button), `/help`, `/link`, `/status`, `/requests`, `/offers`, `/payments`, `/settings`.
- Per-chat rate limiter (10 messages per 10-second window) to prevent abuse.
- Mini App buttons use `web_app` inline keyboard type pointing to `https://amn.gg/telegram/`.
Modified `telegramService.ts`:
- Expanded `TelegramWebhookUpdate` type to include `message.from`, `message.chat`, `callback_query.message`.
- Wired `handleTelegramWebhook` to fire `dispatchBotUpdate` asynchronously (dynamic import) so Telegram always receives HTTP 200 immediately.
Updated `index.ts`:
- Exports: `sendTelegramNotification`, `sendBotMessage`, `NotificationDeliveryResult`, `BotSendOptions`, `InlineKeyboard`.
Created `backend/__tests__/telegram-bot-service.test.ts` — 17 tests, all passing.
Committed: "Implement Telegram bot command handlers and notification foundation (task 5.3)" — 4 files, 1092 insertions.
### Telegram Bot Commands Registered
Registered bot commands with Telegram Bot API (`setMyCommands`):
- `/start` — Open Amanat escrow marketplace
- `/help` — Show help and command list
- `/link` — Link your Amanat account
- `/status` — Check your active escrow status
- `/requests` — Browse service requests
- `/offers` — View your offers
- `/payments` — Check payment status
- `/settings` — Account settings
### PRD — TON Wallet Ownership Proof
Created `nick-doc/07 - Development/PRD - TON Wallet Ownership Proof.md`.
Covers: challenge nonce endpoint, TonConnect proof request flow, backend ed25519 verification with `@ton/ton` + `tweetnacl`, new User model fields, frontend badge/CTA, migration notes, acceptance criteria, and tests.
### TON Wallet Ownership Proof — Backend Implemented
Full backend implementation completed in session 3.
New file: `backend/src/services/user/tonProofService.ts`
- In-memory TTL challenge store keyed by `userId` (5-minute TTL).
- `generateChallenge(userId)` — generates 32-byte hex nonce, returns `{ payload, domain, timestamp }`.
- `verifyTonProof(userId, walletAddress, proof)` — full ed25519 verification:
1. Nonce match and expiry check.
2. Domain must equal `amn.gg`.
3. Timestamp within ±300 s of now.
4. `stateInit` required; its hash must match the claimed wallet address hash (anti-substitution).
5. Public key extracted from wallet data cell (skip 64-bit seqno+subwallet_id, read 32-byte pubkey — works for v3/v4/v5 standard wallets).
6. Signed message reconstructed: `ton-proof-item-v2/ + workChain (BE int32) + address.hash + domainLen (LE uint32) + domain + timestamp (LE int64) + payload (hex)`.
7. `sha256(0xff0xff + "ton-connect" + sha256(message))` — verified with `nacl.sign.detached.verify`.
8. Nonce cleared only on success (allows retry on failure within TTL).
Modified `backend/src/services/user/userController.ts`:
- `updateWalletAddress` extracts optional `tonProof` from body; calls `verifyTonProof` when present; sets `profile.walletProofVerified` and `profile.walletProofTimestamp`; returns `INVALID_TON_PROOF` on failure.
- New `getTonProofChallenge` handler calls `generateChallenge(userId)` and returns the nonce object.
Modified `backend/src/services/user/userControllerRoutes.ts`:
- `POST /api/user/wallet-address/ton-proof/challenge` registered (authenticated).
Modified `backend/src/models/User.ts`:
- Added `walletProofVerified?: boolean` and `walletProofTimestamp?: Date` to both IUser interface and Mongoose schema.
Added `@ton/ton@16.2.4`, `@ton/crypto@3.3.0`, `tweetnacl@1.0.3` to backend `package.json`.
New file: `backend/__tests__/ton-proof.test.ts` — 15 tests, all passing:
- Challenge generation (uniqueness, TTL invalidation).
- Valid proof accepted, nonce cleared (replay rejected).
- Rejection cases: no challenge, nonce mismatch, wrong domain, expired timestamp, missing stateInit, stateInit/address mismatch, bad signature, wrong keypair.
- Nonce preserved on failure (allows retry).
### Telegram Mini App Shell — LTR Fix and Smart Display Name
Modified `frontend/src/sections/telegram/telegram-mini-app-shell.tsx`:
- Added `dir="ltr"` to all three Container states (linked, unlinked, unsupported) so English content does not inherit the page-level RTL direction.
- Added smart `displayName` logic: falls back from Telegram name → auth user name → `"there"` if Telegram returns a generic or empty name (e.g., the literal string `"Telegram user"`).
### Socket Status — Suppressed Inside Telegram Mini App
Modified `frontend/src/socket/components/socket-status.tsx`:
- Added `usePathname` import; added path-guard `if (pathname?.startsWith('/telegram')) return null`.
- The "قطع" (disconnected) chip that briefly appeared on Mini App load was caused by normal Socket.IO connection latency (~0.5 s before handshake). Suppressing it on `/telegram` routes eliminates the false alarm without hiding useful status on other pages.
### EVM WalletConnect Stub Card
Telegram Wallet is TON-only. Request Network requires EVM. MetaMask is not available in Telegram; WalletConnect is the right approach but requires a project ID from `cloud.walletconnect.com`.
Added `EvmWalletStubCard` to `frontend/src/sections/account/account-wallet-connection.tsx`:
- Always rendered below the TON wallet card on the Account Wallet page.
- Shows WalletConnect logo, "به زودی" (coming soon) chip, and explanation.
- Connect button is disabled when `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` is not set; tooltip tells developer what to configure.
- Button activates automatically when the env var is added — wire the wagmi `walletConnect()` connector (already commented out in `src/web3/config.ts`) at that point.
Rendered from `frontend/src/sections/account/view/account-wallet-view.tsx`.
## Verification Completed
Backend:
```bash
cd backend
npm run typecheck --if-present
```
Result: passed.
Frontend focused tests:
```bash
cd frontend
npm test -- __tests__/account-test/account-actions.test.ts __tests__/auth/telegram-auth-action.test.ts __tests__/sections/telegram/telegram-mini-app-shell.test.tsx --runInBand
```
Result:
- 3 suites passed
- 38 tests passed
Diff hygiene:
```bash
git diff --check
```
Result: passed in backend and frontend after whitespace cleanup.
Public smoke checks completed during the session:
- `https://amn.gg/` returned HTTP 200.
- `https://amn.gg/tonconnect-manifest.json` returned HTTP 200 with JSON.
- `https://amn.gg/socket.io/?EIO=4&transport=polling` returned HTTP 200 polling handshake.
- `https://amn.gg/dashboard/account/wallet/` returned HTTP 200 after compilation.
## Known Issues Left
### TON Wallet Ownership Proof — Frontend Wiring Pending
Backend is fully implemented and tested (see "Implemented" section above, and PRD `07 - Development/PRD - TON Wallet Ownership Proof.md` — status: `backend-implemented`).
Remaining frontend work:
1. Add `getTonProofChallenge` action in `src/actions/account.ts` (calls `POST /api/user/wallet-address/ton-proof/challenge`).
2. In `handleConnectTelegramWallet` in `account-wallet-connection.tsx`:
- Fetch challenge nonce before opening modal.
- Call `tonConnectUI.setConnectRequestParameters({ state: 'ready', value: { tonProof: payload } })`.
- After connection, read `tonWallet.connectItems?.tonProof?.proof` and send to `PATCH /api/user/wallet-address` with `tonProof` field.
3. Show "Verified ✅" badge when `walletProofVerified: true`; show "Verify ownership" CTA when wallet is saved but unverified.
Until the frontend proof flow is wired, the backend accepts wallet saves without proof (legacy path — `walletProofVerified` will be absent/false). TON wallet addresses saved without proof must not be used for automatic payout routing.
### EVM Wallet (WalletConnect) Not Yet Functional
A stub card is now rendered on the wallet page with a disabled connect button. To activate:
1. Register at `cloud.walletconnect.com` (free), get a project ID.
2. Add `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=<id>` to `.env.local` (and server env).
3. Uncomment the `walletConnect()` connector in `src/web3/config.ts`.
4. Wire the connect handler in `EvmWalletStubCard` to open the WalletConnect modal.
### Profile Update UX Needs Manual Telegram Test
The backend email verification endpoints are implemented and covered by tests. The UI flow in the actual Telegram Mini App has not been end-to-end verified manually:
- Start with a Telegram-created buyer without email.
- Add email in profile.
- Confirm warning/verification controls appear.
- Confirm Resend works.
- Enter verification code.
- Confirm session refresh shows verified state.
### Pangolin/Newt Routing Not Re-Verified
The desired public shape is one URL:
- Browser and Telegram app: `https://amn.gg`
- Backend proxied by path under the same host.
Expected route intent:
- `/*` → frontend container/service
- `/api/*` → backend container/service
- `/socket.io/*` → backend container/service with WebSocket support
- `/uploads/*` → backend container/service
- `/health` → backend container/service
If Telegram shows network errors, open `https://amn.gg/?debug=1` and check the debug panel API/Socket fields.
### Package Manager State
The frontend project declares Yarn 1 as package manager and `yarn.lock` was updated for TON dependencies.
`package-lock.json` was intentionally restored to avoid unrelated massive lockfile churn.
Next agent should continue using Yarn for dependency changes unless the project standard changes.
### Dev Runtime Note
During dependency installation, the mounted `node_modules` was temporarily broken by an Alpine/Yarn install attempt involving native packages. It was repaired on the host with dependency install and the public app recovered.
If the container shows stale Next errors in logs, check for a later successful startup before treating them as current.
## Current Git State At Handoff (Updated 2026-05-24 Session 3)
### Backend — Session 3 Changes (Uncommitted)
New files:
- `src/services/user/tonProofService.ts` — full TON proof verification service
- `__tests__/ton-proof.test.ts` — 15 unit tests (all passing)
Modified files:
- `src/models/User.ts` — added `walletProofVerified`, `walletProofTimestamp` fields
- `src/services/user/userController.ts` — TON proof challenge handler + proof verification in wallet save
- `src/services/user/userControllerRoutes.ts` — challenge endpoint registered
- `package.json` / `package-lock.json``@ton/ton`, `@ton/crypto`, `tweetnacl` added
Still uncommitted from earlier sessions:
- `src/services/user/userRoutes.ts`
- `__tests__/user-profile-email-wallet.test.ts` (27 tests)
### Frontend — Session 3 Changes (Uncommitted)
Modified:
- `src/sections/account/account-wallet-connection.tsx` — added `EvmWalletStubCard` component
- `src/sections/account/view/account-wallet-view.tsx` — renders `EvmWalletStubCard`
- `src/sections/telegram/telegram-mini-app-shell.tsx``dir="ltr"` on all containers, smart `displayName` fallback
- `src/socket/components/socket-status.tsx` — suppressed on `/telegram` paths
Other files still uncommitted from earlier sessions (listed in Session 2 handoff).
### Vault — Session 3 Changes (Uncommitted)
- `07 - Development/PRD - TON Wallet Ownership Proof.md` — status updated to `backend-implemented`, acceptance criteria checked
- `08 - Operations/Handoff - Telegram Mini App Debug - 2026-05-24.md` — this file
### Recommended Commit Split
1. Backend commit A — user/email/wallet:
- `src/models/User.ts`
- `src/services/user/userController.ts`
- `src/services/user/userControllerRoutes.ts`
- `src/services/user/userRoutes.ts`
- `__tests__/user-profile-email-wallet.test.ts`
2. Frontend commit:
- All frontend files above.
3. Vault commit:
- All vault files above.
Before committing, rerun:
```bash
cd /Users/manwe/CascadeProjects/escrow/backend
npm run typecheck --if-present
npx jest --testPathPattern='user-profile-email-wallet|telegram-bot-service' --runInBand
cd /Users/manwe/CascadeProjects/escrow/frontend
npx tsc --noEmit -p tsconfig.json
```
Do not include secrets, local `.env` files, or regenerated `package-lock.json` churn.
## Suggested Next Steps
1. Implement TON wallet ownership proof per `07 - Development/PRD - TON Wallet Ownership Proof.md`. This is the only remaining security-critical backend item.
2. Manually test profile email update + verification flow in the Telegram Mini App.
3. Recheck Pangolin/Newt routing and confirm Mini App loads via `https://amn.gg/telegram/`.
4. Build Telegram Mini App shell for marketplace workflows (Taskmaster task 5.4).
5. Add frontend tests for `updateWalletAddress` and `TelegramDebugPanel`.
6. Rebuild/redeploy frontend container after dependency changes (`@tonconnect/ui-react`, `@ton/core`).
7. Commit pending vault changes (PRD file, updated handoff).
## Detailed Remaining Work
### 1. ~~Confirm Telegram Bot Web App URL~~ ✓ Done
Bot commands registered via `setMyCommands`. Mini App buttons in `/start` and `/help` use `web_app` type pointing to `https://amn.gg/telegram/`. No BotFather URL change was needed for command dispatch.
If the menu button URL still points to an old URL, update it via BotFather → Bot Settings → Menu Button.
### 2. Recheck Pangolin/Newt Routing
Not touched this session. See "Known Issues Left" section above for routing checklist.
### 3. ~~Finish Email Verification Test Coverage~~ ✓ Done
27 tests in `backend/__tests__/user-profile-email-wallet.test.ts` cover all email verification cases. See "Implemented" section above.
### 4. ~~Finish Wallet Test Coverage~~ ✓ Done (format-only)
Backend tests for TON format validation and EVM signature enforcement are in `user-profile-email-wallet.test.ts`.
Ownership proof tests will be added when TonConnect proof verification is implemented (see item 5).
Recommended frontend tests still pending:
- `updateWalletAddress` sends legacy EVM payload unchanged when no options are passed.
- `updateWalletAddress` sends TON payload without signature when `{ walletType: "ton" }` is passed.
- `TelegramDebugPanel` renders socket/auth/TG fields when debug is enabled.
- Account profile page renders email verification controls for an unverified email.
### 5. Implement TON Ownership Proof
PRD written: `07 - Development/PRD - TON Wallet Ownership Proof.md`.
Implementation not started. See PRD for full spec. Summary:
- Add `yarn add @ton/ton @ton/crypto tweetnacl` to backend.
- Add `POST /api/user/wallet-address/ton-proof/challenge` (generates TTL nonce).
- Update `PATCH /api/user/wallet-address` to verify ed25519 proof when `tonProof` is present.
- Add `profile.walletProofVerified` and `profile.walletProofTimestamp` to User model.
- Frontend: request proof via `tonConnectUI.connectWallet({ tonProof: payload })`, send proof to backend.
- Frontend: show "Verified" badge or "Verify ownership" CTA.
### 6. Check Profile Update UX in Telegram
Backend and tests done. Manual Telegram Mini App test not yet done:
- Start with a Telegram-created buyer without email.
- Add email in profile, confirm page does not discard it.
- Confirm warning/verification controls appear.
- Confirm Resend works and email arrives from `noreply@amn.gg`.
- Enter verification code, confirm `isEmailVerified=true`.
### 7. ~~Resolve Existing Typecheck Debt~~ ✓ Done
All 13 pre-existing frontend TypeScript errors fixed. `npx tsc --noEmit` passes clean. Add it to the frontend verification gate.
### 8. Deployment/Container Follow-Up
Because dependencies changed in `frontend/package.json` and `yarn.lock`, the deployed frontend image/container should be rebuilt from a clean dependency install.
Recommended sequence:
```bash
cd /Users/manwe/CascadeProjects/escrow/frontend
yarn install --frozen-lockfile
yarn test __tests__/account-test/account-actions.test.ts __tests__/auth/telegram-auth-action.test.ts __tests__/sections/telegram/telegram-mini-app-shell.test.tsx --runInBand
cd /Users/manwe/CascadeProjects/escrow
docker compose build frontend backend
docker compose up -d frontend backend
```
If the existing compose setup relies on mounted `node_modules`, verify the mounted dependencies include `@tonconnect/ui-react` and `@ton/core`.
### 9. Spark Agent Status
The user asked to use codex-spark agents. Earlier in this session, existing subagents were already active and a fresh spark verifier could not be spawned because the agent thread limit was reached.
Next agent can continue with spark workers once capacity is available. Good isolated assignments:
- Worker A: backend tests for profile email verification.
- Worker B: backend tests for wallet address EVM/TON behavior.
- Worker C: frontend tests for debug panel and TON wallet action payload.
- Worker D: manual Telegram/Pangolin runtime verification report.
### 10. Safety Notes
- Do not commit Telegram bot token, Resend API key, Pangolin/Newt secret, or any `.env` file.
- Do not revert unrelated vault audit files; they predate this handoff and should be reviewed as separate documentation work.
- Do not regenerate package locks casually. The frontend project declares Yarn 1 and `yarn.lock` is the relevant lockfile for this change.
- Keep Trezor optional; this pass did not modify Trezor safekeeping behavior.
- Payment adapter remains optional; this pass did not force SHKeeper or Request Network.

View File

@@ -256,6 +256,38 @@ docker exec -it nickapp-backend node -e "
If user data may have leaked, treat as sev 1 and follow your data-breach disclosure process. If user data may have leaked, treat as sev 1 and follow your data-breach disclosure process.
### 3.8 Request Network rollback + reconciliation
Use when Request Network payments are failing, stalled, or out of sync with local payment state.
**Immediate rollback (minutes):**
1. Stop routing new intents to Request Network by setting:
- `REQUEST_NETWORK_ENABLED=false`
- `PAYMENT_ENABLED_PROVIDERS=shkeeper`
- keep `PAYMENT_ROLLBACK_PROVIDER=shkeeper`
2. Restart backend and confirm new `/api/payment/request-network/*` checks are no longer in your checkout path.
3. Confirm `PAYMENT_PROVIDER_MODE` is in a safe operational mode:
- `live`: standard operations
- `read-only`: observe only, no writes
- `dry-run`: status updates without on-chain actions
**Reconciliation before re-enabling:**
1. Keep `PAYMENT_RECONCILIATION_ENABLED=false` until investigation is complete.
2. Run a dry reconciliation pass (dry-run) using the Request Network reconciliation service and capture summary counters.
3. If summary is healthy, run with `apply=true` for the intended payment window.
4. Re-enable RN intentionally only after two deployment health checks pass.
Escalate if repeated `lookup_failed`, `missing_reference`, or coordinator-blocked outcomes block reconciliation for more than 10 minutes.
--- ---
## 4. Communication templates ## 4. Communication templates

View File

@@ -0,0 +1,96 @@
---
title: Payment and Trezor Verification Report
tags: [operations, testing, payments, trezor]
---
# Payment and Trezor Verification Report
Date: 2026-05-24
Scope:
- Task 3 provider-neutral payment migration.
- Request Network optional pay-in, webhook, and reconciliation support.
- Internal funds ledger and release/refund ledger gates.
- Optional Trezor safekeeping support.
## Optionality Verdict
Trezor safekeeping is optional by default.
```env
TREZOR_SAFEKEEPING_REQUIRED=false
```
Only the literal value `true` enforces Trezor proof during release/refund confirmation. When unset, `false`, or any other value, release/refund confirmation continues through the existing payment adapter path.
Enforcement is centralized in `backend/src/services/trezor/trezorService.ts` and called from `backend/src/services/payment/orchestration/releaseRefundService.ts`.
## Focused Verification Command
Run this command from the backend package:
```bash
npm test -- __tests__/payment-adapter-registry.test.ts __tests__/request-network-adapter.test.ts __tests__/request-network-payin.test.ts __tests__/request-network-webhook.test.ts __tests__/payment-ledger.model.test.ts __tests__/payment-ledger.service.test.ts __tests__/payment-migration.service.test.ts __tests__/payment-release-refund-orchestration.test.ts __tests__/payment-release-refund-routes.test.ts __tests__/payment-reconciliation.service.test.ts __tests__/payment-observability-redaction.test.ts __tests__/payment-observability-events.test.ts __tests__/trezor-safekeeping.service.test.ts --runInBand
```
Expected result:
```text
Test Suites: 13 passed, 13 total
Tests: 64 passed, 64 total
```
Also run:
```bash
npm run typecheck
git diff --check
```
Expected result: both pass for backend changes.
## Suite Coverage
| Suite | Test count | Verifies |
| --- | ---: | --- |
| `payment-adapter-registry.test.ts` | 8 | Provider adapter selection, rollback defaults, enabled provider flags, Request Network alias support |
| `request-network-adapter.test.ts` | 6 | Request Network payload creation, parse/map helpers, webhook signature verification, adapter HTTP wiring |
| `request-network-payin.test.ts` | 3 | Pay-in creation, pending-intent dedupe, provider-disabled rejection |
| `request-network-webhook.test.ts` | 6 | Signature validation, test webhook allowlist, duplicate delivery handling, coordinator-blocked duplicate path |
| `payment-ledger.model.test.ts` | 3 | Ledger model entry types, required fields, unique sparse idempotency index |
| `payment-ledger.service.test.ts` | 9 | Append/idempotency behavior, balance aggregation, release/refund availability, held/disputed invariant |
| `payment-migration.service.test.ts` | 3 | SHKeeper migration dry-run counts, bounded sampling, ledger backfill candidate filtering |
| `payment-release-refund-orchestration.test.ts` | 5 | Release/refund instruction flow, ledger append, partial release, rollback compatibility, Trezor proof forwarding |
| `payment-release-refund-routes.test.ts` | 2 | Release/refund route ordering and controller dispatch |
| `payment-reconciliation.service.test.ts` | 6 | Dry-run/apply reconciliation, no-op alignment, missing refs, fallback Request Network references |
| `payment-observability-redaction.test.ts` | 3 | Recursive secret redaction and immutability |
| `payment-observability-events.test.ts` | 5 | Incident control snapshots and event construction |
| `trezor-safekeeping.service.test.ts` | 5 | Deterministic xpub derivation, xpub validation, registration proof, address allocation reuse, operation signature verification |
## Known Gaps
- No live Request Network API test is included in this CI-safe suite.
- No physical Trezor/hardware-device validation is included; Trezor tests use deterministic xpub/address/signature fixtures.
- Migration tests are read/report-oriented and do not execute destructive production backfills.
- Frontend checkout still needs a separate browser/build verification once frontend dependencies are installed.
- The existing `npm run test:payment` script points at a missing `__tests__/payment-services.test.ts`; use the focused command above instead.
## Release Gate
Before enabling Request Network for a non-test cohort:
1. Run the focused verification command.
2. Run backend typecheck.
3. Test one Request Network sandbox pay-in with webhook callback.
4. Confirm reconciliation dry-run output is empty or expected.
5. Keep `PAYMENT_ROLLBACK_PROVIDER=shkeeper`.
Before enabling Trezor safekeeping enforcement:
1. Register an admin Trezor account through `/api/trezor/register`.
2. Confirm `/api/trezor/account` reports `registered: true`.
3. Generate and sign one `/api/trezor/operation-message`.
4. Confirm `/api/trezor/verify-operation` succeeds.
5. Set `TREZOR_SAFEKEEPING_REQUIRED=true`.
6. Confirm release/refund without Trezor proof is rejected and release/refund with proof succeeds.

View File

@@ -0,0 +1,123 @@
---
title: Audit Index — 2026-05-24
tags: [audit, index, security, logic, performance]
created: 2026-05-24
status: open
---
# Audit Index — 2026-05-24
Full-system audit triggered by completion of Telegram first-class auth, Request Network integration, rate-limiting enablement, and funds ledger. Three parallel audit streams were run against actual source code.
| Report | Findings |
|--------|---------|
| [[Security Audit - 2026-05-24]] | 6 critical · 5 high · 7 medium · 4 low |
| [[Logic Audit - 2026-05-24]] | 4 critical · 5 high · 7 medium · 2 low |
| [[Performance Audit - 2026-05-24]] | 6 high · 8 medium · 4 low |
---
## Cross-Cutting Criticals (Fix Immediately)
These items appear in multiple audit streams or are exploitable right now.
| ID | Severity | What | Where | Fix Effort |
|----|----------|------|-------|------------|
| SEC-C3 / LOG-CRIT3 | CRITICAL | Simulated transaction bypass (`SIM_*`) active in production | `paymentRoutes.ts:379` | 1 line — wrap in `NODE_ENV !== 'production'` |
| SEC-C4 | CRITICAL | `forceVerifyUser` gate is wrong (`!== development` → unset env passes) | `authController.ts:1127` | 1 line — flip to `=== 'development'` |
| SEC-C1 / SEC-C2 | CRITICAL | Hardcoded admin password + logged to stdout on every deploy | `init-admin.ts:7,50` | Remove fallback + delete log line + rotate credential |
| SEC-C5 / LOG-CRIT4 | CRITICAL | Hardcoded SHKeeper admin credential in source | `shkeeperPayoutService.ts:224` | Move to env var + rotate |
| SEC-C6 | CRITICAL | Access and refresh tokens share the same JWT signing secret | `authService.ts:18,54` | Add `REFRESH_TOKEN_SECRET` env var |
| LOG-CRIT1 | CRITICAL | Concurrent webhooks can double-process the same payment (no DB-level lock) | `paymentCoordinator.ts` / `shkeeperWebhook.ts` | Atomic `findOneAndUpdate` with status guard |
| LOG-CRIT2 | CRITICAL | Parallel Telegram auth creates orphan User documents (TOCTOU on link+user creation) | `authController.ts:377` | Upsert link first, create user only if upsert won |
---
## High-Priority Queue (Fix Before Soft Launch)
| ID | Stream | What | Where |
|----|--------|------|-------|
| SEC-H2 | Security | SHKeeper webhook authentication bypass via `User-Agent` / `crypto` heuristic | `shkeeperWebhook.ts:95` |
| SEC-H3 | Security | Request Network `allowTestMode: true` hardcoded — test header skips all sig verification | `requestNetworkRoutes.ts:104` |
| SEC-H5 | Security | `global.io.emit(...)` broadcasts financial event data to all connected sockets | `shkeeperWebhook.ts:546` |
| SEC-H4 | Security | Typing indicator IDOR — no chat membership check | `app.ts:267` |
| SEC-H1 | Security | Telegram in-memory replay map reset on restart; replay possible within 24h window | `telegramService.ts:395` |
| LOG-HIGH5 | Logic | `verifyEmailWithCode` non-atomic User.save + TempVerification.delete | `authController.ts:620` |
| LOG-HIGH3 | Logic | `refreshTokens[]` array grows unboundedly | `authController.ts:62` |
| LOG-HIGH4 | Logic | Blocked Telegram user bypasses block when TelegramLink is deleted | `authController.ts:355` |
| LOG-MED5 | Logic | Unauthenticated `/payment/callback` endpoint can mutate any payment status | `paymentControllerRoutes.ts:20` |
| PERF-H3 | Performance | Unbounded seller fan-out on new request: `User.find({role:'seller'})` + N socket emits | `PurchaseRequestService.ts:190` |
| PERF-H4 | Performance | Full chat document (~250 MB for large chats) loaded into memory for every paginated request | `ChatService.ts:370` |
---
## Medium Priority (Fix Before Public Launch)
| ID | Stream | What | Where |
|----|--------|------|-------|
| SEC-M1 | Security | OTP + reset codes logged in plaintext in all environments | `authController.ts:174,715` |
| SEC-M2 | Security | `Math.random()` used for OTP (not CSPRNG) | `authService.ts:226` |
| SEC-M3 | Security | No refresh token reuse/theft detection | `authController.ts:510` |
| SEC-M4 | Security | Profile update mass-assignment + `validateBeforeSave: false` | `authController.ts:921` |
| SEC-M5 | Security | Login Widget auth has no replay protection (Mini App has it) | `authController.ts:110` |
| SEC-M6 | Security | No JWT secret length enforcement at startup | `config/index.ts:42` |
| LOG-MED1 | Logic | Funds ledger availability check + append not atomic (concurrent double-release possible) | `releaseRefundService.ts:37` |
| LOG-MED2 | Logic | `updatePurchaseRequestStatus` bypasses state machine validator | `PurchaseRequestService.ts:551` |
| LOG-MED3 | Logic | `getUserPayments` queries `userId` (wrong field) — always returns empty | `paymentService.ts:342` |
| LOG-MED4 | Logic | Payout created without verifying a completed inbound payment exists | `shkeeperPayoutService.ts:42` |
| PERF-H1 | Performance | N+1: one `Payment.findOne` per request row in buyer dashboard | `PurchaseRequestService.ts:516` |
| PERF-H2 | Performance | Missing index on `Payment.purchaseRequestId` | `models/Payment.ts:190` |
| PERF-M1 | Performance | Missing compound index `(buyerId, createdAt)` on PurchaseRequest | `models/PurchaseRequest.ts:360` |
| PERF-M2 | Performance | Unanchored regex on title/description — full collection scan | `PurchaseRequestService.ts:703` |
| PERF-M7 | Performance | `user-online` event broadcast to all sockets globally | `app.ts:300` |
---
## Low Priority / Hardening
| ID | Stream | What |
|----|--------|------|
| SEC-L1 | Security | Passkey challenge debug logs expose all active challenges + all users' passkey IDs |
| SEC-L2 | Security | Login attempt counters in-memory (multi-replica bypass possible) |
| SEC-L3 | Security | `FRONTEND_URL` unset allows CORS `*` |
| SEC-M7 | Security | Legacy `verifyEmail` token route has no expiry check |
| LOG-LOW1 | Logic | Duplicate `/payment/callback` route definition |
| LOG-MED7 | Logic | `acceptOffer` notification uses undefined `offer.title` |
| PERF-M3 | Performance | Double-fetch pattern in update methods (no `.lean()` on pre-check) |
| PERF-M6 | Performance | `getSellers()` unbounded — no `.limit()` |
| PERF-M8 | Performance | Post-filter after pagination causes wrong `totalItems` count |
---
## Recommended Index Additions
Add these to eliminate the collection scans identified in the performance audit:
```ts
// models/Payment.ts
paymentSchema.index({ purchaseRequestId: 1, status: 1 });
// models/PurchaseRequest.ts
PurchaseRequestSchema.index({ buyerId: 1, createdAt: -1 });
PurchaseRequestSchema.index({ status: 1, createdAt: -1 });
PurchaseRequestSchema.index({ categoryId: 1, status: 1, createdAt: -1 });
PurchaseRequestSchema.index({ title: 'text', description: 'text', tags: 'text' });
```
---
## Items Confirmed Correctly Handled (PASS)
- HMAC timing-safe comparison on all webhooks ✓
- Telegram `initData` HMAC derivation and bot account rejection ✓
- Blocked Telegram user check on existing links ✓
- Refresh token rotation (old removed before new issued) ✓
- Password change / reset clears all refresh tokens ✓
- Socket.IO JWT enforcement on connect ✓
- `join-chat-room` membership check ✓
- bcrypt work factor = 12 ✓
- WebAuthn challenge consumed on first use ✓
- All TTL indexes (TempVerification, TelegramSession, Notification) ✓
- FundsLedgerEntry idempotency key (sparse unique index) ✓
- SHKeeper polling bounded and self-cleaning ✓
- Socket.IO room cleanup on disconnect ✓

View File

@@ -0,0 +1,707 @@
---
title: Authorization Matrix - REST and Socket.IO
tags: [audit, security, authorization, matrix, reference]
created: 2026-05-24
status: living
reviewers: [backend, security]
---
# Authorization Matrix - REST and Socket.IO
**Purpose:** Canonical reference mapping every REST endpoint and Socket.IO event to its required access level, ownership checks, state preconditions, rate-limit tier, and audit-log requirement. Implementation tasks reference individual rows by ID.
**How to read this document:**
- Each row is a discrete authorization rule identified by a unique ID (e.g., `AUTH-R001`).
- "Current State" reflects the documented and audited behavior as of 2026-05-24, based on [[Platform Logical Audit - 2026-05-24]], [[Backend Stack Security and Refactor Assessment - 2026-05-24]], and [[Threat Model - Amanat Escrow Platform]].
- "Required State" is what this matrix mandates for launch. Gaps are enumerated in Section 4.
- `[VERIFY]` indicates uncertainty from documentation alone; the implementation must be inspected.
**Cross-references:**
- [[Threat Model - Amanat Escrow Platform]] -- T03 (Socket.IO room join), T09 (admin privilege escalation), T21 (unauthenticated data exfiltration)
- [[Security Architecture]] -- current auth/role model, rate limiting
- [[Backend Architecture]] -- route registration, middleware chain
- [[Real-time Layer]] -- Socket.IO rooms and events
- [[Security Ownership and Launch Decision Criteria]] -- launch checklist items 2.1-2.6
---
## Section 1: Access Level Definitions
| Access Level | Criteria | Enforcement Mechanism |
|---|---|---|
| **Public** | No authentication required. No `req.user`. | No middleware. Rate-limited at IP level. |
| **Authenticated** | Valid Bearer JWT. `req.user = { id, email, role }` is populated. | `authenticateToken` middleware on route. |
| **Owner** | Authenticated + `req.user.id` matches the resource owner. Ownership is determined per resource type: (a) User profile/addresses/wallet: `user._id === req.params.userId`; (b) PurchaseRequest: `request.buyerId === req.user.id`; (c) SellerOffer: `offer.sellerId === req.user.id`; (d) Payment: `payment.buyerId === req.user.id || payment.sellerId === req.user.id`; (e) Chat: `chat.participants.includes(req.user.id)`; (f) Notification: `notification.userId === req.user.id`; (g) Dispute: `dispute.buyerId === req.user.id || dispute.sellerId === req.user.id`; (h) RequestTemplate: `template.sellerId === req.user.id`; (i) BlogPost: N/A (admin-only writes); (j) File: uploader ownership via metadata association. | `authenticateToken` + ownership query in controller/service. |
| **Buyer** | Authenticated + user is the buyer on the specific purchase request (i.e., `request.buyerId === req.user.id`). | `authenticateToken` + service-layer buyer check. |
| **Seller** | Authenticated + user is the selected seller on the purchase request (`request.selectedOffer.sellerId === req.user.id`) or has a seller role making an offer. | `authenticateToken` + service-layer seller check. |
| **Admin** | Authenticated + `req.user.role === 'admin'`. All admin actions MUST be audit-logged. | `authenticateToken` + `roleGuard('admin')` or `authorizeRoles('admin')`. |
| **Support** | Authenticated + `req.user.role === 'support'`. Read-only access to user data, dispute records, and chat. Can reset passwords and escalate to admin. Cannot modify financial records or release funds. | `authenticateToken` + `roleGuard('support')`. Controller must enforce read-only constraint. |
| **Service** | Internal service-to-service calls. Authenticated via shared secret (`X-Internal-Secret` header) or restricted to localhost network. Not user-facing. | Custom middleware verifying internal header or `req.ip === '127.0.0.1'`. |
| **Step-up** | Admin + re-authenticated within configured window (default 5 minutes). Required for high-risk admin actions (role changes, user deletion, payout/release, manual overrides, sensitive wallet operations). | `authenticateToken` + `roleGuard('admin')` + step-up timestamp check from Redis session. |
| **HMAC** | No user auth. Verified via HMAC-SHA256 signature on raw body using `SHKEEPER_WEBHOOK_SECRET`. Signature-verified, not identity-verified. | `express.raw()` body parser + timing-safe HMAC comparison. |
---
## Section 2: REST Endpoint Authorization Matrix
### Rate-Limit Tiers (reference)
| Tier | Limit | Scope | Applies to |
|---|---|---|---|
| Tier 1 (strict) | 5 req / 5 min / IP | Login, register, password reset, verification | Auth mutation paths |
| Tier 2 (auth-financial) | 20 req / 15 min / user | Payment operations, AI calls, file uploads | Financial and cost-bearing |
| Tier 3 (moderate) | 60 req / 15 min / user | Chat messages, notifications, marketplace writes | User interaction |
| Tier 4 (relaxed) | 200 req / 15 min / IP | Public reads, browsing | Public data access |
| Tier 5 (webhook) | Provider-specific; signature-verified | SHKeeper inbound webhooks | External callbacks |
| Tier 6 (admin) | 60 req / 15 min / user | Admin write operations | Admin mutations |
### 2.1 Auth Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| AUTH-R001 | POST | /api/auth/register | Public | None | None | Tier 1 | No | No rate limit. Code logged to stdout. T07. | Public + Tier 1 + remove code logging | Triggers email send. |
| AUTH-R002 | POST | /api/auth/verify-email-code | Public | None | TempVerification exists and not expired | Tier 1 | No | No rate limit. T07. | Public + Tier 1 | Creates User + issues JWT. |
| AUTH-R003 | GET | /api/auth/verify-email/:token | Public | None | Token not expired | Tier 1 | No | Legacy path. | Public + Tier 1 | Legacy URL-based verification. |
| AUTH-R004 | POST | /api/auth/resend-verification | Public | None | User/TempVerification exists | Tier 1 | No | No rate limit. T07. | Public + Tier 1 | Email cost abuse vector. |
| AUTH-R005 | POST | /api/auth/force-verify-user | Service | None | NODE_ENV=development only | Tier 1 | Yes | No auth gate. Exposed in production. | Disable in production builds | Dev-only. Must be removed from prod. |
| AUTH-R006 | POST | /api/auth/login | Public | None | Account not locked | Tier 1 | Yes (failure) | Redis lockout exists. No global rate limit. T12. | Public + Tier 1 + audit failure | Lockout after N failures. |
| AUTH-R007 | POST | /api/auth/refresh-token | Public | Token belongs to user | Refresh token valid and not revoked | Tier 1 | No | No rate limit. | Public + Tier 1 | Rotation detects reuse. |
| AUTH-R008 | POST | /api/auth/logout | Authenticated | Token belongs to user | None | Tier 3 | No | Auth enforced. | Authenticated | Clears Redis session. |
| AUTH-R009 | POST | /api/auth/google/signup | Public | None | Google ID token valid | Tier 1 | No | No rate limit. | Public + Tier 1 | Creates user if new email. |
| AUTH-R010 | POST | /api/auth/google/signin | Public | None | Google ID token valid, user exists | Tier 1 | No | No rate limit. | Public + Tier 1 | Returns JWT for existing user. |
| AUTH-R011 | POST | /api/auth/passkey/authenticate/challenge | Public | None | None | Tier 1 | No | Stubbed implementation. T10. | Disable or fix | Passkeys are broken per T10. |
| AUTH-R012 | POST | /api/auth/passkey/authenticate | Public | None | Challenge valid (in-memory) | Tier 1 | No | In-memory challenge store breaks at scale. T10. | Disable or fix | Public key is stub string. |
| AUTH-R013 | POST | /api/auth/passkey/register/challenge | Authenticated | None | None | Tier 1 | No | Auth required. | Authenticated | Challenge in process memory. |
| AUTH-R014 | POST | /api/auth/passkey/register | Authenticated | None | Challenge valid | Tier 1 | No | Auth required. | Authenticated | Stores stub public key. |
| AUTH-R015 | GET | /api/auth/passkey/list | Authenticated | Owner | None | Tier 3 | No | Auth required. | Authenticated | Returns caller's passkeys. |
| AUTH-R016 | DELETE | /api/auth/passkey/:passkeyId | Authenticated | Owner | Passkey belongs to user | Tier 3 | Yes | Auth required. | Authenticated + audit | |
| AUTH-R017 | POST | /api/auth/request-password-reset | Public | None | User with email exists | Tier 1 | No | Redis rate limit exists. Code logged to stdout. T22. | Public + Tier 1 + remove code logging | Always returns success to avoid enumeration. |
| AUTH-R018 | POST | /api/auth/reset-password | Public | None | Reset token valid and not expired | Tier 1 | Yes | No rate limit. | Public + Tier 1 + audit | Wipes refresh tokens. |
| AUTH-R019 | POST | /api/auth/reset-password-with-code | Public | None | Code valid and not expired | Tier 1 | Yes | No rate limit. | Public + Tier 1 + audit | Alternative reset path. |
| AUTH-R020 | POST | /api/auth/change-password | Authenticated | Owner | Current password correct | Tier 3 | Yes | Auth enforced. | Authenticated + audit | Clears all refresh tokens. |
| AUTH-R021 | GET | /api/auth/profile | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Returns full user doc. |
| AUTH-R022 | PUT | /api/auth/profile | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | |
| AUTH-R023 | POST | /api/auth/update-profile | Authenticated | Owner | None | Tier 3 | No | Auth enforced. Legacy alias. | Authenticated | Duplicate of R022. |
| AUTH-R024 | DELETE | /api/auth/account | Authenticated | Owner | Password re-verified | Tier 3 | Yes | Auth + password required. | Authenticated + audit | Permanent deletion. |
| AUTH-R025 | POST | /api/auth/step-up | Admin | None | Valid challenge context or credentials | Tier 6 | Yes | Not implemented | Admin + Step-up | Required by ADR for high-risk admin actions. Creates 5-minute elevated session in Redis. |
| AUTH-R026 | GET | /api/auth/sessions | Authenticated | Owner | Current refresh session exists | Tier 3 | Yes | Not implemented | Authenticated | Returns active sessions with device, IP, and session age. |
| AUTH-R027 | POST | /api/auth/revoke-session | Authenticated | Owner | Target session belongs to user | Tier 3 | Yes | Not implemented | Authenticated + audit | Revokes one session by sessionTokenHash. |
| AUTH-R028 | POST | /api/auth/revoke-all-sessions | Authenticated | Owner | Multiple active sessions loaded | Tier 3 | Yes | Not implemented | Authenticated + audit | Revokes all sessions except current. |
### 2.2 User Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| USER-R001 | GET | /api/user/profile | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | New controller path. |
| USER-R002 | PUT | /api/user/profile | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | |
| USER-R003 | GET | /api/users/profile | Authenticated | Owner | None | Tier 3 | No | Auth enforced. Legacy. | Authenticated | Legacy alias. |
| USER-R004 | GET | /api/users/profile/:userId | Authenticated | Owner or Admin | None | Tier 3 | No | Auth enforced. Public/private split based on isPublic flag. | Authenticated | Returns limited fields for non-owners. |
| USER-R005 | GET | /api/user/wallet-address | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Returns stored wallet. |
| USER-R006 | PATCH | /api/user/wallet-address | Authenticated | Owner | Signature verification passes | Tier 3 | Yes | Auth enforced. EIP-191 verify. | Authenticated + audit | Financial implications. |
| USER-R007 | GET | /api/users/contacts | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Role-filtered contact list. |
| USER-R008 | GET | /api/users/search | Authenticated | None | q.length >= 2 | Tier 3 | No | Auth enforced. | Authenticated | Returns max 20 results. |
| USER-R009 | GET | /api/users | Authenticated | None | None | Tier 3 | No | Auth enforced. No admin gate. [VERIFY] | Authenticated | Paginated user directory. Should restrict non-admin to limited fields. |
### 2.3 User Admin Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| UADM-R001 | POST | /api/user/admin/create | Admin | None | Email not taken | Tier 6 | Yes | Inline role check. [VERIFY] consistent enforcement. | Admin + Step-up + audit | Creates user with arbitrary role. T09. |
| UADM-R002 | DELETE | /api/user/admin/:userId | Admin | Cannot delete self or other admins | Target user exists | Tier 6 | Yes | Inline role check. | Admin + Step-up + audit | Hard delete. T09. |
| UADM-R003 | PATCH | /api/user/admin/:userId/status | Admin | None | Target user exists | Tier 6 | Yes | Inline role check. | Admin + audit | Activate/suspend. |
| UADM-R004 | PATCH | /api/user/admin/:userId/toggle-status | Admin | None | Target user exists | Tier 6 | Yes | Inline role check. | Admin + audit | Flip active/suspended. |
| UADM-R005 | PATCH | /api/user/admin/:userId/role | Admin | None | Target user exists; valid role | Tier 6 | Yes | Inline role check. | Admin + Step-up + audit | Role change is high-risk. T09. |
| UADM-R006 | GET | /api/user/admin/list | Admin | None | None | Tier 6 | No | Inline role check. | Admin | Paginated user directory. |
| UADM-R007 | GET | /api/user/admin/:userId/dependencies | Admin | None | Target user exists | Tier 6 | No | Inline role check. | Admin | Pre-delete check. |
| UADM-R008 | GET | /api/users/admin/stats | Admin | None | None | Tier 6 | No | Inline role check. | Admin | Aggregated stats. |
| UADM-R009 | GET | /api/users/admin/:userId | Admin | None | Target user exists | Tier 6 | No | Inline role check. | Admin | Full user detail. |
| UADM-R010 | PUT | /api/users/admin/:userId | Admin | None | Target user exists | Tier 6 | Yes | Inline role check. | Admin + audit | Mass update user. |
| UADM-R011 | PUT | /api/users/admin/update/:email | Admin | None | User with email exists | Tier 6 | Yes | Inline role check. | Admin + audit | Mass update by email. |
| UADM-R012 | PATCH | /api/users/admin/:userId/password | Admin | None | Target user exists | Tier 6 | Yes | Inline role check. | Admin + Step-up + audit | Wipes all sessions. |
| UADM-R013 | POST | /api/users/admin/:userId/resend-verification | Admin | None | User not already verified | Tier 6 | Yes | Inline role check. | Admin + audit | Triggers email. |
### 2.3A Admin Approval Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| APV-R001 | GET | /api/admin/approvals | Admin | None | None | Tier 6 | Yes | Not implemented | Admin + Step-up | Pending approval queue for high-value actions. |
| APV-R002 | POST | /api/admin/approvals/{id}/confirm | Admin | None | Approval exists, status = PENDING, approver != creator | Tier 6 | Yes | Not implemented | Admin + Step-up + audit | Confirms pending approval and executes action. |
| APV-R003 | POST | /api/admin/approvals/{id}/reject | Admin | None | Approval exists, status = PENDING | Tier 6 | Yes | Not implemented | Admin + Step-up + audit | Rejects pending approval and records reason. |
### 2.4 Address Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| ADDR-R001 | GET | /api/addresses | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Lists caller's addresses. |
| ADDR-R002 | POST | /api/addresses | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | First auto-primary. |
| ADDR-R003 | PUT | /api/addresses/:addressId | Authenticated | Owner | Address belongs to user | Tier 3 | No | Auth enforced. 404 if not owned. | Authenticated | |
| ADDR-R004 | DELETE | /api/addresses/:addressId | Authenticated | Owner | Address belongs to user | Tier 3 | No | Auth enforced. | Authenticated | Promotes next primary. |
| ADDR-R005 | PATCH | /api/addresses/:addressId/primary | Authenticated | Owner | Address belongs to user | Tier 3 | No | Auth enforced. | Authenticated | |
### 2.5 Purchase Request Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| PR-R001 | POST | /api/marketplace/purchase-requests | Buyer | None | None | Tier 3 | No | Auth enforced. | Authenticated (Buyer) | Emits to sellers room. |
| PR-R002 | POST | /api/marketplace/purchase-requests/bulk | Buyer | None | None | Tier 3 | No | Auth enforced. | Authenticated (Buyer) | Template checkout. |
| PR-R003 | GET | /api/marketplace/purchase-requests | Authenticated | None (filtered by role) | None | Tier 3 | No | Auth enforced. | Authenticated | Buyers see own; sellers see routed; admins see all. |
| PR-R004 | GET | /api/marketplace/purchase-requests/my | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Shortcut for caller's requests. |
| PR-R005 | GET | /api/marketplace/purchase-requests/:id | Public | None | None | Tier 4 | No | No auth. Public read for shareable links. | Public | Exposes request data to unauthenticated users. Consider limiting fields. |
| PR-R006 | PATCH | /api/marketplace/purchase-requests/:id | Owner (Buyer) | buyerId match | Status in [draft, pending] | Tier 3 | No | Auth enforced. Owner check via controller. | Owner (Buyer) | |
| PR-R007 | PATCH | /api/marketplace/purchase-requests/:id/status | Owner or Admin | buyerId match or admin | Valid state transition | Tier 3 | Yes | Auth enforced. Owner or admin. | Owner or Admin + audit | State machine must enforce transitions. |
| PR-R008 | DELETE | /api/marketplace/purchase-requests/:id | Owner (Buyer) | buyerId match | No committed payment | Tier 3 | Yes | Auth enforced. Owner check. | Owner (Buyer) + audit | Must verify no payment attached. |
| PR-R009 | GET | /api/marketplace/purchase-requests/:id/workflow-steps | Public | None | None | Tier 4 | No | No auth. | Public | Returns workflow metadata. |
| PR-R010 | GET | /api/marketplace/purchase-requests/:id/payment-status | Authenticated | Buyer or Seller or Admin | None | Tier 3 | No | Auth enforced. | Authenticated | Returns payment + escrow state. |
| PR-R011 | POST | /api/marketplace/purchase-requests/:id/sync-payment-status | Authenticated | Buyer or Seller | Request has payment | Tier 3 | No | Auth enforced. | Authenticated | Triggers provider re-check. |
| PR-R012 | POST | /api/marketplace/purchase-requests/:id/confirm-payment | Buyer | buyerId match | Payment exists | Tier 3 | No | Auth enforced. | Authenticated (Buyer) | Legacy endpoint. |
| PR-R013 | POST | /api/marketplace/purchase-requests/:id/release-payment | Admin | None | Payment funded; escrowState=funded | Tier 6 | Yes | Auth enforced. Admin check. | Admin + Step-up + audit | Escrow release. T06: must check no active dispute. |
| PR-R014 | PUT | /api/marketplace/purchase-requests/:id/delivery | Seller (selected) | sellerId match on selectedOffer | Status in [payment, processing, delivery] | Tier 3 | No | Auth enforced. Seller check. | Authenticated (Seller) | Shipping details. |
| PR-R015 | POST | /api/marketplace/requests/:id/start-delivery | Seller (selected) | sellerId match | Status = processing | Tier 3 | No | Auth enforced. Seller check. | Authenticated (Seller) | Emits status update. |
| PR-R016 | PATCH | /api/marketplace/purchase-requests/:id/confirm-delivery | Buyer | buyerId match | Status in [delivery] | Tier 3 | No | Auth enforced. Buyer check. | Authenticated (Buyer) | Should NOT allow pre-delivery confirmation. T20. |
| PR-R017 | POST | /api/marketplace/purchase-requests/:id/final-approval | Buyer | buyerId match | Status = delivered | Tier 3 | Yes | Auth enforced. | Authenticated (Buyer) + audit | Triggers escrow release. |
| PR-R018 | GET | /api/marketplace/buyers/:buyerId/purchase-requests | Authenticated | None (admin/seller view) | None | Tier 3 | No | Auth enforced. | Authenticated | Admin or seller view of buyer history. |
### 2.6 Delivery Code Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| DC-R001 | POST | /api/marketplace/purchase-requests/:id/delivery-code/generate | Buyer | buyerId match | Status = delivery | Tier 3 | No | Auth enforced. Buyer check. | Authenticated (Buyer) | |
| DC-R002 | POST | /api/marketplace/purchase-requests/:id/delivery-code/verify | Seller (selected) | sellerId match on selectedOffer | Status = delivery; code not expired | Tier 1 | Yes | Auth enforced. NO rate limit on attempts. T20. | Authenticated (Seller) + Tier 1 | Max 5 attempts per 15 min per request. |
| DC-R003 | GET | /api/marketplace/purchase-requests/:id/delivery-code | Authenticated | Buyer or Seller | None | Tier 3 | No | Auth enforced. | Authenticated | Returns code metadata. |
| DC-R004 | GET | /api/marketplace/purchase-requests/:id/delivery-code/status | Authenticated | Buyer or Seller | None | Tier 3 | No | Auth enforced. | Authenticated | Returns validity state. |
### 2.7 Seller Offer Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| OFF-R001 | POST | /api/marketplace/purchase-requests/:id/offers | Seller | None (any seller can offer) | Request status = pending or in_negotiation | Tier 3 | No | Auth enforced. Seller role. | Authenticated (Seller) | Emits to buyer room. |
| OFF-R002 | PUT | /api/marketplace/purchase-requests/:id/offers | Seller | Offer belongs to seller | Offer status = pending | Tier 3 | No | Auth enforced. Legacy. | Authenticated (Seller) | Legacy alias. |
| OFF-R003 | GET | /api/marketplace/purchase-requests/:id/offers | Public | None | None | Tier 4 | No | No auth. | Public | Exposes all offers for a request. |
| OFF-R004 | GET | /api/marketplace/purchase-requests/:id/has-offer | Seller | None | None | Tier 3 | No | Auth enforced. Seller role. | Authenticated (Seller) | Helper for seller UI. |
| OFF-R005 | GET | /api/marketplace/purchase-requests/:id/offers/:sellerId | Public | None | None | Tier 4 | No | No auth. | Public | Specific seller's offer. |
| OFF-R006 | PATCH | /api/marketplace/offers/:id | Owner (Seller) | offer.sellerId match | offer.status = pending | Tier 3 | No | Auth enforced. Owner check. | Owner (Seller) | Must reject if status != pending. T19. |
| OFF-R007 | DELETE | /api/marketplace/offers/:id | Owner (Seller) | offer.sellerId match | offer.status = pending | Tier 3 | No | Auth enforced. Owner check. | Owner (Seller) | Withdraw offer. |
| OFF-R008 | PUT | /api/marketplace/offers/:id/status | Admin | None | Valid status transition | Tier 6 | Yes | Auth enforced. No admin gate documented. [VERIFY] | Admin + audit | Direct status mutation. T09. |
| OFF-R009 | POST | /api/marketplace/purchase-requests/:id/select-offer | Buyer | buyerId match | Request has offers; no payment yet | Tier 3 | Yes | Auth enforced. | Authenticated (Buyer) + audit | Triggers payment intent. |
| OFF-R010 | POST | /api/marketplace/offers/:id/accept | Buyer | buyerId match | Legacy compatibility | Tier 3 | No | Auth enforced. Legacy. | Authenticated (Buyer) | Legacy alias for select-offer. |
### 2.8 Request Template Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| TPL-R001 | POST | /api/marketplace/request-templates | Seller | None | None | Tier 3 | No | Auth enforced. Seller role. | Authenticated (Seller) | Creates template. |
| TPL-R002 | GET | /api/marketplace/request-templates | Seller | Owner | None | Tier 3 | No | Auth enforced. Seller's own templates. | Authenticated (Seller) | Paginated. |
| TPL-R003 | GET | /api/marketplace/request-templates/stats | Seller | Owner | None | Tier 3 | No | Auth enforced. | Authenticated (Seller) | Aggregate stats. |
| TPL-R004 | GET | /api/marketplace/request-templates/:id | Seller | Owner | Template exists | Tier 3 | No | Auth enforced. Owner check. | Authenticated (Seller) | |
| TPL-R005 | PUT | /api/marketplace/request-templates/:id | Seller | Owner | Template exists | Tier 3 | No | Auth enforced. Owner check. | Authenticated (Seller) | |
| TPL-R006 | DELETE | /api/marketplace/request-templates/:id | Seller | Owner | Template exists | Tier 3 | No | Auth enforced. Owner check. | Authenticated (Seller) | |
| TPL-R007 | PATCH | /api/marketplace/request-templates/:id/toggle-status | Seller | Owner | Template exists | Tier 3 | No | Auth enforced. Owner check. | Authenticated (Seller) | |
| TPL-R008 | GET | /api/marketplace/request-templates/public/:shareableLink | Public | None | Template active and not expired | Tier 4 | No | No auth. Public read. | Public | Shop preview page. |
| TPL-R009 | POST | /api/marketplace/request-templates/:shareableLink/convert | Buyer | None | Template active and not expired | Tier 3 | No | Auth enforced. | Authenticated (Buyer) | Creates PurchaseRequest. |
| TPL-R010 | POST | /api/marketplace/request-templates/batch-convert | Buyer | None | Templates active and not expired | Tier 3 | No | Auth enforced. | Authenticated (Buyer) | Cart checkout. |
| TPL-R011 | POST | /api/marketplace/request-templates/complete-payment | Authenticated | Buyer | Requests exist; payment data provided | Tier 3 | No | Auth enforced. | Authenticated | Marks batch requests as paid. |
### 2.9 Shop Settings Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| SHOP-R001 | GET | /api/marketplace/shop/settings/:sellerId | Public | None | None | Tier 4 | No | No auth. Public read. | Public | Shop landing page. |
| SHOP-R002 | GET | /api/marketplace/shop/settings | Seller | Owner | None | Tier 3 | No | Auth enforced. Seller role. | Authenticated (Seller) | Own settings. |
| SHOP-R003 | PUT | /api/marketplace/shop/settings | Seller | Owner | None | Tier 3 | No | Auth enforced. Seller role. | Authenticated (Seller) | |
### 2.10 Category and Seller Directory Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| CAT-R001 | GET | /api/marketplace/categories | Public | None | None | Tier 4 | No | No auth. | Public | |
| CAT-R002 | GET | /api/marketplace/categories/tree | Public | None | None | Tier 4 | No | No auth. | Public | Nested tree. |
| CAT-R003 | GET | /api/marketplace/categories/:id | Public | None | Category exists | Tier 4 | No | No auth. | Public | |
| CAT-R004 | GET | /api/marketplace/sellers | Public | None | None | Tier 4 | No | No auth. | Public | Seller directory. |
| CAT-R005 | GET | /api/marketplace/request-templates/sellers | Public | None | None | Tier 4 | No | No auth. | Public | Sellers with active templates. |
| CAT-R006 | GET | /api/marketplace/request-templates/sellers/:sellerId | Public | None | Seller has active templates | Tier 4 | No | No auth. | Public | Shop profile page. |
### 2.11 Review Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| REV-R001 | GET | /api/marketplace/reviews/:subjectType/:subjectId | Public | None | Reviews enabled by seller | Tier 4 | No | No auth. | Public | Paginated. |
| REV-R002 | GET | /api/marketplace/reviews/:subjectType/:subjectId/summary | Public | None | Reviews enabled | Tier 4 | No | No auth. | Public | Stats only. |
| REV-R003 | POST | /api/marketplace/reviews | Authenticated | None (one review per user per subject) | Subject exists; reviews enabled; no duplicate | Tier 3 | No | Auth enforced. | Authenticated | Computes isVerifiedBuyer. |
### 2.12 Payment Routes (General)
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| PAY-R001 | POST | /api/payment/configuration | Public | None | None | Tier 4 | No | No auth. | Public | Returns widget config. |
| PAY-R002 | GET | /api/payment/health | Public | None | None | Tier 4 | No | No auth. | Public | Health probe. |
| PAY-R003 | GET | /api/payment/shkeeper/config | Public | None | None | Tier 4 | No | No auth. CORS *. | Public | SHKeeper widget config. |
| PAY-R004 | POST | /api/payment | Authenticated | Buyer or Admin | purchaseRequestId, sellerOfferId valid | Tier 2 | Yes | Auth enforced. | Authenticated + audit | Manual payment creation. |
| PAY-R005 | PUT | /api/payment/:id | Authenticated | Owner or Admin | Payment exists | Tier 2 | Yes | Auth enforced. | Authenticated + audit | Status mutation. |
| PAY-R006 | GET | /api/payment | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Caller's payments. |
| PAY-R007 | GET | /api/payment/:id | Authenticated | Owner or Admin | Payment exists | Tier 3 | No | Auth enforced. [VERIFY] ownership | Authenticated | |
| PAY-R008 | GET | /api/payment/:id/debug | Admin | None | Payment exists | Tier 6 | No | Auth enforced. Admin intended. [VERIFY] | Admin | Debug bundle. |
| PAY-R009 | GET | /api/payment/user/:userId | Authenticated | Owner or Admin | User exists | Tier 3 | No | Auth enforced. Ownership check [VERIFY]. | Authenticated | Payments for user. |
| PAY-R010 | GET | /api/payment/stats | Authenticated | Owner or Admin | None | Tier 3 | No | Auth enforced. | Authenticated | Aggregated stats. |
| PAY-R011 | GET | /api/payment/stats/:userId | Authenticated | Owner or Admin | User exists | Tier 3 | No | Auth enforced. | Authenticated | |
| PAY-R012 | GET | /api/payment/export | Authenticated | Owner | None | Tier 3 | Yes | Auth enforced. | Authenticated + audit | Financial data export. |
| PAY-R013 | GET | /api/payment/export/:userId | Admin | None | User exists | Tier 6 | Yes | Auth enforced. Admin check [VERIFY]. | Admin + audit | Export other user's payments. |
| PAY-R014 | POST | /api/payment/payments/cleanup-pending | Admin | None | None | Tier 6 | Yes | Auth enforced. Admin. | Admin + audit | Deletes stale payments. |
| PAY-R015 | POST | /api/payment/payments/:id/fetch-tx | Admin | None | Payment exists | Tier 6 | No | Auth enforced. [VERIFY] admin gate. | Admin | Re-queries chain. |
| PAY-R016 | POST | /api/payment/payments/auto-fetch-missing | Admin | None | None | Tier 6 | No | Auth enforced. [VERIFY] admin gate. | Admin | Batch backfill. |
| PAY-R017 | POST | /api/payment/callback | Service | paymentRef match | None | Tier 5 | Yes | No user auth. paymentRef-based. | Service | Generic callback. [VERIFY] security of paymentRef. |
| PAY-R018 | POST | /api/payment/verify | Authenticated | Buyer | Payment exists | Tier 2 | Yes | Auth enforced. | Authenticated (Buyer) + audit | Frontend Web3 verification. |
### 2.13 Payment Routes (SHKeeper Pay-in)
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| SHK-R001 | POST | /api/payment/shkeeper/intents | Authenticated (Buyer) | Buyer on the purchase request | Request and offer exist; no existing payment | Tier 2 | Yes | Auth enforced. Buyer check. | Authenticated (Buyer) + audit | Creates pay-in intent. |
| SHK-R002 | POST | /api/payment/shkeeper/webhook | HMAC | None | Signature valid; payment exists | Tier 5 | Yes | HMAC verification. Returns 202 on all errors. T02. | HMAC + proper error codes + audit | Must return 400/500 not just 202. |
| SHK-R003 | POST | /api/payment/shkeeper/confirm-transaction | Authenticated | Buyer | Payment exists; not already completed | Tier 2 | Yes | Auth enforced. | Authenticated (Buyer) + audit | Manual fallback. |
| SHK-R004 | POST | /api/payment/shkeeper/test | Service | None | NODE_ENV=development | Tier 1 | No | No auth. Dev only. | Disable in production | T01, T11. |
| SHK-R005 | POST | /api/payment/shkeeper/callback-test | Service | None | NODE_ENV=development | Tier 1 | No | No auth. Dev only. | Disable in production | Echo endpoint. |
| SHK-R006 | GET | /api/payment/shkeeper/callback-test | Service | None | NODE_ENV=development | Tier 1 | No | No auth. Dev only. | Disable in production | GET equivalent. |
| SHK-R007 | POST | /api/payment/shkeeper/create-test-payment | Service | None | NODE_ENV=development | Tier 1 | No | NO AUTH. Exposed in production. T11, T21. | Disable in production | Injects fake payment records. |
| SHK-R008 | POST | /api/payment/shkeeper/trigger-webhook | Authenticated | None | None | Tier 2 | No | Auth enforced. | Authenticated | Sends fake webhook. Dev tool. |
| SHK-R009 | GET | /api/payment/shkeeper/wallet-monitor/status | Public | None | None | Tier 4 | No | No auth. | Public | Monitor state. |
| SHK-R010 | GET | /api/payment/shkeeper/auto-webhook/status | Public | None | None | Tier 4 | No | No auth. | Public | Fallback monitor state. |
| SHK-R011 | GET | /api/payment/shkeeper/webhook-stats | Admin | None | None | Tier 6 | No | Auth enforced. Admin. | Admin | Webhook telemetry. |
### 2.14 Payment Routes (SHKeeper Release/Refund)
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| REL-R001 | POST | /api/payment/shkeeper/:id/release | Admin | None | Payment funded; no active dispute (T06); escrowState=funded | Tier 6 | Yes | Auth enforced. Admin. NO dispute check. T06. | Admin + Step-up + dispute check + audit, + two-person approval for payout > 1000 USD equivalent (see APV-R002/APV-R003) | Builds release tx payload. |
| REL-R002 | POST | /api/payment/shkeeper/:id/release/confirm | Admin | None | Release tx pending; valid txHash | Tier 6 | Yes | Auth enforced. Admin. | Admin + Step-up + audit | Confirms release on-chain. |
| REL-R003 | POST | /api/payment/shkeeper/:id/refund | Admin | None | Payment funded; no active dispute; escrowState=funded | Tier 6 | Yes | Auth enforced. Admin. NO dispute check. T06. | Admin + Step-up + dispute check + audit, + two-person approval for payout > 1000 USD equivalent (see APV-R002/APV-R003) | Builds refund tx. |
| REL-R004 | POST | /api/payment/shkeeper/:id/refund/confirm | Admin | None | Refund tx pending; valid txHash | Tier 6 | Yes | Auth enforced. Admin. | Admin + Step-up + audit | Confirms refund on-chain. |
### 2.15 Payment Routes (SHKeeper Payout)
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| PO-R001 | POST | /api/payment/shkeeper/payout | Admin | None | No existing pending payout for same escrow | Tier 6 | Yes | Auth enforced. Admin. | Admin + Step-up + audit, + two-person approval for payout > 1000 USD equivalent (see APV-R002/APV-R003) | Creates payout task. T05. |
| PO-R002 | GET | /api/payment/shkeeper/payout/status/:taskId | Authenticated | Owner or Admin | Task exists | Tier 3 | No | Auth enforced. | Authenticated | Poll payout status. |
| PO-R003 | POST | /api/payment/shkeeper/payout/webhook | HMAC | None | Signature valid | Tier 5 | Yes | HMAC verification. | HMAC + audit | Payout state changes. |
### 2.16 Payment Routes (Decentralized / Web3)
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| DEC-R001 | POST | /api/payment/decentralized/save | Authenticated (Buyer) | Buyer on referenced purchase request | Request exists | Tier 2 | Yes | NO AUTH. T11, T21. Critical gap. | Authenticated (Buyer) + audit | Persists Web3 payment. |
| DEC-R002 | GET | /api/payment/decentralized/status/:paymentId | Authenticated | Owner or Admin | Payment exists | Tier 3 | No | NO AUTH. T21. | Authenticated | Payment status. |
| DEC-R003 | PUT | /api/payment/decentralized/update | Authenticated (Buyer) | Buyer on referenced purchase request | Payment exists | Tier 2 | Yes | NO AUTH. T11. | Authenticated (Buyer) + audit | Updates payment status/confirmations. |
| DEC-R004 | GET | /api/payment/decentralized/receiver | Public | None | None | Tier 4 | No | No auth. Public escrow address. | Public | Returns escrow wallet. |
| DEC-R005 | GET | /api/payment/decentralized/history/:userId | Authenticated | Owner or Admin | User exists | Tier 3 | No | NO AUTH. T21. Critical gap. | Authenticated | Payment history. |
| DEC-R006 | POST | /api/payment/decentralized/verify/:paymentId | Authenticated | Buyer or Admin | Payment exists; pending status | Tier 2 | Yes | NO AUTH. T01, T11. | Authenticated + audit | Re-verifies on chain. Must decode Transfer event. |
| DEC-R007 | POST | /api/payment/decentralized/verify-all-pending | Admin | None | None | Tier 6 | Yes | NO AUTH. T11. | Admin + audit | Batch verification. |
| DEC-R008 | POST | /api/payment/decentralized/admin-payout | Admin | None | Valid request; valid wallet | Tier 6 | Yes | Auth enforced. Admin. | Admin + Step-up + audit | Direct wallet payout. |
### 2.17 Marketplace Payment Routes (Legacy)
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| MPAY-R001 | POST | /api/marketplace/payments | Authenticated | Buyer | Valid request/offer | Tier 2 | Yes | Auth enforced. | Authenticated + audit | Legacy. Prefer PAY-R004. |
| MPAY-R002 | GET | /api/marketplace/payments | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Legacy. |
| MPAY-R003 | GET | /api/marketplace/payments/:paymentId | Authenticated | Owner or Admin | Payment exists | Tier 3 | No | Auth enforced. | Authenticated | Legacy. |
| MPAY-R004 | PATCH | /api/marketplace/payments/:paymentId | Authenticated | Owner or Admin | Payment exists | Tier 2 | Yes | Auth enforced. | Authenticated + audit | Legacy. |
| MPAY-R005 | POST | /api/marketplace/payments/verify | Authenticated (Buyer) | Buyer on purchase request | Valid request/offer | Tier 2 | Yes | Auth enforced. | Authenticated (Buyer) + audit | Legacy Web3 verification. |
### 2.18 Chat Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| CHAT-R001 | POST | /api/chat | Authenticated | None | 2+ participant IDs | Tier 3 | No | Auth enforced. | Authenticated | Creates chat. |
| CHAT-R002 | POST | /api/chat/purchase-request | Authenticated | Buyer or Seller on request | Request exists | Tier 3 | No | Auth enforced. | Authenticated | Idempotent. |
| CHAT-R003 | POST | /api/chat/support | Authenticated | None | None | Tier 3 | No | Auth enforced. | Authenticated | Support chat. |
| CHAT-R004 | GET | /api/chat | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Caller's chats. |
| CHAT-R005 | GET | /api/chat/stats | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Aggregate counts. |
| CHAT-R006 | GET | /api/chat/:id/info | Authenticated | Participant | Chat exists; user in participants | Tier 3 | No | Auth enforced. Participant check. | Authenticated | Chat metadata. |
| CHAT-R007 | PATCH | /api/chat/:id/archive | Authenticated | Participant | Chat exists | Tier 3 | No | Auth enforced. | Authenticated | Toggle archive. |
| CHAT-R008 | POST | /api/chat/:id/participants | Authenticated | Creator or Admin | Chat is group type | Tier 3 | No | Auth enforced. | Authenticated | Add participant. |
| CHAT-R009 | DELETE | /api/chat/:id/participants/:participantId | Authenticated | Creator or Admin | Chat is group type | Tier 3 | No | Auth enforced. | Authenticated | Remove participant. |
| CHAT-R010 | GET | /api/chat/:id/messages | Authenticated | Participant | Chat exists | Tier 3 | No | Auth enforced. Participant check. | Authenticated | Paginated messages. |
| CHAT-R011 | POST | /api/chat/:id/messages | Authenticated | Participant | Chat exists | Tier 3 | No | Auth enforced. Participant check. | Authenticated | Send text message. |
| CHAT-R012 | POST | /api/chat/:id/messages/file | Authenticated | Participant | Chat exists; file within limits | Tier 2 | No | Auth enforced. Participant check. | Authenticated | File attachment. |
| CHAT-R013 | PATCH | /api/chat/:id/messages/read | Authenticated | Participant | Chat exists | Tier 3 | No | Auth enforced. | Authenticated | Mark as read. |
| CHAT-R014 | PUT | /api/chat/:id/messages/:messageId | Authenticated | Message author | Message exists; within edit window | Tier 3 | No | Auth enforced. Author check. | Authenticated | Edit message. |
| CHAT-R015 | DELETE | /api/chat/:id/messages/:messageId | Authenticated | Author or Admin | Message exists | Tier 3 | No | Auth enforced. Author or admin. | Authenticated | Soft-delete. |
### 2.19 Notification Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| NOTIF-R001 | GET | /api/notifications | Authenticated | Owner | None | Tier 3 | No | Controller route: auth enforced. Legacy route: NO AUTH, ?userId= param. T21. | Authenticated | Legacy router must be removed or gated. |
| NOTIF-R002 | GET | /api/notifications/unread-count | Authenticated | Owner | None | Tier 3 | No | Controller route: auth enforced. | Authenticated | |
| NOTIF-R003 | GET | /api/notifications/:id | Authenticated | Owner | Notification belongs to user | Tier 3 | No | Controller route: auth enforced. | Authenticated | |
| NOTIF-R004 | PATCH | /api/notifications/:id/read | Authenticated | Owner | Notification belongs to user | Tier 3 | No | Controller: auth. Legacy: { userId } in body. T21. | Authenticated | Legacy path must be removed. |
| NOTIF-R005 | PATCH | /api/notifications/mark-all-read | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | |
| NOTIF-R006 | PATCH | /api/notifications/bulk/mark-read | Authenticated | Owner | All IDs belong to user | Tier 3 | No | Auth enforced. | Authenticated | |
| NOTIF-R007 | DELETE | /api/notifications/:id | Authenticated | Owner | Notification belongs to user | Tier 3 | No | Auth enforced. | Authenticated | |
| NOTIF-R008 | DELETE | /api/notifications/bulk/delete | Authenticated | Owner | All IDs belong to user | Tier 3 | No | Auth enforced. | Authenticated | |
| NOTIF-R009 | POST | /api/notifications | Authenticated | None (creates for any userId) | None | Tier 3 | Yes | Controller: auth. Legacy: OPEN. T21. | Authenticated (Admin or Service) + audit | Should restrict who can create notifications for other users. |
### 2.20 Dispute Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| DIS-R001 | POST | /api/disputes | Authenticated | Buyer or Seller on request | Request exists; no open dispute for same request | Tier 3 | Yes | Auth enforced. Participant check. | Authenticated + audit | Must set escrowState=disputed. T06. |
| DIS-R002 | GET | /api/disputes | Authenticated | Owner or Admin | None | Tier 3 | No | Auth enforced. | Authenticated | Own disputes or all (admin). |
| DIS-R003 | GET | /api/disputes/statistics | Admin | None | None | Tier 6 | No | Auth enforced. Admin. | Admin | Dashboard data. |
| DIS-R004 | GET | /api/disputes/:id | Authenticated | Participant or Admin | Dispute exists | Tier 3 | No | Auth enforced. Participant or admin check. | Authenticated | |
| DIS-R005 | POST | /api/disputes/:id/assign | Admin | None | Dispute exists; status=open | Tier 6 | Yes | Auth enforced. Admin. | Admin + audit | Assigns moderator. |
| DIS-R006 | PATCH | /api/disputes/:id/status | Admin | None | Valid status transition | Tier 6 | Yes | Auth enforced. Admin. | Admin + audit | Status update. |
| DIS-R007 | POST | /api/disputes/:id/resolve | Admin | None | Dispute in under_review status; no pending payout | Tier 6 | Yes | Auth enforced. Admin. | Admin + Step-up + audit | Triggers escrow release/refund. T06. |
| DIS-R008 | POST | /api/disputes/:id/evidence | Authenticated | Participant or Admin | Dispute exists; not closed | Tier 3 | No | Auth enforced. Participant or admin check. | Authenticated | Attach evidence. |
### 2.21 AI Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| AI-R001 | POST | /api/ai/generate | Authenticated | None | None | Tier 2 | No | NO AUTH. T08. Cost abuse. | Authenticated | OpenAI cost-bearing. |
| AI-R002 | POST | /api/ai/analyze | Authenticated | None | None | Tier 2 | No | NO AUTH. T08. | Authenticated | Moderation/sentiment. |
| AI-R003 | POST | /api/ai/translate | Authenticated | None | None | Tier 2 | No | NO AUTH. T08. | Authenticated | Translation. |
| AI-R004 | POST | /api/ai/assist | Authenticated | None | None | Tier 2 | No | NO AUTH. T08. | Authenticated | Conversational assistant. |
### 2.22 Blog Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| BLOG-R001 | GET | /api/blog/posts | Public | None | None | Tier 4 | No | No auth. Public. | Public | Paginated. |
| BLOG-R002 | GET | /api/blog/posts/featured | Public | None | None | Tier 4 | No | No auth. | Public | |
| BLOG-R003 | GET | /api/blog/posts/recent | Public | None | None | Tier 4 | No | No auth. | Public | |
| BLOG-R004 | GET | /api/blog/posts/search | Public | None | q.length >= 2 | Tier 4 | No | No auth. | Public | |
| BLOG-R005 | GET | /api/blog/posts/:slug | Public | None | Post published | Tier 4 | No | No auth. Increments viewsCount on GET. | Public | Move view count to separate beacon. |
| BLOG-R006 | GET | /api/blog/admin/posts | Admin | None | None | Tier 6 | No | Auth enforced. Admin. | Admin | Includes drafts. |
| BLOG-R007 | GET | /api/blog/admin/posts/:id | Admin | None | Post exists | Tier 6 | No | Auth enforced. Admin. | Admin | |
| BLOG-R008 | POST | /api/blog/posts | Admin | None | None | Tier 6 | Yes | Auth enforced. Admin. | Admin + audit | Create post. |
| BLOG-R009 | PUT | /api/blog/posts/:id | Admin | None | Post exists | Tier 6 | Yes | Auth enforced. Admin. | Admin + audit | Update post. |
| BLOG-R010 | DELETE | /api/blog/posts/:id | Admin | None | Post exists | Tier 6 | Yes | Auth enforced. Admin. | Admin + audit | Hard delete. |
### 2.23 Points Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| PTS-R001 | GET | /api/points/my-points | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Balance and level. |
| PTS-R002 | GET | /api/points/transactions | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Paginated ledger. |
| PTS-R003 | GET | /api/points/referrals | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | Referral history. |
| PTS-R004 | GET | /api/points/levels | Authenticated | None | None | Tier 3 | No | Auth enforced. Data is non-sensitive. | Authenticated | Level config list. |
| PTS-R005 | GET | /api/points/leaderboard | Authenticated | None | None | Tier 3 | No | Auth enforced. | Authenticated | Top referrers. |
| PTS-R006 | POST | /api/points/redeem | Authenticated | Owner | Sufficient balance | Tier 2 | Yes | Auth enforced. | Authenticated + audit | Redeems points. |
| PTS-R007 | POST | /api/points/generate-referral-code | Authenticated | Owner | None | Tier 3 | No | Auth enforced. | Authenticated | |
| PTS-R008 | POST | /api/points/admin/add | Admin | None | Target user exists; amount valid | Tier 6 | Yes | Auth enforced. Admin. | Admin + audit | Grant/deduct points. |
### 2.24 File Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| FILE-R001 | POST | /api/files/upload/avatar | Authenticated | Owner | File within size/type limits | Tier 2 | No | Auth enforced. | Authenticated | |
| FILE-R002 | POST | /api/files/upload/file | Authenticated | Owner | File within limits | Tier 2 | No | Auth enforced. | Authenticated | Generic upload. |
| FILE-R003 | POST | /api/files/upload/files | Authenticated | Owner | Up to 5 files; within limits | Tier 2 | No | Auth enforced. | Authenticated | Multi-file. |
| FILE-R004 | POST | /api/files/upload/request-template-images | Authenticated (Seller) | Owner | Up to 10 images; valid types | Tier 2 | No | Auth enforced. Seller role. | Authenticated (Seller) | Template images. |
| FILE-R005 | POST | /api/files/upload/blog-images | Admin | None | Up to 10 images; valid types | Tier 2 | No | Auth enforced. [VERIFY] admin gate. | Admin | Blog images. |
| FILE-R006 | DELETE | /api/files/delete | Authenticated | Owner (uploader) | File exists; path within uploads/ | Tier 3 | No | Auth enforced. | Authenticated | |
| FILE-R007 | GET | /api/files/info/:filePath | Authenticated | None | File exists | Tier 3 | No | Auth enforced. | Authenticated | |
| FILE-R008 | GET | /api/files/stats | Admin | None | None | Tier 6 | No | Auth enforced. Admin gating planned. | Admin | Upload statistics. |
| FILE-R009 | GET | /uploads/* | Public | None | File exists | Tier 4 | No | No auth. Static serving. | Public | No auth on file access. Sensitive dispute evidence is accessible. |
### 2.25 Admin Data Cleanup Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| ADM-R001 | GET | /api/admin/cleanup/stats | Admin | None | None | Tier 6 | No | Auth + role enforced. | Admin | Collection stats. |
| ADM-R002 | GET | /api/admin/cleanup/collections | Admin | None | None | Tier 6 | No | Auth + role enforced. | Admin | Collection list. |
| ADM-R003 | POST | /api/admin/cleanup/clean | Admin | None | confirm="DELETE_ALL_DATA" for real execution | Tier 6 | Yes | Auth + role enforced. | Admin + Step-up + audit | Bulk delete. |
| ADM-R004 | DELETE | /api/admin/cleanup/user/:userId | Admin | Cannot delete self | confirm=DELETE_USER_DATA | Tier 6 | Yes | Auth + role enforced. | Admin + Step-up + audit | GDPR deletion. |
| ADM-R005 | POST | /api/admin/cleanup/temp | Admin | None | None | Tier 6 | Yes | Auth + role enforced. | Admin + audit | Purge temp data. |
| ADM-R006 | POST | /api/admin/cleanup/seed-templates | Admin | None | None | Tier 6 | No | Auth + role enforced. | Admin | Re-seed templates. |
| ADM-R007 | POST | /api/admin/cleanup/seed-all | Admin | None | None | Tier 6 | No | Auth + role enforced. | Admin | Full seed. |
### 2.26 Health and Discovery Routes
| ID | Method | Path | Access Level | Ownership Check | State Preconditions | Rate-Limit Tier | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| SYS-R001 | GET | /health | Public | None | None | Tier 4 | No | Public. | Public | Docker healthcheck. |
| SYS-R002 | GET | /api | Public | None | None | Tier 4 | No | Public. | Public | API discovery. |
---
## Section 3: Socket.IO Event Authorization Matrix
### 3.1 Connection and Handshake Events
| ID | Event Name | Direction | Access Level | Room Membership | Payload Restrictions | Rate-Limit | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| SOCK-E001 | `connection` | Client -> Server | Authenticated | None at connect; rooms joined after | JWT in handshake.auth.token | N/A | No | JWT verified; invalid token disconnects. [VERIFY] enforcement. | Authenticated | Server sets socket.data.user. |
| SOCK-E002 | `disconnect` | Client -> Server | Authenticated | All rooms left | None | N/A | No | No user-offline broadcast. | Authenticated | Should emit user-status-change offline. |
### 3.2 Room Join Events
| ID | Event Name | Direction | Access Level | Room Membership | Payload Restrictions | Rate-Limit | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| SOCK-E003 | `join-user-room` | Client -> Server | Authenticated | `user-{userId}` | userId MUST equal socket.data.user.id | Tier 3 | No | Client-supplied userId. Server verification UNCERTAIN. T03. | Server-derived; remove client event | Server should auto-join on handshake. T03. |
| SOCK-E004 | `join-request-room` | Client -> Server | Authenticated | `request-{requestId}` | User must be participant (buyer or selected seller) of request | Tier 3 | No | Client-supplied requestId. Server verification UNCERTAIN. T03. | Authenticated + participant check | Must verify user is buyer/seller on request. |
| SOCK-E005 | `leave-request-room` | Client -> Server | Authenticated | Leaves `request-{requestId}` | User must currently be in room | Tier 3 | No | Client-supplied. [VERIFY] auth. | Authenticated | |
| SOCK-E006 | `join-seller-room` | Client -> Server | Authenticated (Seller) | `seller-{sellerId}` + `sellers` | sellerId MUST equal socket.data.user.id; user role must include seller | Tier 3 | No | Client-supplied sellerId. Server verification UNCERTAIN. T03. | Server-derived from JWT role + id | Auto-join if role=seller. |
| SOCK-E007 | `leave-seller-room` | Client -> Server | Authenticated (Seller) | Leaves both | Must currently be in room | Tier 3 | No | Client-supplied. | Authenticated | |
| SOCK-E008 | `join-buyer-room` | Client -> Server | Authenticated (Buyer) | `buyer-{buyerId}` + `buyers` | buyerId MUST equal socket.data.user.id; user role must include buyer | Tier 3 | No | Client-supplied buyerId. Server verification UNCERTAIN. T03. | Server-derived from JWT role + id | Auto-join if role=buyer. |
| SOCK-E009 | `leave-buyer-room` | Client -> Server | Authenticated (Buyer) | Leaves both | Must currently be in room | Tier 3 | No | Client-supplied. | Authenticated | |
| SOCK-E010 | `join-chat-room` | Client -> Server | Authenticated | `chat-{chatId}` | User must be in chat.participants | Tier 3 | No | Client-supplied chatId. Server verification UNCERTAIN. T03. | Authenticated + participant check | Must verify membership. |
| SOCK-E011 | `leave-chat-room` | Client -> Server | Authenticated | Leaves `chat-{chatId}` | Must currently be in room | Tier 3 | No | Client-supplied. | Authenticated | |
### 3.3 Chat Events
| ID | Event Name | Direction | Access Level | Room Membership | Payload Restrictions | Rate-Limit | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| SOCK-E012 | `typing-start` | Client -> Server | Authenticated | Must be in `chat-{chatId}` | chatId, userId, userName; userId must match socket | Tier 3 | No | No auth documented on event handler. | Authenticated + room member | Broadcasts to chat room. |
| SOCK-E013 | `typing-stop` | Client -> Server | Authenticated | Must be in `chat-{chatId}` | chatId, userId; userId must match socket | Tier 3 | No | No auth documented. | Authenticated + room member | |
### 3.4 Presence Events
| ID | Event Name | Direction | Access Level | Room Membership | Payload Restrictions | Rate-Limit | Audit Log | Current State | Required State | Notes |
|---|---|---|---|---|---|---|---|---|---|---|
| SOCK-E014 | `user-online` | Client -> Server | Authenticated | Joins `user-{userId}` | userId must match socket.data.user.id | Tier 3 | No | Client-supplied. Broadcasts user-status-change. | Authenticated | Should be server-derived. |
### 3.5 Server-to-Client Events (Emission Authorization)
Server-to-client events are emitted by backend services after REST-level authorization. The socket layer does NOT re-authorize emissions; it relies on room membership to limit audience. This table documents which rooms receive which events and what data they contain.
| ID | Event Name | Direction | Emitted By | Target Rooms | Payload Sensitivity | Audit Log | Notes |
|---|---|---|---|---|---|---|---|
| SOCK-E015 | `new-notification` | Server -> Client | NotificationService | `user-{recipientId}` | Contains notification body; PII if notification content includes it | No | Relies on correct room membership. |
| SOCK-E016 | `unread-count-update` | Server -> Client | NotificationService | `user-{userId}` | Low (count only) | No | |
| SOCK-E017 | `new-purchase-request` | Server -> Client | PurchaseRequestService | `sellers` | Contains request details; some fields may be sensitive | No | Broadcast to all sellers. |
| SOCK-E018 | `new-offer` | Server -> Client | SellerOfferService | `buyer-{buyerId}` | Contains offer price; financial data | No | |
| SOCK-E019 | `seller-offer-update` | Server -> Client | SellerOfferService | `seller-{sellerId}` + global on payment | Contains offer status and payment info | No | Global emit leaks data. |
| SOCK-E020 | `purchase-request-update` | Server -> Client | PurchaseRequestService | `request-{requestId}` | Contains status and tx hashes | No | |
| SOCK-E021 | `request-cancelled` | Server -> Client | PurchaseRequestService | `user-{buyerId}`, `user-{sellerId}` | Contains request ID | No | |
| SOCK-E022 | `transaction-completed` | Server -> Client | MarketplaceController | `user-{buyerId}`, `user-{sellerId}` | Contains amount and currency | No | |
| SOCK-E023 | `delivery-code-generated` | Server -> Client | DeliveryService | `request-{requestId}` | Contains 6-digit code. HIGH sensitivity. | No | Code exposed to room; only seller should see it. |
| SOCK-E024 | `delivery-update` | Server -> Client | DeliveryService | `request-{requestId}` | Contains carrier/tracking | No | |
| SOCK-E025 | `delivery-confirmed` | Server -> Client | DeliveryService | `request-{requestId}` | Contains request ID only | No | |
| SOCK-E026 | `buyer-confirmed-delivery` | Server -> Client | DeliveryService | `user-{sellerId}` | Contains buyerId | No | |
| SOCK-E027 | `payment-created` | Server -> Client | PaymentService | Global | Contains paymentId, amount, currency, parties | No | Global emit leaks financial metadata. |
| SOCK-E028 | `payment-received` | Server -> Client | PaymentRoutes | `user-{sellerId}` | Contains amount, buyerId | No | |
| SOCK-E029 | `payment-update` | Server -> Client | PaymentCoordinator | Global + room-specific | Contains status, escrowState, txHash | No | Global emit is too broad. |
| SOCK-E030 | `payout-created` | Server -> Client | PayoutService | Global | Contains payoutId, sellerId, amount | No | Global emit. Should be targeted. |
| SOCK-E031 | `payout-completed` | Server -> Client | PayoutService | Global, `user-{sellerId}` | Contains txHash | No | |
| SOCK-E032 | `payout-updated` | Server -> Client | PayoutService | Global | Contains status | No | |
| SOCK-E033 | `new-message` | Server -> Client | ChatService | `chat-{chatId}` | Contains message content; potentially sensitive | No | |
| SOCK-E034 | `messages-read` | Server -> Client | ChatService | `chat-{chatId}` | Low (read receipt) | No | |
| SOCK-E035 | `message-edited` | Server -> Client | ChatService | `chat-{chatId}` | Contains new content | No | |
| SOCK-E036 | `message-deleted` | Server -> Client | ChatService | `chat-{chatId}` | Low (messageId) | No | |
| SOCK-E037 | `participants-added` | Server -> Client | ChatService | `chat-{chatId}` | Contains user IDs | No | |
| SOCK-E038 | `participant-removed` | Server -> Client | ChatService | `chat-{chatId}` | Contains user ID | No | |
| SOCK-E039 | `user-typing` | Server -> Client | Socket handler | `chat-{chatId}` | Contains userId, userName | No | |
| SOCK-E040 | `user-status-change` | Server -> Client | Socket handler | Broadcast | Contains userId, lastSeen | No | |
| SOCK-E041 | `level-up` | Server -> Client | PointsService | `user-{userId}` | Low (level info) | No | |
| SOCK-E042 | `referral-reward` | Server -> Client | PointsService | `user-{referrerId}` | Contains points earned | No | |
| SOCK-E043 | `referral-signup` | Server -> Client | AuthController | `user-{referrerId}` | Contains referred user info | No | |
| SOCK-E044 | `template-checkout-payment-confirmed` | Server -> Client | PaymentCoordinator | Global + `template-checkout-{id}` | Contains paymentId, requestIds | No | Global emit. |
| SOCK-E045 | `template-checkout-payment-pending` | Server -> Client | PaymentCoordinator | Global | Contains checkoutId | No | Global emit. |
| SOCK-E046 | `template-checkout-payment-failed` | Server -> Client | PaymentCoordinator | Global | Contains checkoutId, reason | No | Global emit. |
---
## Section 4: Current Gaps and Required Fixes
### 4.1 CRITICAL -- Authentication Missing but Required
| Gap ID | Endpoint / Event | Threat IDs | Description | Impact |
|---|---|---|---|---|
| GAP-C001 | POST /api/payment/decentralized/save | T11, T21 | No authentication. Anyone can persist Web3 payment records. | Payment fraud; data poisoning |
| GAP-C002 | PUT /api/payment/decentralized/update | T11 | No authentication. Anyone can update decentralized payment status/confirmations. | Payment status manipulation |
| GAP-C003 | GET /api/payment/decentralized/history/:userId | T21 | No authentication. Anyone can read any user's payment history. | Privacy breach; data exfiltration |
| GAP-C004 | POST /api/payment/decentralized/verify/:paymentId | T01, T11 | No authentication. Anyone can trigger chain re-verification. | DoS; payment fraud if verification is flawed |
| GAP-C005 | POST /api/payment/decentralized/verify-all-pending | T11 | No authentication. Anyone can trigger batch verification. | DoS; resource exhaustion |
| GAP-C006 | GET /api/payment/decentralized/status/:paymentId | T21 | No authentication. Payment status exposed. | Information disclosure |
| GAP-C007 | POST /api/payment/shkeeper/create-test-payment | T11, T21 | No authentication. Injects fake payment records. | Data poisoning in production |
| GAP-C008 | POST /api/ai/generate | T08 | No authentication. Unlimited OpenAI cost abuse. | Financial cost; DoS |
| GAP-C009 | POST /api/ai/analyze | T08 | No authentication. | Financial cost |
| GAP-C010 | POST /api/ai/translate | T08 | No authentication. | Financial cost |
| GAP-C011 | POST /api/ai/assist | T08 | No authentication. | Financial cost |
| GAP-C012 | POST /api/auth/force-verify-user | T09 | No authentication gate. Bypasses email verification. | Account takeover |
| GAP-C013 | POST /api/payment/shkeeper/test | T11 | No authentication in production. | Test data in production |
| GAP-C014 | POST /api/payment/shkeeper/callback-test | T11 | No authentication in production. | Test data in production |
| GAP-C015 | GET /api/payment/shkeeper/callback-test | T11 | No authentication in production. | Test data in production |
| GAP-C016 | Legacy notification router (mounted without auth) | T21 | Accepts ?userId= query parameter for notification read/modify. | Privacy breach; notification manipulation |
| GAP-C017 | Socket.IO room join events | T03 | Client-supplied userId/requestId/chatId; server verification uncertain. Any authenticated user may subscribe to any other user's rooms. | Private data exfiltration; real-time surveillance |
### 4.2 CRITICAL -- Ownership Check Missing but Required
| Gap ID | Endpoint / Event | Threat IDs | Description | Impact |
|---|---|---|---|---|
| GAP-C018 | PATCH /api/marketplace/offers/:id (post-acceptance) | T19 | updateOffer does not enforce status check. Seller can change price after acceptance. | Financial fraud |
| GAP-C019 | POST /api/payment/shkeeper/:id/release | T06 | No check for active dispute before release. Escrow released during active dispute. | Fund loss |
| GAP-C020 | POST /api/payment/shkeeper/:id/refund | T06 | Same as above for refund. | Fund loss |
| GAP-C021 | Web3 payment verification (BSCTransactionVerifier) | T01 | Only checks receipt.status, does not verify recipient address, token contract, or amount. | Payment fraud |
### 4.3 HIGH -- Rate Limiting Missing but Required
| Gap ID | Endpoint / Event | Threat IDs | Description | Impact |
|---|---|---|---|---|
| GAP-H001 | Global rate limiting disabled | T12 | express-rate-limit disabled in app.ts. All endpoints unprotected. | All abuse vectors open |
| GAP-H002 | POST /api/auth/register | T07 | No rate limit. Email spam. | SMTP cost; reputation damage |
| GAP-H003 | POST /api/auth/resend-verification | T07 | No rate limit. Email spam. | SMTP cost |
| GAP-H004 | POST /api/auth/login | T12 | Redis lockout exists but no IP-level rate limit. | Brute force |
| GAP-H005 | POST /api/marketplace/purchase-requests/:id/delivery-code/verify | T20 | No rate limit on 6-digit code verification. 900K combinations brute-forceable. | Delivery fraud |
| GAP-H006 | POST /api/ai/* (all four) | T08 | No rate limit. No per-user budget. | OpenAI cost explosion |
| GAP-H007 | POST /api/payment/decentralized/* | T11 | No rate limit even when auth is added. | Database flooding |
| GAP-H008 | POST /api/chat/:id/messages | T12 | No rate limit on chat messages. | Chat spam |
### 4.4 HIGH -- State Precondition Check Missing but Required
| Gap ID | Endpoint / Event | Threat IDs | Description | Impact |
|---|---|---|---|---|
| GAP-H009 | POST /api/disputes (dispute creation) | T06 | Does not set escrowState=disputed. No escrow hold on dispute creation. | Funds released during dispute |
| GAP-H010 | PATCH /api/marketplace/purchase-requests/:id/confirm-delivery | T20 | Buyer can confirm delivery before seller ships (manual fast-track). | Escrow released without delivery |
| GAP-H011 | PUT /api/marketplace/offers/:id/status | T09 | No documented admin-only enforcement on direct status mutation. | Unauthorized offer manipulation |
### 4.5 MEDIUM -- Audit Logging Missing but Required
| Gap ID | Endpoint / Event | Description | Impact |
|---|---|---|---|
| GAP-M001 | All admin routes (user management) | No structured audit log for admin user management actions. | No accountability for admin actions |
| GAP-M002 | POST /api/payment/shkeeper/:id/release | No append-only audit log for escrow release. | No financial audit trail |
| GAP-M003 | POST /api/payment/shkeeper/:id/refund | No append-only audit log for escrow refund. | No financial audit trail |
| GAP-M004 | POST /api/payment/shkeeper/payout | No append-only audit log for payout creation. | No financial audit trail |
| GAP-M005 | POST /api/disputes/:id/resolve | No append-only audit log for dispute resolution. | No dispute resolution trail |
| GAP-M006 | PATCH /api/user/admin/:userId/role | No audit log for role changes. | No accountability |
| GAP-M007 | DELETE /api/user/admin/:userId | No audit log for user deletion. | No accountability |
| GAP-M008 | POST /api/points/admin/add | No structured audit log for manual point grants. | No points audit trail |
| GAP-M009 | POST /api/auth/change-password | No audit log for password changes. | No security event trail |
| GAP-M010 | Global emit events (payment-created, payment-update, payout-created, etc.) | Sensitive financial data broadcast globally via Socket.IO. Should be targeted. | Data overexposure |
### 4.6 Socket.IO-Specific Gaps
| Gap ID | Issue | Threat IDs | Description | Impact |
|---|---|---|---|---|
| GAP-S001 | No handshake JWT authentication | T03 | Socket Events doc states "no token-based handshake" -- ownership checked at REST layer only. | Unauthenticated socket connections |
| GAP-S002 | Client-driven room joins | T03 | All join-* events accept arbitrary IDs. Server must verify socket.data.user.id matches. | Eavesdropping on other users |
| GAP-S003 | delivery-code-generated event to request room | T03 | 6-digit code emitted to request room where both buyer and seller are present. Code should only go to seller. | Buyer can intercept own code |
| GAP-S004 | Global payment events | T03, T21 | payment-created, payment-update, payout-created emitted globally. Financial data exposed to all connected sockets. | Data overexposure |
| GAP-S005 | No socket event rate limiting | T12 | No per-event rate limiting on typing, messages, or room joins. | Socket spam; DoS |
| GAP-S006 | No offline status tracking | -- | No userId-to-socketId mapping stored; no offline broadcast on disconnect. | Stale presence data |
---
## Section 5: Implementation Priority
### P0 -- Fix Immediately (Blocks Launch)
These gaps allow unauthenticated or unauthorized access to financial data or fund manipulation. They must be resolved before any public deployment.
| Priority | Gap IDs | Work Required | Threat IDs | Estimated Effort |
|---|---|---|---|---|
| P0-1 | GAP-C001, C002, C003, C004, C005, C006 | Add Bearer JWT auth to all /api/payment/decentralized/* endpoints. Add ownership check (buyer on referenced purchase request, or admin). | T11, T21 | 1-2 days |
| P0-2 | GAP-C007, C013, C014, C015 | Remove or disable test/demo endpoints in production builds. Add NODE_ENV gate that returns 404 in production. | T11 | 0.5 days |
| P0-3 | GAP-C008, C009, C010, C011 | Add Bearer JWT auth to all /api/ai/* endpoints. Add per-user daily token budget. | T08 | 0.5 days |
| P0-4 | GAP-C012 | Remove /api/auth/force-verify-user from production builds or gate behind NODE_ENV + admin role. | T09 | 0.5 hours |
| P0-5 | GAP-C016 | Remove legacy notification router or add authenticateToken to all its routes. Ensure controller router wins for all shared paths. | T21 | 1 day |
| P0-6 | GAP-C019, C020, H009 | Add escrowState=disputed check before release/refund. DisputeService.createDispute must set escrow hold. PaymentCoordinator must enforce it. | T06 | 1 day |
| P0-7 | GAP-C021 | Decode Transfer event in BSCTransactionVerifier. Verify recipient==ESCROW_WALLET, value>=expectedAmount, token contract matches. | T01 | 1 day |
| P0-8 | GAP-C017, S001, S002 | Require JWT in Socket.IO handshake. Server auto-joins user-{decoded.id}. For role rooms, derive from decoded.role. For chat rooms, verify participants. Remove client-driven join-* events. | T03 | 2-3 days |
| P0-9 | GAP-H001 | Enable global rate limiting in app.ts. Configure tiered limits per Section 2. | T12 | 0.5 days |
### P1 -- Fix Before Launch
These gaps involve missing ownership checks, rate limits on authentication paths, and state precondition enforcement. They represent significant security risks but are slightly lower priority than unauthenticated financial access.
| Priority | Gap IDs | Work Required | Threat IDs | Estimated Effort |
|---|---|---|---|---|
| P1-1 | GAP-C018 | Reject PATCH /api/marketplace/offers/:id if offer.status !== 'pending'. Snapshot offer amount at payment creation. | T19 | 0.5 days |
| P1-2 | GAP-H002, H003 | Add Tier 1 rate limiting on /api/auth/register, resend-verification. 5 req/5 min/IP. | T07 | 0.5 days |
| P1-3 | GAP-H004 | Add Tier 1 rate limiting on /api/auth/login. 5 req/5 min/IP. Existing Redis lockout remains. | T12 | 0.5 days |
| P1-4 | GAP-H005 | Add Redis-backed rate limit on delivery code verify. 5 attempts/15 min/request. | T20 | 0.5 days |
| P1-5 | GAP-H006 | Add Tier 2 rate limiting on /api/ai/*. 20 req/15 min/user. | T08 | 0.5 days |
| P1-6 | GAP-H008 | Add Tier 3 rate limiting on /api/chat/:id/messages. 60 req/15 min/user. | T12 | 0.5 days |
| P1-7 | GAP-H010 | Restrict delivery confirmation to status=delivery only. Remove manual fast-track or require admin override. | T20 | 0.5 days |
| P1-8 | GAP-H011 | Add roleGuard('admin') to PUT /api/marketplace/offers/:id/status. | T09 | 0.5 hours |
| P1-9 | GAP-S003 | Emit delivery-code-generated only to seller, not to entire request room. | T03 | 0.5 hours |
| P1-10 | GAP-S004 | Change payment-created, payment-update, payout-created from global emit to targeted room emits (user-{buyerId}, user-{sellerId}). | T03, T21 | 1 day |
| P1-11 | GAP-S005 | Add per-socket rate limiting for high-frequency events (typing, messages). | T12 | 1 day |
### P2 -- Fix Post-Launch
These gaps involve audit logging and presence tracking. They are important for operational security and compliance but do not represent immediate attack vectors.
| Priority | Gap IDs | Work Required | Estimated Effort |
|---|---|---|---|
| P2-1 | GAP-M001, M002, M003, M004, M005 | Implement append-only audit log collection for all admin actions, payment mutations, dispute resolutions. Include actor, target, action, before/after diff, request ID. | 3-5 days |
| P2-2 | GAP-M006, M007, M008 | Add structured audit logging for role changes, user deletions, manual point grants. | 1 day |
| P2-3 | GAP-M009 | Add audit logging for password changes and account deletions. | 0.5 days |
| P2-4 | GAP-M010 | Audit all global emit events; convert to targeted room emits where possible. | 1 day |
| P2-5 | GAP-S006 | Implement userId-to-socketId mapping. Emit user-status-change offline on disconnect. | 0.5 days |
| P2-6 | GAP-H007 | Add rate limiting on /api/payment/decentralized/* once auth is in place. | 0.5 days |
---
## Section 6: Summary Statistics
### REST Endpoint Count
| Route Group | Endpoints |
|---|---|
| Auth | 28 |
| User | 9 |
| User Admin | 13 |
| Admin Approval | 3 |
| Address | 5 |
| Purchase Request | 18 |
| Delivery Code | 4 |
| Seller Offer | 10 |
| Request Template | 11 |
| Shop Settings | 3 |
| Category / Seller Directory | 6 |
| Review | 3 |
| Payment (General) | 18 |
| Payment (SHKeeper Pay-in) | 11 |
| Payment (SHKeeper Release/Refund) | 4 |
| Payment (SHKeeper Payout) | 3 |
| Payment (Decentralized) | 8 |
| Marketplace Payment (Legacy) | 5 |
| Chat | 15 |
| Notification | 9 |
| Dispute | 8 |
| AI | 4 |
| Blog | 10 |
| Points | 8 |
| File | 9 |
| Admin Cleanup | 7 |
| System | 2 |
| **Total REST Endpoints** | **255** |
### Socket.IO Event Count
| Category | Events |
|---|---|
| Connection/Handshake | 2 |
| Room Join/Leave | 9 |
| Chat Events | 2 |
| Presence Events | 1 |
| Server-to-Client | 32 |
| **Total Socket Events** | **46** |
### Gap Summary
| Severity | Count | Gap IDs |
|---|---|---|
| CRITICAL (auth missing) | 17 | GAP-C001 through GAP-C017 |
| CRITICAL (ownership missing) | 4 | GAP-C018 through GAP-C021 |
| HIGH (rate limit missing) | 8 | GAP-H001 through GAP-H008 |
| HIGH (state precondition missing) | 3 | GAP-H009 through GAP-H011 |
| MEDIUM (audit log missing) | 10 | GAP-M001 through GAP-M010 |
| Socket.IO specific | 6 | GAP-S001 through GAP-S006 |
| **Total Gaps** | **48** | |
### Priority Distribution
| Priority | Gap Count | Work Estimate |
|---|---|---|
| P0 (blocks launch) | 9 work items covering 24 gaps | ~8-10 days |
| P1 (before launch) | 11 work items covering 11 gaps | ~5-6 days |
| P2 (post-launch) | 6 work items covering 13 gaps | ~7-8 days |
---
*This document was produced on 2026-05-24 as part of the Amanat authorization audit. It must be updated when: new endpoints are added, existing endpoint access levels change, new Socket.IO events are introduced, or the role model is extended. Implementation tasks should reference specific AUTH-R, USER-R, UADM-R, APV-R, ADDR-R, PR-R, DC-R, OFF-R, TPL-R, SHOP-R, CAT-R, REV-R, PAY-R, SHK-R, REL-R, PO-R, DEC-R, MPAY-R, CHAT-R, NOTIF-R, DIS-R, AI-R, BLOG-R, PTS-R, FILE-R, ADM-R, SYS-R, and SOCK-E IDs from this matrix.*

View File

@@ -0,0 +1,117 @@
---
title: Backend Core Stack Decision Record - 2026-05-24
tags: [adr, architecture, backend]
created: 2026-05-24
status: approved
reviewers: [CTO, backend, security]
---
# Backend Core Stack Decision Record - 2026-05-24
## 1. Decision
Keep the security-critical backend core on **TypeScript/Node** in the first 12 months.
Do **not** perform a full greenfield rewrite before the payment/auth/escrow core is fully specified and observable.
## 2. Why this stack (today)
The highest current risk is not framework selection; it is **financial state correctness**.
For the next phase, the team needs:
- provider-neutral payment abstraction,
- immutable funds ledger,
- webhook hardening and reconciliation,
- strict dispute hold behavior,
- admin step-up controls,
- production-grade operational runbooks.
Moving to Go/Kotlin/Rust now would preserve existing risks while adding migration uncertainty and a delay in launch-readiness.
TypeScript remains the fastest way to ship the required controls while keeping operational visibility and team velocity.
## 3. Scope of extraction
The “backend core” now means:
- `Payment` orchestration and payout/release state transitions,
- auth/session validation for financial actions,
- webhook intake and reconciliation,
- ledger-derived escrow eligibility checks,
- admin-risk operations (payout/refund/adjustment).
These modules stay in the same service boundary during migration-in-place, but all calls must go through:
- [[Payment Provider Adapter Spec]]
- [[Webhook Security Spec]]
- [[Funds Ledger and Escrow State Machine Specification]]
Non-core modules remain where they are:
- marketplace browsing, templates, shop settings,
- chat and notifications,
- file uploads/downloads.
## 4. Evaluation of alternatives
### Go
- **Pros:** smaller runtime and dependency surface, better static guarantees.
- **Cons:** highest immediate migration cost, new operational tooling, delayed delivery of core money-movement correctness.
### Kotlin/Java
- **Pros:** strong enterprise ecosystem, mature auth/security libraries.
- **Cons:** heavier stack and slower delivery for a small team.
### Rust
- **Pros:** high correctness potential.
- **Cons:** steep delivery cost and limited team familiarity.
### Keep TypeScript (selected)
- **Pros:** existing team velocity, reduced migration risk, direct integration with current deployment and frontend contracts.
- **Cons:** npm supply-chain risk remains; mitigated by [[Secure Build and Supply-Chain Policy]] and strict dependency policy.
## 5. Migration and rollout plan
1. **Phase A (this quarter):** lock down high-risk flows in TypeScript (ledger, adapter, webhook, auth/session, runbooks).
2. **Phase B (next two quarters):** extract core services behind stable interfaces and add adapter-level contract tests.
3. **Phase C (deferred):** evaluate Go/Kotlin pilot for payout+webhook worker only if:
- Phase A and B are stable for 60 days,
- team staffing supports dual-stack operations,
- audit requirements demand lower runtime dependency exposure.
## 6. Non-goals
- Full frontend rewrite.
- New language migration without closed-loop reconciliation and signed-state invariants.
- New provider support that bypasses the adapter contract.
## 7. Rollback criteria
- Any increase in incident rate above baseline +20% for 24h after migration activity.
- Any unresolved ledger invariant violation (held + disputed + released + refunded mismatch).
- Any provider outage recovery that requires non-operator-tuned workarounds.
Rollback to prior TS implementation:
- disable any split deployment feature flag,
- switch `PAYMENT_ENABLED_PROVIDERS` back to legacy-only,
- freeze new provider routing until incident review is complete,
- complete post-incident update in this ADR.
## 8. Ownership
- **CTO:** final stack decision + dual-stack approvals.
- **Backend Lead (BL):** contract and adapter enforcement.
- **Security Lead (SL):** webhook/security acceptance criteria.
- **DevOps Lead (DL):** deployment safety and rollback testing.
## Related
- [[Backend Stack Security and Refactor Assessment - 2026-05-24]]
- [[Secure Build and Supply-Chain Policy]]
- [[Backend Funds Migration and Operational Runbooks]]

View File

@@ -345,7 +345,7 @@ Should include:
- Trust boundaries: browser, backend, database, Redis, provider APIs, wallet/RPC, admin UI, Socket.IO. - Trust boundaries: browser, backend, database, Redis, provider APIs, wallet/RPC, admin UI, Socket.IO.
- Abuse cases: fake payment proof, replayed webhook, arbitrary room join, stolen token, double payout, dispute bypass, email/AI abuse. - Abuse cases: fake payment proof, replayed webhook, arbitrary room join, stolen token, double payout, dispute bypass, email/AI abuse.
### 2. Funds Ledger Specification ### 2. Funds Ledger and Escrow State Machine Specification
Purpose: make money movement auditable and provider-independent. Purpose: make money movement auditable and provider-independent.
@@ -386,9 +386,7 @@ Should map every endpoint and socket event to:
### 5. Payment Provider Adapter Spec ### 5. Payment Provider Adapter Spec
Purpose: decouple business logic from SHKeeper, Request Network, manual wallet flow, and future providers. Implemented as [[Payment Provider Adapter Spec]], including:
Should define:
- `createPayInIntent` - `createPayInIntent`
- `getPayInStatus` - `getPayInStatus`
@@ -399,13 +397,11 @@ Should define:
- `getPayoutStatus` - `getPayoutStatus`
- `searchProviderPayments` - `searchProviderPayments`
Provider-specific metadata should be namespaced and never become the canonical funds state. Provider-specific metadata is namespaced and never used as canonical funds state.
### 6. Webhook Security Spec ### 6. Webhook Security Spec
Purpose: prevent forged, replayed, or silently failed provider events. Implemented as [[Webhook Security Spec]]:
Should define:
- Raw-body signature verification. - Raw-body signature verification.
- Accepted headers and algorithms. - Accepted headers and algorithms.
@@ -434,6 +430,8 @@ Should define:
### 8. Realtime Authorization Spec ### 8. Realtime Authorization Spec
Implemented as [[Realtime Authorization Spec]].
Purpose: make Socket.IO events subject to the same security model as REST. Purpose: make Socket.IO events subject to the same security model as REST.
Should define: Should define:
@@ -476,9 +474,7 @@ Should define:
### 11. Operational Runbooks ### 11. Operational Runbooks
Purpose: make security incidents and payment failures survivable. Implemented as [[Backend Funds Migration and Operational Runbooks]]:
Should include:
- Failed webhook. - Failed webhook.
- Duplicate payment. - Duplicate payment.

View File

@@ -0,0 +1,944 @@
---
title: Funds Ledger and Escrow State Machine Specification
tags: [specification, funds-ledger, escrow, state-machine, canonical]
created: 2026-05-24
status: canonical
reviewers: [backend, security, product]
---
# Funds Ledger and Escrow State Machine Specification
> This document is the **canonical specification** for money movement, immutable ledger entries, derived balance views, and the full escrow/payment/dispute state machine for the Amanat platform. All implementations, API contracts, and data models must conform to this specification. Any prior document that conflicts with this specification is superseded.
## Cross-references
- [[Threat Model - Amanat Escrow Platform]] -- T05 (double payout), T06 (dispute bypass), T18 (insider manipulation), T19 (seller price manipulation), T23 (state machine inconsistency)
- [[Platform Logical Audit - 2026-05-24]] -- Findings 1, 5, 9, 20, 21
- [[Backend Stack Security and Refactor Assessment - 2026-05-24]] -- Phase 2: Payment and ledger extraction
- [[Escrow Flow]] -- current escrow state machine (superseded by this document)
- [[Payment Flow - SHKeeper]] -- SHKeeper pay-in path
- [[Payment Flow - DePay & Web3]] -- Web3 pay-in path
- [[Dispute Flow]] -- dispute lifecycle (status enums superseded by this document)
- [[Payout Flow]] -- seller payout mechanics
- [[Payment]] -- current Payment data model
- [[PurchaseRequest]] -- current PurchaseRequest data model
- [[Dispute]] -- current Dispute data model
- [[SellerOffer]] -- current SellerOffer data model
---
# Part 1: Funds Account and Ledger Model
## 1.1 FundsAccount
A `FundsAccount` is created per purchase request when a payment intent is first initiated. It is the single source of truth for all money movement associated with a transaction.
### Schema
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `accountId` | UUID (string) | yes | auto-generated | Primary key |
| `purchaseRequestId` | ObjectId | yes | -- | Reference to [[PurchaseRequest]] |
| `buyerId` | ObjectId | yes | -- | Buyer user reference |
| `sellerId` | ObjectId | yes | -- | Seller user reference |
| `sellerOfferId` | ObjectId | yes | -- | Accepted [[SellerOffer]] reference |
| `currency` | string | yes | `"USDT"` | Settlement currency (USDT, USDC) |
| `grossAmountPaid` | Decimal128 | yes | `0` | Total amount received from buyer (gross, before fees) |
| `providerFees` | Decimal128 | no | `0` | Fees taken by the payment provider (SHKeeper, gas, etc.) |
| `platformFees` | Decimal128 | no | `0` | Platform commission |
| `heldAmount` | Decimal128 | no | `0` | Amount held in escrow (not disputed) |
| `disputedAmount` | Decimal128 | no | `0` | Amount under dispute hold |
| `releasableAmount` | Decimal128 | no | `0` | Amount available for release or refund |
| `releasedAmount` | Decimal128 | no | `0` | Total amount released to seller |
| `refundedAmount` | Decimal128 | no | `0` | Total amount refunded to buyer |
| `status` | string (enum) | yes | `"active"` | `ACTIVE`, `SETTLED`, `CANCELLED` |
| `providerReference` | string | no | -- | External provider payment ID (SHKeeper external_id, etc.) |
| `escrowWalletAddress` | string | no | -- | On-chain address holding the funds |
| `settlementTxHash` | string | no | -- | Final settlement on-chain transaction hash |
| `idempotencyKey` | string | yes | auto-generated | Prevents duplicate account creation for the same intent |
| `createdAt` | Date | yes | `Date.now()` | Creation timestamp |
| `updatedAt` | Date | yes | `Date.now()` | Last modification timestamp |
| `settledAt` | Date | no | -- | When the account reached SETTLED status |
| `cancelledAt` | Date | no | -- | When the account reached CANCELLED status |
### Status enum
| Value | Description |
|-------|-------------|
| `ACTIVE` | Funds are in motion or held. Ledger entries can still be appended. |
| `SETTLED` | All funds have been released and/or refunded. No further entries allowed except adjustments. |
| `CANCELLED` | The transaction was cancelled before any funds were received. |
### Indexes
- `{ purchaseRequestId: 1 }` (unique) -- one account per purchase request
- `{ buyerId: 1, status: 1 }` -- buyer dashboard queries
- `{ sellerId: 1, status: 1 }` -- seller dashboard queries
- `{ providerReference: 1 }` (sparse) -- webhook lookup
- `{ idempotencyKey: 1 }` (unique) -- prevents duplicate creation
---
## 1.2 LedgerEntry (Immutable, Append-Only)
Every financial event creates an immutable ledger entry. Entries are never modified or deleted after creation.
### Schema
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `entryId` | UUID (string) | yes | auto-generated | Primary key |
| `accountId` | UUID (string) | yes | -- | Reference to FundsAccount |
| `entryType` | string (enum) | yes | -- | See entry type enum below |
| `amount` | Decimal128 | yes | -- | Amount for this entry (always positive) |
| `currency` | string | yes | -- | Currency of the amount |
| `runningBalance` | Object | yes | -- | Snapshot of all balances after this entry (see below) |
| `providerTxHash` | string | no | -- | On-chain transaction hash (if applicable) |
| `actor` | Object | yes | -- | Who/what triggered this entry |
| `actor.type` | string (enum) | yes | -- | `SYSTEM`, `ADMIN`, `BUYER`, `SELLER`, `PROVIDER_WEBHOOK`, `CRON_JOB` |
| `actor.userId` | ObjectId | no | -- | User ID if actor is a person |
| `actor.serviceName` | string | no | -- | Service name if actor is automated (e.g., `"PaymentCoordinator"`) |
| `idempotencyKey` | string | yes | -- | Prevents duplicate entries |
| `sourceEvent` | Object | no | -- | Reference to the originating event |
| `sourceEvent.webhookDeliveryId` | string | no | -- | Provider webhook delivery ID |
| `sourceEvent.disputeId` | ObjectId | no | -- | Dispute reference |
| `sourceEvent.payoutInstructionId` | string | no | -- | Payout instruction reference |
| `sourceEvent.adminActionId` | string | no | -- | Admin action audit reference |
| `description` | string | no | -- | Human-readable description |
| `createdAt` | Date | yes | `Date.now()` | Immutable creation timestamp |
### Entry type enum
| Value | Direction | Description |
|-------|-----------|-------------|
| `PAY_IN` | Credit (+) | Buyer payment received and confirmed |
| `PROVIDER_FEE` | Debit (-) | Fee deducted by the payment provider |
| `PLATFORM_FEE` | Debit (-) | Platform commission deducted |
| `HOLD` | Neutral (allocation) | Funds moved to held status in escrow |
| `DISPUTE_HOLD` | Neutral (allocation) | Funds moved from held to disputed due to dispute |
| `RELEASE` | Debit (-) | Funds released to seller |
| `REFUND` | Debit (-) | Funds refunded to buyer |
| `ADJUSTMENT` | +/- | Manual correction by admin (requires step-up auth) |
| `REVERSAL` | +/- | Reversal of a previous entry (e.g., failed payout, cancelled hold) |
### Running balance snapshot
Each entry includes a snapshot of the FundsAccount balances immediately after the entry is applied:
```
runningBalance: {
grossPaid: Decimal128,
providerFees: Decimal128,
platformFees: Decimal128,
held: Decimal128,
disputed: Decimal128,
releasable: Decimal128,
released: Decimal128,
refunded: Decimal128
}
```
### Immutability rules
1. Ledger entries MUST be append-only. No UPDATE or DELETE operations are permitted.
2. The `createdAt` field is set once and MUST never be modified.
3. The `entryId` MUST be a UUID v4 generated at creation time.
4. The collection SHOULD use MongoDB's `capped: false` with application-level enforcement (no update/delete hooks).
### Indexes
- `{ accountId: 1, createdAt: 1 }` -- chronological query per account
- `{ accountId: 1, entryType: 1 }` -- balance derivation queries
- `{ accountId: 1, idempotencyKey: 1 }` (unique) -- prevents duplicate entries
- `{ providerTxHash: 1 }` (sparse) -- lookup by on-chain transaction
- `{ "sourceEvent.disputeId": 1 }` -- dispute-related entries
- `{ "sourceEvent.webhookDeliveryId": 1 }` (sparse) -- webhook idempotency
---
## 1.3 FundsBalance (Derived View)
Balances are NOT stored as mutable fields. They are derived by summing ledger entries. The `runningBalance` snapshot on each entry provides a cache, but the canonical source is always the aggregation of ledger entries.
### Derivation formulas
```
grossPaid = SUM(amount) WHERE entryType = PAY_IN
providerFees = SUM(amount) WHERE entryType = PROVIDER_FEE
platformFees = SUM(amount) WHERE entryType = PLATFORM_FEE
released = SUM(amount) WHERE entryType = RELEASE
refunded = SUM(amount) WHERE entryType = REFUND
held = SUM(amount) WHERE entryType = HOLD
- SUM(amount) WHERE entryType = RELEASE AND heldFunds = true
- SUM(amount) WHERE entryType = REFUND AND heldFunds = true
+ SUM(amount) WHERE entryType = REVERSAL AND reversedType = HOLD
disputed = SUM(amount) WHERE entryType = DISPUTE_HOLD
- SUM(amount) WHERE entryType = RELEASE AND disputedFunds = true
- SUM(amount) WHERE entryType = REFUND AND disputedFunds = true
+ SUM(amount) WHERE entryType = REVERSAL AND reversedType = DISPUTE_HOLD
releasable = grossPaid - providerFees - platformFees - held - disputed - released - refunded
```
### Balance invariant
The following invariant MUST hold at all times:
```
grossPaid = providerFees + platformFees + released + refunded + releasable + held + disputed
```
This invariant MUST be checked:
1. After every ledger entry append.
2. During scheduled reconciliation.
3. Before any release or refund operation.
If the invariant is violated, the system MUST:
1. Halt all release and refund operations for the affected account.
2. Log an alert with severity `critical`.
3. Quarantine the account (set status to `ACTIVE` but flag for manual review).
4. Notify the operations team.
---
## 1.4 Reconciliation
### When reconciliation runs
| Trigger | Frequency | Description |
|---------|-----------|-------------|
| Scheduled | Every 6 hours | Automatic reconciliation job |
| Provider event | On webhook delivery | Cross-check webhook data against ledger |
| On-demand | Manual | Admin triggers reconciliation from dashboard |
| Post-migration | Once | After ledger backfill from legacy Payment records |
### What reconciliation compares
1. **Provider balance vs ledger-derived balance**: Compare the amount reported by the payment provider (SHKeeper invoice status, on-chain wallet balance) against the sum of ledger entries.
2. **On-chain transactions vs ledger entries**: For each `providerTxHash` in the ledger, verify the on-chain transaction exists with matching amount, sender, and recipient.
3. **Payment records vs ledger entries**: Cross-reference legacy `Payment` records with `LedgerEntry` records for consistency.
### Provider-specific event mapping
| Provider Event | Ledger Entry | Mapping |
|----------------|-------------|---------|
| SHKeeper webhook `PAID` | PAY_IN | `amount = balance_fiat`, `idempotencyKey = external_id + "PAID"` |
| SHKeeper webhook `OVERPAID` | PAY_IN + ADJUSTMENT | PAY_IN for expected amount; ADJUSTMENT for surplus |
| SHKeeper webhook `PARTIAL` | PAY_IN | `amount = balance_fiat` (partial amount received) |
| SHKeeper webhook `EXPIRED` | REVERSAL | Reverse any partial PAY_IN entries |
| SHKeeper fee deduction | PROVIDER_FEE | `amount = fee_percent * grossAmount` |
| Web3 verification success | PAY_IN | `amount = verified on-chain amount`, `idempotencyKey = txHash` |
| Payout task completed | RELEASE | `amount = payout amount`, `idempotencyKey = task_id` |
| Admin manual refund | REFUND | `amount = refund amount`, `idempotencyKey = admin-tx-hash` |
### Mismatch handling
| Severity | Condition | Response |
|----------|-----------|----------|
| `info` | Ledger-derived balance matches provider balance within 0.01 currency units | No action, log success |
| `warning` | Difference between 0.01 and 1.00 currency units | Log warning, flag for review, operations notified |
| `critical` | Difference greater than 1.00 currency units | Halt operations on the account, quarantine, create admin alert, require manual resolution |
| `critical` | Missing on-chain transaction for a recorded entry | Halt operations, quarantine, investigate potential fraud (addresses [[T05]], [[T18]]) |
---
## 1.5 Idempotency
### Idempotency key rules
1. Every ledger entry MUST have an `idempotencyKey`.
2. The uniqueness constraint is: `(accountId, idempotencyKey)`.
3. Attempting to insert a duplicate entry with the same `(accountId, idempotencyKey)` MUST be rejected with a conflict error.
4. The system MUST return the existing entry's details on duplicate rejection (not an error that hides the data).
### Idempotency key sources
| Entry Type | Idempotency Key Source | Format |
|------------|----------------------|--------|
| `PAY_IN` (SHKeeper) | Webhook `external_id` + status | `shk:{external_id}:PAID` |
| `PAY_IN` (Web3) | On-chain transaction hash | `w3:{transactionHash}` |
| `PROVIDER_FEE` | Payment reference + `"fee"` | `{paymentRef}:fee` |
| `PLATFORM_FEE` | Payment reference + `"commission"` | `{paymentRef}:commission` |
| `HOLD` | Account ID + `"hold"` | `{accountId}:hold` |
| `DISPUTE_HOLD` | Dispute ID | `dispute:{disputeId}` |
| `RELEASE` | Payout instruction ID or task ID | `release:{taskId}` or `release:{adminTxHash}` |
| `REFUND` | Refund instruction ID or admin tx hash | `refund:{instructionId}` or `refund:{adminTxHash}` |
| `ADJUSTMENT` | Admin action ID | `adj:{adminActionId}` |
| `REVERSAL` | Reference to reversed entry's idempotency key + `"reverse"` | `rev:{originalKey}` |
### FundsAccount idempotency
- Account creation is keyed by `purchaseRequestId` (one account per purchase request).
- The `idempotencyKey` field on FundsAccount is set to `account:{purchaseRequestId}`.
- Attempting to create a second account for the same purchase request MUST return the existing account.
---
# Part 2: Escrow State Machine
## 2.1 Canonical Status Enums
The following enums are the single source of truth. All previous definitions in data model files, API reference documents, and flow documents that conflict with these are superseded.
### 2.1.1 PurchaseRequest statuses
**Canonical enum:**
| Value | Description | Deprecated aliases |
|-------|-------------|-------------------|
| `pending` | Request created, awaiting seller offers | `pending_payment` (Data Model), `draft` (API Reference) |
| `received_offers` | At least one offer received | -- |
| `in_negotiation` | Buyer and seller actively negotiating | -- |
| `payment` | Payment captured, awaiting seller acknowledgment | `active` (Data Model -- deprecated) |
| `processing` | Seller acknowledged, preparing order | -- |
| `delivery` | Seller has shipped or is delivering | -- |
| `delivered` | Buyer has received the goods/service | -- |
| `confirming` | Delivery confirmed, awaiting escrow release | -- |
| `completed` | Escrow released to seller, transaction done | -- |
| `seller_paid` | Payout confirmed on chain (subset of completed) | -- |
| `cancelled` | Transaction cancelled | -- |
**Ghost states resolved:**
| Ghost State | Resolution | Migration |
|-------------|-----------|-----------|
| `draft` | Removed from canonical enum. Requests are created as `pending`. No `draft` state exists. | Map any `draft` records to `pending`. |
| `finalized` | Removed from canonical enum. `completed` is the terminal state after escrow release. Ratings and feedback are captured as sub-fields of `completed`, not as a separate status. | Map any `finalized` records to `completed`. |
| `archived` | Removed from canonical enum. Archival is a display concern, not a state-machine concern. Use a separate `isArchived` boolean flag or `archivedAt` timestamp. | Map any `archived` records to `completed` with `archivedAt` set. |
| `pending_payment` | Removed. Replaced by `pending`. | Map any `pending_payment` records to `pending`. |
| `active` | Removed. Replaced by `payment` (post-payment) or `pending` (pre-payment). | Map any `active` records to their correct state based on whether payment has been captured. |
**Previous document sources:**
| Document | Status values used | Alignment |
|----------|-------------------|-----------|
| [[PurchaseRequest]] Data Model | `pending_payment`, `pending`, `active`, `received_offers`, `in_negotiation`, `payment`, `processing`, `delivery`, `delivered`, `confirming`, `completed`, `seller_paid`, `cancelled` | `pending_payment`, `active` deprecated |
| [[Marketplace API]] | `draft`, `pending`, `payment`, `processing`, `delivery`, `delivered`, `seller_paid`, `completed`, `cancelled` | `draft` deprecated |
| [[Purchase Request Flow]] | `pending`, `received_offers`, `in_negotiation`, `payment`, `processing`, `delivery`, `delivered`, `confirming`, `completed`, `finalized`, `archived`, `cancelled` | `finalized`, `archived` deprecated |
### 2.1.2 Payment statuses
**Canonical enum:**
| Value | Description | Deprecated aliases |
|-------|-------------|-------------------|
| `PENDING` | Payment intent created, awaiting funds | `pending` |
| `PROCESSING` | Funds detected, awaiting confirmation | `processing` |
| `COMPLETED` | Funds confirmed and escrow funded | `completed`, `confirmed` (skipped) |
| `FAILED` | Payment failed (expired, rejected, reverted) | `failed` |
| `CANCELLED` | Payment cancelled before funds arrived | `cancelled` |
| `RELEASED` | Escrow released to seller | -- |
| `REFUNDED` | Escrow refunded to buyer | -- |
**Ghost states resolved:**
| Ghost State | Resolution | Migration |
|-------------|-----------|-----------|
| `confirmed` | Removed. The SHKeeper webhook maps `PAID` directly to `COMPLETED`. `confirmed` was an intermediate state that no automated flow sets. | Map any `confirmed` records to `COMPLETED`. |
### 2.1.3 Payment escrowState values
**Canonical enum:**
| Value | Description | Deprecated aliases |
|-------|-------------|-------------------|
| `FUNDED` | Buyer payment confirmed, funds held in escrow | `funded` |
| `PARTIALLY_FUNDED` | Partial payment received, awaiting full amount | `partial` (Escrow Flow, Payment Flow - SHKeeper) |
| `RELEASABLE` | Delivery confirmed, funds ready for release | `releasable` |
| `DISPUTED` | Active dispute hold, all releases/refunds blocked | -- (NEW -- addresses [[T06]]) |
| `RELEASING` | Payout initiated, awaiting on-chain confirmation | `releasing` |
| `RELEASED` | Payout confirmed on chain, funds transferred to seller | `released` |
| `REFUNDING` | Refund initiated, awaiting on-chain confirmation | -- |
| `REFUNDED` | Refund confirmed on chain, funds returned to buyer | `refunded` |
| `FAILED` | Payout or refund failed on chain, retry required | `failed` |
| `CANCELLED` | Payment expired or cancelled before funding | -- |
**Ghost states resolved:**
| Ghost State | Resolution | Migration |
|-------------|-----------|-----------|
| `partial` | Replaced by `PARTIALLY_FUNDED`. Previously used in [[Escrow Flow]] and [[Payment Flow - SHKeeper]] but absent from the [[Payment]] data model. | Map any `partial` escrowState values to `PARTIALLY_FUNDED`. |
**Key addition: `DISPUTED` state**
The `DISPUTED` escrow state is the primary mitigation for [[T06]] (dispute bypass -- release during active dispute). This state:
1. Is set automatically when a dispute is opened against a funded payment.
2. Blocks all `RELEASE` and `REFUND` operations.
3. Can only be cleared by dispute resolution or admin override with step-up authentication.
4. Replaces the previous (non-functional) "hold flag" described in the [[Dispute API]].
**Key addition: `REFUNDING` state**
The `REFUNDING` state provides parity with `RELEASING` -- when a refund is initiated, the escrow state moves to `REFUNDING` until the on-chain transaction is confirmed, at which point it moves to `REFUNDED`.
**Previous document sources:**
| Document | escrowState values used | Alignment |
|----------|------------------------|-----------|
| [[Payment]] Data Model | `funded`, `releasable`, `released`, `refunded`, `releasing`, `failed` | Add `DISPUTED`, `PARTIALLY_FUNDED`, `REFUNDING`, `CANCELLED` |
| [[Escrow Flow]] | `funded`, `partial`, `releasable`, `releasing`, `released`, `refunded`, `failed` | `partial` -> `PARTIALLY_FUNDED`, add `DISPUTED`, `REFUNDING`, `CANCELLED` |
| [[Payment Flow - SHKeeper]] | `funded`, `partial`, `releasable`, `releasing`, `released`, `refunded`, `cancelled` | Same as Escrow Flow |
| [[Payment API]] | `unfunded`, `funded`, `released`, `refunded` | `unfunded` replaced by null/PENDING status; add missing states |
### 2.1.4 Dispute statuses and resolution actions
This is the resolution of the three mutually incompatible enum sets identified in [[Platform Logical Audit - 2026-05-24]] Finding 5.
**Canonical Dispute status enum:**
| Value | Description | Previously in |
|-------|-------------|---------------|
| `OPEN` | Dispute created, awaiting admin assignment | Data Model: `pending`; API: `open`; Flow: `pending` |
| `UNDER_REVIEW` | Admin assigned, investigation in progress | Data Model: `in_progress`, `waiting_response`; API: `under_review`; Flow: `in_progress` |
| `RESOLVED_BUYER` | Resolved in favor of buyer (refund/partial refund) | API: `resolved_buyer` |
| `RESOLVED_SELLER` | Resolved in favor of seller (release) | API: `resolved_seller` |
| `RESOLVED_SPLIT` | Split decision (partial refund + partial release) | -- (NEW) |
| `REJECTED` | Dispute rejected as invalid or duplicate | Data Model: `rejected` |
| `CLOSED` | Dispute closed (terminal) | Data Model: `closed`; Flow: `closed` |
**Canonical Dispute resolution action enum:**
| Value | Description | Escrow Effect | Previously in |
|-------|-------------|---------------|---------------|
| `REFUND` | Full refund to buyer | escrowState -> REFUNDING -> REFUNDED | Data Model: `refund`; Flow: `refund` |
| `PARTIAL_REFUND` | Partial refund to buyer, partial release to seller | escrowState -> REFUNDING/RELEASING -> split | Data Model: `compensation`; Flow: `partial`; API: `split` |
| `RELEASE` | Full release to seller | escrowState -> RELEASING -> RELEASED | Flow: `release`; API: `seller` |
| `REJECT` | No financial action, escrow returns to previous state | escrowState -> FUNDED (or RELEASABLE) | Flow: `reject`; API: N/A |
| `WARNING` | Seller warned but no financial action | No escrow change | Data Model: `warning_seller` |
| `BAN_SELLER` | Seller banned, funds refunded to buyer | escrowState -> REFUNDING -> REFUNDED | Data Model: `ban_seller` |
| `NO_ACTION` | No financial or disciplinary action | No escrow change | Data Model: `no_action` |
**Previous document sources and their incompatibilities:**
| Document | Status enum | Resolution action enum | Conflict |
|----------|-------------|----------------------|----------|
| [[Dispute]] Data Model | `pending`, `in_progress`, `waiting_response`, `resolved`, `rejected`, `closed` | `refund`, `replacement`, `compensation`, `warning_seller`, `ban_seller`, `no_action` | Uses `resolved` (ambiguous), `replacement` (not financial), `waiting_response` (substate of `in_progress`) |
| [[Dispute API]] | `open`, `under_review`, `resolved_buyer`, `resolved_seller`, `closed` | `buyer`, `seller`, `split` (as `decision`) | Uses outcome as status, `decision` instead of `action`, no `REJECTED` state |
| [[Dispute Flow]] | `pending`, `in_progress`, `resolved`, `closed` | `refund`, `partial`, `release`, `reject` | Uses `partial` (ambiguous), conflates status and action, missing `REJECTED` |
**Resolution rationale:**
1. `RESOLVED_BUYER`, `RESOLVED_SELLER`, `RESOLVED_SPLIT` encode the outcome directly in the status, eliminating ambiguity about who the resolution favored (matching the API design).
2. `UNDER_REVIEW` replaces both `in_progress` and `waiting_response`. The distinction between "admin is actively investigating" and "waiting for a party response" is tracked in the dispute timeline, not in the status.
3. `REJECTED` is distinct from `RESOLVED_*` because a rejection means the dispute was invalid, while a resolution means a judgment was rendered.
4. `replacement` is not a financial action -- it is an operational action between buyer and seller. It is removed from the canonical financial resolution enum. If needed, it can be tracked in the dispute timeline as a non-financial resolution.
5. `PARTIAL_REFUND` replaces `partial`, `compensation`, and the `split` decision, providing a clear financial description.
---
## 2.2 State Transition Diagrams
### 2.2.1 PurchaseRequest lifecycle
```mermaid
stateDiagram-v2
[*] --> pending : buyer creates request
pending --> received_offers : first SellerOffer saved
pending --> cancelled : buyer cancels (no offers)
received_offers --> in_negotiation : buyer engages seller
received_offers --> payment : offer accepted + payment captured
received_offers --> cancelled : buyer cancels
in_negotiation --> received_offers : counter rejected, more offers pending
in_negotiation --> payment : offer accepted + payment captured
in_negotiation --> cancelled : buyer cancels
payment --> processing : seller acknowledges
payment --> cancelled : dispute resolved as refund (pre-shipment)
processing --> delivery : seller marks shipped
processing --> DISPUTED : dispute opened
delivery --> delivered : buyer confirms delivery (code or fast-track)
delivery --> DISPUTED : dispute opened
delivered --> confirming : delivery code verified or fast-track
delivered --> DISPUTED : dispute opened
confirming --> completed : escrow released
confirming --> DISPUTED : dispute opened
DISPUTED --> payment : dispute rejected (no action)
DISPUTED --> processing : dispute rejected (no action)
DISPUTED --> delivery : dispute rejected (no action)
DISPUTED --> delivered : dispute rejected (no action)
DISPUTED --> confirming : dispute rejected (no action)
DISPUTED --> confirming : dispute RESOLVED_SELLER (release)
DISPUTED --> cancelled : dispute RESOLVED_BUYER (refund)
completed --> seller_paid : payout confirmed on chain
seller_paid --> [*]
cancelled --> [*]
```
Note: `DISPUTED` on PurchaseRequest is triggered by dispute creation and reverts to the previous state if the dispute is rejected or resolved with no action. This is a transient overlay state -- the underlying state is preserved in the dispute record.
### 2.2.2 Payment + escrowState lifecycle
```mermaid
stateDiagram-v2
[*] --> PENDING : payment intent created
PENDING --> PROCESSING : funds detected on chain
PENDING --> CANCELLED : intent expired / buyer cancelled
PROCESSING --> COMPLETED : funds confirmed
PROCESSING --> FAILED : verification failed
COMPLETED --> FUNDED : escrowState set to FUNDED
FUNDED --> PARTIALLY_FUNDED : partial payment received (escrowState only)
PARTIALLY_FUNDED --> FUNDED : top-up reaches threshold
FUNDED --> RELEASABLE : delivery confirmed
FUNDED --> DISPUTED : dispute opened
RELEASABLE --> DISPUTED : dispute opened
DISPUTED --> FUNDED : dispute rejected / no action
DISPUTED --> RELEASABLE : dispute RESOLVED_SELLER (release authorized)
DISPUTED --> REFUNDING : dispute RESOLVED_BUYER (refund authorized)
RELEASABLE --> RELEASING : payout initiated
RELEASING --> RELEASED : payout confirmed on chain
RELEASING --> FAILED : payout failed on chain
REFUNDING --> REFUNDED : refund confirmed on chain
REFUNDING --> FAILED : refund failed on chain
FAILED --> RELEASING : admin retries payout
FAILED --> REFUNDING : admin retries refund
RELEASED --> [*]
REFUNDED --> [*]
CANCELLED --> [*]
```
### 2.2.3 Dispute lifecycle
```mermaid
stateDiagram-v2
[*] --> OPEN : buyer or seller opens dispute
OPEN --> UNDER_REVIEW : admin assigned
OPEN --> REJECTED : admin rejects (duplicate / spam / invalid)
OPEN --> CLOSED : auto-close (e.g. requester cancelled)
UNDER_REVIEW --> RESOLVED_BUYER : admin resolves in buyer favor
UNDER_REVIEW --> RESOLVED_SELLER : admin resolves in seller favor
UNDER_REVIEW --> RESOLVED_SPLIT : admin splits funds
UNDER_REVIEW --> REJECTED : admin rejects after review
RESOLVED_BUYER --> CLOSED : financial action completed
RESOLVED_SELLER --> CLOSED : financial action completed
RESOLVED_SPLIT --> CLOSED : financial actions completed
REJECTED --> CLOSED : admin closes
CLOSED --> [*]
```
### 2.2.4 Cross-entity interaction diagram
```mermaid
stateDiagram-v2
state "FundsAccount" as FA {
[*] --> ACTIVE : account created
ACTIVE --> SETTLED : released + refunded = grossPaid - fees
ACTIVE --> CANCELLED : no funds received
SETTLED --> [*]
CANCELLED --> [*]
}
state "LedgerEntry" as LE {
[*] --> PAY_IN : funds received
PAY_IN --> HOLD : funds allocated to held
HOLD --> DISPUTE_HOLD : dispute opened
DISPUTE_HOLD --> RELEASE : dispute resolved for seller
DISPUTE_HOLD --> REFUND : dispute resolved for buyer
HOLD --> RELEASE : delivery confirmed, payout
HOLD --> REFUND : pre-shipment cancellation
RELEASE --> [*]
REFUND --> [*]
}
state "Dispute" as DS {
[*] --> OPEN : created
OPEN --> UNDER_REVIEW : admin assigned
UNDER_REVIEW --> RESOLVED_BUYER : refund ordered
UNDER_REVIEW --> RESOLVED_SELLER : release ordered
UNDER_REVIEW --> RESOLVED_SPLIT : split ordered
RESOLVED_BUYER --> CLOSED : refund executed
RESOLVED_SELLER --> CLOSED : release executed
RESOLVED_SPLIT --> CLOSED : split executed
}
DS --> FA : OPEN triggers DISPUTE_HOLD on FundsAccount
DS --> FA : RESOLVED_BUYER triggers REFUND entry
DS --> FA : RESOLVED_SELLER triggers RELEASE entry
```
---
## 2.3 Valid Transitions Table
### 2.3.1 PurchaseRequest transitions
| From | To | Trigger | Preconditions | Side effects |
|------|----|---------|--------------|-------------|
| -- | `pending` | Buyer creates request | Buyer authenticated; category exists; title + description provided | Create PurchaseRequest; fan-out notification to sellers |
| `pending` | `received_offers` | First SellerOffer saved | Valid offer; seller is active | Add offer to `offers[]`; emit `purchase-request-update` |
| `pending` | `cancelled` | Buyer cancels | No accepted offer; no payment | Set `cancelledAt`; emit `request-cancelled` |
| `received_offers` | `in_negotiation` | Buyer engages seller | At least one pending offer | Emit `purchase-request-update` |
| `received_offers` | `payment` | Payment captured (SHKeeper PAID or Web3 verified) | escrowState = FUNDED; FundsAccount exists | Set `selectedOfferId`; create chat; emit cascade |
| `received_offers` | `cancelled` | Buyer cancels | No payment | Set `cancelledAt` |
| `in_negotiation` | `received_offers` | Counter rejected, more offers pending | No accepted offer | Emit `purchase-request-update` |
| `in_negotiation` | `payment` | Payment captured | escrowState = FUNDED; FundsAccount exists | Set `selectedOfferId`; create chat; emit cascade |
| `in_negotiation` | `cancelled` | Buyer cancels | No payment | Set `cancelledAt` |
| `payment` | `processing` | Seller acknowledges | Payment COMPLETED; escrowState FUNDED | Emit `purchase-request-update` |
| `payment` | `cancelled` | Dispute resolved as refund (pre-shipment) | Dispute status RESOLVED_BUYER | Set `cancelledAt`; escrowState -> REFUNDING |
| `processing` | `delivery` | Seller marks shipped | Seller is selected seller | Set `deliveryInfo.shippedAt`; emit update |
| `processing` | `DISPUTED` | Dispute opened | Buyer or seller opens dispute | Create Dispute; escrowState -> DISPUTED |
| `delivery` | `delivered` | Buyer confirms (code or fast-track) | Delivery code valid OR fast-track authorized | Set `deliveryConfirmedAt` |
| `delivery` | `DISPUTED` | Dispute opened | Buyer or seller opens dispute | Create Dispute; escrowState -> DISPUTED |
| `delivered` | `confirming` | Delivery code verified | Code matches; not already confirmed | Set `deliveryCodeUsedAt` |
| `delivered` | `DISPUTED` | Dispute opened | Buyer or seller opens dispute | Create Dispute; escrowState -> DISPUTED |
| `confirming` | `completed` | Escrow released | escrowState = RELEASED; FundsAccount settled or partially settled | Set `completedAt`; emit notification |
| `confirming` | `DISPUTED` | Dispute opened | Buyer or seller opens dispute | Create Dispute; escrowState -> DISPUTED |
| `DISPUTED` | `confirming` | Dispute RESOLVED_SELLER or REJECTED | Dispute status terminal | escrowState -> RELEASABLE or previous state |
| `DISPUTED` | `cancelled` | Dispute RESOLVED_BUYER | Dispute status RESOLVED_BUYER | escrowState -> REFUNDING; set `cancelledAt` |
| `completed` | `seller_paid` | Payout confirmed on chain | escrowState = RELEASED; on-chain tx verified | Set `seller_paid`; FundsAccount settled |
### FORBIDDEN PurchaseRequest transitions
| From | To | Reason |
|------|----|--------|
| `payment` | `pending` | Cannot regress after payment captured |
| `processing` | `received_offers` | Cannot regress after seller acknowledges |
| `completed` | `cancelled` | Cannot cancel a completed transaction |
| `seller_paid` | any non-terminal | `seller_paid` is terminal |
| `cancelled` | any | `cancelled` is terminal |
| `payment` | `payment` | No self-transitions |
| any non-DISPUTED | `DISPUTED` without dispute | DISPUTED only via dispute creation |
| `DISPUTED` | `completed` | Must go through confirming first (release path) |
| `DISPUTED` | `cancelled` without dispute | Only dispute resolution can trigger cancellation from DISPUTED |
### 2.3.2 Payment + escrowState transitions
| From escrowState | To escrowState | Trigger | Preconditions | Side effects |
|-----------------|---------------|---------|--------------|-------------|
| null (PENDING) | `PARTIALLY_FUNDED` | SHKeeper PARTIAL webhook | `Payment.status` = PENDING | LedgerEntry PAY_IN (partial) |
| null (PENDING) | `FUNDED` | SHKeeper PAID/OVERPAID webhook or Web3 verification | `Payment.status` = PENDING or PARTIALLY_FUNDED | LedgerEntry PAY_IN; LedgerEntry HOLD; FundsAccount ACTIVE |
| `PARTIALLY_FUNDED` | `FUNDED` | Top-up reaches threshold | cumulative PAY_IN >= expected amount | LedgerEntry PAY_IN (top-up); LedgerEntry HOLD (full) |
| `FUNDED` | `RELEASABLE` | Buyer confirms delivery OR auto-release timer | No active dispute; FundsAccount releasable >= 0 | LedgerEntry (no entry -- held becomes releasable via derived balance) |
| `FUNDED` | `DISPUTED` | Dispute opened | Dispute created for this payment | LedgerEntry DISPUTE_HOLD; escrowState set to DISPUTED (addresses [[T06]]) |
| `RELEASABLE` | `DISPUTED` | Dispute opened | Dispute created | LedgerEntry DISPUTE_HOLD; block all payouts |
| `DISPUTED` | `FUNDED` | Dispute REJECTED or no action | Dispute status terminal | LedgerEntry REVERSAL (reverse DISPUTE_HOLD) |
| `DISPUTED` | `RELEASABLE` | Dispute RESOLVED_SELLER | Dispute status RESOLVED_SELLER; seller wallet present | LedgerEntry REVERSAL (reverse DISPUTE_HOLD); funds become releasable |
| `DISPUTED` | `REFUNDING` | Dispute RESOLVED_BUYER or RESOLVED_SPLIT | Dispute status terminal; releasable >= refund amount | LedgerEntry REVERSAL; LedgerEntry REFUND |
| `RELEASABLE` | `RELEASING` | Admin/system initiates payout | No active dispute; releasable >= release amount; seller wallet present | Create outgoing Payment; LedgerEntry RELEASE |
| `RELEASING` | `RELEASED` | Payout confirmed on chain | On-chain tx hash verified; `Payment.status` -> RELEASED | FundsAccount released += amount; emit notification |
| `RELEASING` | `FAILED` | Payout failed on chain | On-chain tx reverted | LedgerEntry REVERSAL; admin alerted |
| `REFUNDING` | `REFUNDED` | Refund confirmed on chain | On-chain tx hash verified; `Payment.status` -> REFUNDED | FundsAccount refunded += amount; emit notification |
| `REFUNDING` | `FAILED` | Refund failed on chain | On-chain tx reverted | LedgerEntry REVERSAL; admin alerted |
| `FAILED` | `RELEASING` | Admin retries payout | Admin step-up auth; seller wallet present | New LedgerEntry RELEASE |
| `FAILED` | `REFUNDING` | Admin retries refund | Admin step-up auth; buyer wallet present | New LedgerEntry REFUND |
### FORBIDDEN escrowState transitions
| From | To | Reason | Threat addressed |
|------|----|--------|-----------------|
| `FUNDED` | `RELEASING` | Must pass through RELEASABLE first | [[T05]] |
| `DISPUTED` | `RELEASING` | Cannot release during active dispute | [[T06]] |
| `DISPUTED` | `RELEASED` | Must resolve dispute first | [[T06]] |
| `RELEASED` | `FUNDED` | Cannot undo a confirmed release | [[T05]] |
| `REFUNDED` | `FUNDED` | Cannot undo a confirmed refund | [[T05]] |
| `RELEASED` | `REFUNDED` | Cannot refund after release | [[T05]] |
| `REFUNDED` | `RELEASED` | Cannot release after refund | [[T05]] |
| `FUNDED` | `FUNDED` | No self-transitions | [[T23]] |
| `RELEASED` | `RELEASED` | No self-transitions (prevents double release) | [[T05]] |
| any | `FUNDED` (after `RELEASED`/`REFUNDED`) | Terminal states are terminal | [[T05]], [[T23]] |
| `DISPUTED` | `RELEASABLE` (without dispute resolution) | Only dispute resolution clears disputed state | [[T06]] |
### 2.3.3 Dispute transitions
| From | To | Trigger | Preconditions | Side effects |
|------|----|---------|--------------|-------------|
| -- | `OPEN` | Buyer or seller creates dispute | PurchaseRequest exists; initiator is buyer or seller; no other OPEN/UNDER_REVIEW dispute for same request | Create Dispute; create group chat; escrowState -> DISPUTED (if FUNDED/RELEASABLE); set `responseDeadline = now + 48h`; set `deadline = now + 7d` |
| `OPEN` | `UNDER_REVIEW` | Admin assigned | Admin authenticated with admin role | Set `adminId`; add admin to chat participants; emit notification |
| `OPEN` | `REJECTED` | Admin rejects | Admin authenticated; dispute is duplicate, spam, or invalid | Set `rejectedAt`; escrowState -> previous state (reverse DISPUTE_HOLD) |
| `OPEN` | `CLOSED` | Auto-close (requester cancelled) | Requester cancels the dispute | Set `closedAt` |
| `UNDER_REVIEW` | `RESOLVED_BUYER` | Admin resolves in buyer favor | Admin authenticated; admin is assigned admin; resolution action provided | Set `resolution`; escrowState -> REFUNDING; LedgerEntry REFUND |
| `UNDER_REVIEW` | `RESOLVED_SELLER` | Admin resolves in seller favor | Admin authenticated; admin is assigned admin; resolution action provided | Set `resolution`; escrowState -> RELEASABLE |
| `UNDER_REVIEW` | `RESOLVED_SPLIT` | Admin splits funds | Admin authenticated; refundAmount + releaseAmount <= releasable | Set `resolution` with amounts; escrowState -> RELEASING + REFUNDING |
| `UNDER_REVIEW` | `REJECTED` | Admin rejects after review | Admin authenticated | Set `rejectedAt`; escrowState -> previous state |
| `RESOLVED_BUYER` | `CLOSED` | Refund executed | escrowState = REFUNDED; on-chain tx confirmed | Set `closedAt` |
| `RESOLVED_SELLER` | `CLOSED` | Release executed | escrowState = RELEASED; on-chain tx confirmed | Set `closedAt` |
| `RESOLVED_SPLIT` | `CLOSED` | Both release and refund executed | escrowState = RELEASED + REFUNDED | Set `closedAt` |
| `REJECTED` | `CLOSED` | Admin closes | Admin authenticated | Set `closedAt` |
### FORBIDDEN Dispute transitions
| From | To | Reason |
|------|----|--------|
| `CLOSED` | any | `CLOSED` is terminal |
| `RESOLVED_BUYER` | `UNDER_REVIEW` | Cannot reopen after resolution |
| `RESOLVED_SELLER` | `UNDER_REVIEW` | Cannot reopen after resolution |
| `RESOLVED_SPLIT` | `UNDER_REVIEW` | Cannot reopen after resolution |
| `REJECTED` | `UNDER_REVIEW` | Cannot reopen after rejection |
| `OPEN` | `RESOLVED_BUYER` | Must be assigned to admin first |
| `OPEN` | `RESOLVED_SELLER` | Must be assigned to admin first |
| `OPEN` | `RESOLVED_SPLIT` | Must be assigned to admin first |
| `UNDER_REVIEW` | `OPEN` | Cannot regress |
---
## 2.4 Dispute Hold Enforcement
Dispute holds are the primary defense against [[T06]] (release during active dispute) and support [[T18]] (insider manipulation detection).
### Hold creation
1. When a dispute is created (`Dispute.status = OPEN`), the system MUST:
a. Create a `LedgerEntry` of type `DISPUTE_HOLD` on the associated FundsAccount.
b. Set `Payment.escrowState = DISPUTED` on all funded payments associated with the PurchaseRequest.
c. Log the transition in the Dispute timeline.
d. This MUST happen atomically within a single MongoDB transaction (or with compensating actions if transactions are unavailable).
2. If the FundsAccount has no funds (e.g., dispute on an unfunded request), the dispute is still created but no `DISPUTE_HOLD` entry is made. The dispute is informational only.
3. If multiple payments exist for the same PurchaseRequest (should not happen with canonical model, but defensively): all funded payments MUST be placed in `DISPUTED` state.
### Hold checking
Every release and refund operation MUST check for active dispute holds BEFORE executing:
```
function checkDisputeHold(accountId):
activeDisputes = Dispute.find({
purchaseRequestId: account.purchaseRequestId,
status: { $in: [OPEN, UNDER_REVIEW] }
})
if (activeDisputes.length > 0):
throw Error("Cannot release/refund: active dispute hold exists")
```
This check MUST occur:
1. In the FundsService (not just in the controller) -- service-layer enforcement.
2. After acquiring any distributed lock (to prevent TOCTOU races).
3. On both the `Payment.escrowState` field AND the FundsAccount ledger (defense in depth).
### Hold release
A dispute hold is cleared only when:
1. **Dispute resolved**: `Dispute.status` transitions to `RESOLVED_BUYER`, `RESOLVED_SELLER`, `RESOLVED_SPLIT`, `REJECTED`, or `CLOSED`.
2. The resolution handler creates the appropriate `LedgerEntry` (RELEASE, REFUND, or REVERSAL).
3. The `Payment.escrowState` transitions from `DISPUTED` to the next state based on resolution.
### Admin override
An admin MAY override a dispute hold under the following conditions:
1. **Step-up authentication required**: The admin must re-authenticate (password + 2FA) before overriding.
2. **Audit trail**: An `ADJUSTMENT` ledger entry is created with:
- `actor.type = ADMIN`
- `actor.userId = adminId`
- `sourceEvent.adminActionId` set to a unique action ID
- `description` containing the reason for override
3. **Two-person approval**: For accounts holding more than a configurable threshold (default: 1000 USDT), two separate admin approvals are required.
4. **Alert**: An alert is sent to the operations team for every admin override.
5. **Rate limit**: An admin can override at most 3 disputes per hour. Exceeding this triggers a security alert.
---
## 2.5 Release and Refund Preconditions
### 2.5.1 Release to seller
**Preconditions (ALL must be true):**
| Condition | Check |
|-----------|-------|
| Escrow state | `escrowState` is `RELEASABLE` or (`DISPUTED` with dispute `RESOLVED_SELLER`) |
| No active disputes | No Dispute with status `OPEN` or `UNDER_REVIEW` for this PurchaseRequest |
| Sufficient releasable balance | `FundsAccount.releasable >= releaseAmount` (derived from ledger) |
| Balance invariant | `grossPaid = providerFees + platformFees + released + refunded + releasable + held + disputed` |
| Seller wallet present | `seller.profile.walletAddress` is set and valid (`^0x[0-9a-fA-F]{40}$`) |
| No concurrent release | Distributed lock acquired for this FundsAccount |
| Payment not already released | No existing `LedgerEntry` of type `RELEASE` with the same idempotency key |
| Offer price not modified | `SellerOffer.price.amount` matches the amount at payment creation time (addresses [[T19]]) |
**Execution steps:**
1. Acquire distributed lock (Redis) for `accountId`.
2. Re-check all preconditions inside the lock.
3. Create `LedgerEntry` of type `RELEASE` with idempotency key.
4. Set `Payment.escrowState = RELEASING`.
5. Initiate on-chain transfer (SHKeeper payout or manual admin signing).
6. On on-chain confirmation: set `Payment.escrowState = RELEASED`.
7. Update `FundsAccount.releasedAmount` (derived from ledger).
8. Release distributed lock.
9. If `released + refunded + fees = grossPaid`, set `FundsAccount.status = SETTLED`.
### 2.5.2 Refund to buyer
**Preconditions (ALL must be true):**
| Condition | Check |
|-----------|-------|
| Escrow state | `escrowState` is `FUNDED`, `RELEASABLE`, `DISPUTED`, or `PARTIALLY_FUNDED` |
| Dispute resolved (if disputed) | If `escrowState = DISPUTED`, Dispute status must be `RESOLVED_BUYER` or `RESOLVED_SPLIT` |
| OR pre-shipment cancellation | PurchaseRequest status is `payment` or earlier (no delivery started) |
| OR admin override | Admin with step-up auth has authorized the refund |
| Sufficient releasable balance | `FundsAccount.releasable >= refundAmount` (derived from ledger) |
| Balance invariant | Invariant holds before refund |
| Buyer wallet present | `Payment.blockchain.sender` (original buyer wallet) is available |
| No concurrent refund | Distributed lock acquired for this FundsAccount |
**Execution steps:**
1. Acquire distributed lock for `accountId`.
2. Re-check all preconditions inside the lock.
3. Create `LedgerEntry` of type `REFUND` with idempotency key.
4. Set `Payment.escrowState = REFUNDING`.
5. Build refund transaction payload (destination = buyer's original wallet).
6. Admin signs and broadcasts (or automated if SHKeeper payout).
7. On on-chain confirmation: set `Payment.escrowState = REFUNDED`.
8. Release distributed lock.
9. If `released + refunded + fees = grossPaid`, set `FundsAccount.status = SETTLED`.
### 2.5.3 Partial release and refund
Partial operations are allowed only in the following cases:
1. **Split dispute resolution** (`RESOLVED_SPLIT`): The admin specifies `refundAmount` and `releaseAmount` where `refundAmount + releaseAmount <= releasable`.
2. **Partial refund of overpayment**: If the buyer overpaid, the surplus can be refunded separately. `refundAmount = grossAmountPaid - expectedAmount`.
3. **Admin override with justification**: Admin may specify a custom amount with step-up auth and audit trail.
**Amount tracking for partial operations:**
- Each partial operation creates a separate `LedgerEntry` with its specific amount.
- The FundsAccount tracks cumulative `releasedAmount` and `refundedAmount` (derived from ledger sums).
- Multiple partial operations MAY occur for a single FundsAccount, but the invariant `released + refunded + fees <= grossPaid` must always hold.
- A FundsAccount reaches `SETTLED` when `released + refunded + providerFees + platformFees = grossPaid` and `held = 0` and `disputed = 0`.
### 2.5.4 Admin override additional checks
For any admin-initiated release or refund that deviates from the standard flow:
| Check | Requirement |
|-------|-------------|
| Step-up authentication | Admin must re-authenticate (password + 2FA) |
| Reason required | A non-empty `reason` string must be provided |
| Audit entry | `LedgerEntry` of type `ADJUSTMENT` with full context |
| Two-person approval (high value) | If amount > threshold (default 1000 USDT), second admin must approve |
| Alert | Operations team notified immediately |
| Rate limit | Max 5 admin overrides per hour per admin |
---
## 2.6 Ghost States and Cleanup
The following "ghost states" were identified in [[Platform Logical Audit - 2026-05-24]] as status values that exist in enums but are not set by any automated flow.
### 2.6.1 Assessment
| Ghost State | Entity | Assessment | Resolution |
|-------------|--------|------------|------------|
| `confirmed` (Payment.status) | Payment | No automated flow sets this. SHKeeper maps PAID directly to `completed`. The data model includes it as an intermediate between `processing` and `completed`, but the webhook skips it. | **Remove from canonical enum.** Any records currently in `confirmed` should be migrated to `COMPLETED`. |
| `partial` (Payment.escrowState) | Payment | Used by Escrow Flow and Payment Flow - SHKeeper but NOT present in the Payment data model enum. This is a documentation/implementation gap. | **Replace with `PARTIALLY_FUNDED` in canonical enum.** Add to the Payment model's `escrowState` enum. Migrate any records in `partial` to `PARTIALLY_FUNDED`. |
| `draft` (PurchaseRequest.status) | PurchaseRequest | Referenced only in the Marketplace API status transition description. No code creates requests in `draft` state; requests are always `pending`. | **Remove from canonical enum.** No migration needed (no records should exist in `draft`). |
| `finalized` (PurchaseRequest.status) | PurchaseRequest | Referenced in Purchase Request Flow as post-ratings state. No automated flow sets it. | **Remove from canonical enum.** Use `completed` as the terminal state. Track ratings via `PurchaseRequest.rating` and `Review` model. Map any `finalized` records to `completed`. |
| `archived` (PurchaseRequest.status) | PurchaseRequest | Referenced in Purchase Request Flow as 30-day idle state. No automated job sets it. | **Remove from canonical enum.** Use an `archivedAt` timestamp or `isArchived` boolean instead. Map any `archived` records to `completed` with `archivedAt` set. |
| `active` (PurchaseRequest.status) | PurchaseRequest | Listed in Data Model enum but semantically ambiguous (could mean pre-payment or post-payment). | **Remove from canonical enum.** Pre-payment is `pending`/`received_offers`/`in_negotiation`. Post-payment is `payment`. Map any `active` records to the appropriate state based on payment status. |
| `pending_payment` (PurchaseRequest.status) | PurchaseRequest | Listed in Data Model enum but no flow creates this state. Payment creates a Payment record, not a PurchaseRequest status change to `pending_payment`. | **Remove from canonical enum.** Map any `pending_payment` records to `pending`. |
| `replacement` (Dispute.resolution.action) | Dispute | Listed in Data Model enum. Not a financial action; cannot be represented in the ledger. | **Remove from canonical resolution action enum.** Track as a non-financial timeline event in the dispute. |
### 2.6.2 Migration strategy
**Phase 1: Data audit (before enforcement)**
Run a data audit script to identify all records currently in ghost states:
```javascript
// Identify ghost state records
db.payments.find({ status: "confirmed" })
db.payments.find({ escrowState: "partial" })
db.purchaserequests.find({ status: { $in: ["draft", "finalized", "archived", "active", "pending_payment"] } })
db.disputes.find({ "resolution.action": "replacement" })
```
**Phase 2: Migration (with ledger)**
For each ghost state, migrate records to the canonical state:
| Ghost State | Migration Target | Migration Action |
|-------------|-----------------|-----------------|
| `Payment.status = "confirmed"` | `"COMPLETED"` | Update status; verify escrowState is FUNDED |
| `Payment.escrowState = "partial"` | `"PARTIALLY_FUNDED"` | Update escrowState; verify partial amount in metadata |
| `PurchaseRequest.status = "draft"` | `"pending"` | Update status |
| `PurchaseRequest.status = "finalized"` | `"completed"` | Update status; verify escrow is RELEASED |
| `PurchaseRequest.status = "archived"` | `"completed"` | Update status; set `archivedAt = updatedAt` |
| `PurchaseRequest.status = "active"` | Determined by payment status | If payment exists: `"payment"`. If not: `"pending"` or `"received_offers"` based on offers |
| `PurchaseRequest.status = "pending_payment"` | `"pending"` | Update status |
| `Dispute.resolution.action = "replacement"` | `"NO_ACTION"` | Update action; add timeline note that replacement was agreed |
**Phase 3: Enum enforcement**
After migration:
1. Update all model schemas to use only the canonical enum values.
2. Add Mongoose enum validation that rejects any value not in the canonical enum.
3. Add a `pre('save')` hook that validates state transitions against the valid transitions tables.
4. Run the data audit script again to verify zero ghost state records.
**Phase 4: Documentation update**
After enforcement, update all referencing documents:
- [[Payment]] data model: update status and escrowState enums
- [[PurchaseRequest]] data model: update status enum
- [[Dispute]] data model: update status and resolution.action enums
- [[Payment API]]: update status model section
- [[Dispute API]]: update status and decision enums
- [[Marketplace API]]: update status transition descriptions
- [[Escrow Flow]]: update state machine diagram
- [[Dispute Flow]]: update state machine diagram
- [[Purchase Request Flow]]: update state machine diagram
---
## Appendix A: Threat Traceability
| Threat | Addressed by |
|--------|-------------|
| [[T05]] Double payout / double release | Immutable ledger with idempotency keys; distributed locks; releasable balance derived from ledger (not stored); FundsAccount status enforcement |
| [[T06]] Dispute bypass (release during active dispute) | `DISPUTED` escrow state; mandatory dispute hold check before every release/refund; service-layer enforcement |
| [[T18]] Insider fund manipulation | Immutable append-only ledger; admin override requires step-up auth; two-person approval for high-value operations; audit trail on every entry; reconciliation detects discrepancies |
| [[T19]] Seller price manipulation post-acceptance | Release precondition checks offer price matches payment-time snapshot; offer status `accepted` is immutable after payment |
| [[T23]] State machine inconsistency | Single canonical enum set; valid transition tables enforced at service layer; ghost states removed; pre-save validation hooks |
## Appendix B: Glossary
| Term | Definition |
|------|-----------|
| **FundsAccount** | The financial ledger account for a single purchase request, tracking all money movement |
| **LedgerEntry** | An immutable, append-only record of a single financial event |
| **escrowState** | The state of funds held by the platform, independent of payment processing status |
| **Dispute hold** | A mechanism that blocks all fund movement while a dispute is active |
| **Releasable balance** | Funds available for release to seller or refund to buyer (derived from ledger) |
| **Running balance** | A snapshot of all balance fields appended to each ledger entry for query efficiency |
| **Ghost state** | A status value that exists in an enum but is never set by any automated flow |
| **Step-up authentication** | Re-authentication required for high-risk admin operations (release, refund, override) |
| **Distributed lock** | A Redis-based mutex preventing concurrent operations on the same FundsAccount |
## Appendix C: Implementation Order
1. Add `FundsAccount` and `LedgerEntry` models to the database.
2. Build `FundsService` with idempotency enforcement and balance derivation.
3. Implement `EscrowStateMachine` service with valid transition validation.
4. Add `DISPUTED` state to Payment model and dispute hold logic to DisputeService.
5. Integrate FundsService into existing payment webhooks (SHKeeper and Web3).
6. Integrate FundsService into release and refund flows.
7. Add distributed locking for FundsAccount operations.
8. Build reconciliation job.
9. Migrate ghost state records.
10. Update all referencing documentation.

View File

@@ -0,0 +1,343 @@
---
title: Logic & Correctness Audit — 2026-05-24
tags: [audit, logic, correctness, findings]
created: 2026-05-24
status: open
---
# Logic & Correctness Audit — 2026-05-24
Full-codebase review of business logic, state machines, race conditions, and data integrity. Triggered by completion of Request Network integration, Telegram first-class auth, and the internal funds ledger. Every finding is derived from actual source code — no hypothetical issues are included.
> [!danger] Action required
> 4 CRITICAL findings: two are exploitable in the current production code path (double-spend via concurrent webhooks, simulated-payment bypass); two cause data corruption (orphan User documents, non-atomic create/delete).
---
## CRITICAL
### CRIT-1 — Concurrent Webhooks Can Double-Process the Same Payment
**File:** `src/services/payment/shkeeper/shkeeperWebhook.ts:195241` and `src/services/payment/paymentCoordinator.ts:2560`
**Status:** Open
The duplicate-detection guard uses an in-memory 10-second window. There is no database-level atomic lock. Two simultaneous webhook deliveries for the same `external_id` will both read `payment` before either writes, both pass the in-memory deduplication check, both enter `PaymentCoordinator.coordinatePaymentUpdate`, and both proceed — because `coordinationState` is an in-memory `Map` whose `get/set` pair is not atomic under async concurrency (any `await` between them allows the event loop to interleave).
The `status === 'completed'` DB guard at `paymentCoordinator.ts:54` only protects re-processing of already-completed payments. The first webhook has not yet written `completed` when the second arrives, so both pass.
Side effects of double-processing:
- Duplicate chats created between buyer and seller (`chatService.createChat` has no uniqueness guard).
- Duplicate socket events sent to the buyer.
- Depending on timing, duplicate state transitions on `PurchaseRequest`.
**Remediation:** Replace the coordinator's check with an atomic MongoDB conditional update:
```ts
const result = await Payment.findOneAndUpdate(
{ _id: paymentId, status: { $ne: 'completed' } },
{ $set: { status: 'completed', ... } },
{ new: true }
);
if (!result) return; // already processed
```
For chat creation, add a unique compound index on `{ 'relatedTo.type': 1, 'relatedTo.id': 1 }` and handle the duplicate key error gracefully.
---
### CRIT-2 — Telegram Auto-Provisioning Race Creates Orphan User Documents
**File:** `src/services/auth/authController.ts:377434`
**Status:** Open
The flow is:
1. `TelegramLink.findOne({ telegramUserId })` → null (new user)
2. `new User({...}).save()` — creates User document
3. `TelegramLink.findOneAndUpdate({ telegramUserId }, ..., { upsert: true })`
Two parallel Telegram auth requests for the same `telegramUserId` both find no link, both create a new `User`, and both try to upsert `TelegramLink`. Because `TelegramLink.telegramUserId` has `unique: true`, only one upsert wins. The loser's `User.save()` has already committed, producing a fully-active `User` document with no `TelegramLink` — it can never be authenticated via Telegram and will not be cleaned up.
**Remediation:** Use optimistic locking or a MongoDB transaction. Practical approach without transactions:
```ts
// Step 1: try to upsert the link first
const link = await TelegramLink.findOneAndUpdate(
{ telegramUserId },
{ $setOnInsert: { telegramUserId, source, ... } },
{ upsert: true, new: true, rawResult: true }
);
const isNew = link.lastErrorObject?.upserted != null;
// Step 2: create User only if this process won the upsert
if (isNew) { user = await User.create({...}); await link.value.updateOne({ userId: user._id }); }
else { user = await User.findById(link.value.userId); }
```
---
### CRIT-3 — Simulated Transaction Bypass Active in Production
**File:** `src/services/payment/paymentRoutes.ts:379396`
**Status:** Open
*(Also cited in Security Audit C-3.)*
```ts
if (paymentHash.startsWith('SIM_') || ...) {
isVerified = true;
}
```
No environment guard. A client sending `paymentHash: "SIM_anything"` gets a completed `Payment` and the `PurchaseRequest` advances without any real funds.
**Remediation:** Wrap in `if (process.env.NODE_ENV !== 'production')`.
---
### CRIT-4 — Hardcoded Credential in Production Payment Code
**File:** `src/services/payment/shkeeper/shkeeperPayoutService.ts:224228`
**Status:** Open
*(Also cited in Security Audit C-5.)*
```ts
'Authorization': `Basic ${Buffer.from(`admin:!NMI4WdGkVQ#dQ`).toString('base64')}`
```
**Remediation:** `process.env.SHKEEPER_ADMIN_PASSWORD` — rotate immediately.
---
## HIGH
### HIGH-1 — `updatePurchaseRequest` Has a Read-Modify-Write Race Condition
**File:** `src/services/marketplace/PurchaseRequestService.ts:405425`
**Status:** Open
```ts
const currentRequest = await PurchaseRequest.findById(requestId); // read
// ... validate status ...
await PurchaseRequest.findByIdAndUpdate(requestId, updateData, { new: true }); // write
```
Two concurrent calls both read the same `currentRequest.status`, both pass `isValidStatusProgression`, and both write — potentially into conflicting terminal states or setting the same field twice.
Furthermore, `isValidStatusProgression` allows any status → any terminal state transition (e.g., `pending``completed` in one step), because the guard only blocks backward progression among non-terminal states.
**Remediation:** Use atomic optimistic locking:
```ts
const updated = await PurchaseRequest.findOneAndUpdate(
{ _id: requestId, status: currentStatus }, // guard: status unchanged
{ $set: { status: newStatus } },
{ new: true }
);
if (!updated) throw new ConflictError('State changed concurrently — retry');
```
Additionally, tighten `isValidStatusProgression` to use an explicit allowed-transitions table rather than a blanket terminal-from-anywhere rule.
---
### HIGH-2 — Seller Offer Acceptance Has No Concurrency Guard
**File:** `src/services/marketplace/SellerOfferService.ts:355426`
**Status:** Open
`acceptOffer` reads the offer, then updates it in a separate write. Two simultaneous accept calls for different offers on the same purchase request will both succeed: both set their target offer to `accepted`, and both run `updateMany` to reject all others — each query excluding its own `offerId`. The result is two `accepted` offers on the same purchase request.
**Remediation:** Use `findOneAndUpdate` with a status guard:
```ts
const updated = await SellerOffer.findOneAndUpdate(
{ _id: offerId, status: 'pending' },
{ $set: { status: 'accepted' } },
{ new: true }
);
if (!updated) throw new ConflictError('Offer already processed');
await SellerOffer.updateMany(
{ purchaseRequestId, _id: { $ne: offerId } },
{ $set: { status: 'rejected' } }
);
```
Consider wrapping in a MongoDB transaction to keep the accept + reject-others atomic.
---
### HIGH-3 — `refreshTokens[]` Array Grows Unboundedly
**File:** `src/services/auth/authController.ts:6263` and login handler
**Status:** Open
Every `login` pushes a new refresh token onto `user.refreshTokens[]` without any cap or expiry pruning. The `User` model has no `maxlength` on this array. A user logging in from many devices, or an attacker rapidly requesting tokens, causes the array to grow without limit — increasing document size and slowing any query that reads the user document.
The `refreshToken` rotation endpoint prunes the old token on rotation, but `login` does not prune expired tokens before pushing.
**Remediation:** Before pushing, prune expired tokens:
```ts
const now = Date.now() / 1000;
user.refreshTokens = user.refreshTokens.filter(t => {
try { return jwt.decode(t)?.exp > now; } catch { return false; }
});
```
Enforce a hard cap (e.g. `slice(-10)`) after pruning.
---
### HIGH-4 — Blocked Telegram User Can Bypass Block via Deleted TelegramLink
**File:** `src/services/auth/authController.ts:355363`
**Status:** Open
The block check queries `TelegramLink` where `status = 'blocked'` AND `blockedReason != 'unlinked_by_user'`. If the blocked user's `TelegramLink` is **deleted** entirely (not just status-changed), the check finds nothing, `activeLink` finds nothing, and the code auto-provisions a brand-new `User` with a fresh `TelegramLink` — bypassing the block entirely.
**Remediation:** Store the blocked Telegram user ID on the `User` document (a `blockedTelegramIds` set at the app level, or a `status: 'suspended'` flag tied to the Telegram ID) so the block persists even without a `TelegramLink` record.
---
### HIGH-5 — `verifyEmailWithCode`: Non-Atomic User Create + TempVerification Delete
**File:** `src/services/auth/authController.ts:620633`
**Status:** Open
```ts
await user.save(); // line 630
await TempVerification.findByIdAndDelete(tempVerification._id); // line 633
```
If `user.save()` succeeds but the delete fails (network error, timeout), the `TempVerification` document remains. The next call with the same `email+code` will find it valid, attempt `User.create` again, and hit the unique email index — returning a 500 error instead of a clean "already verified" message. This persists until the MongoDB TTL scan runs (up to 15 min + scan interval).
**Remediation:** Delete the TempVerification **before** saving the user, or check for duplicate-key errors after `user.save()` and delete the TempVerification in the catch handler.
---
## MEDIUM
### MED-1 — Funds Ledger Availability Check Is Not Atomic with Append
**File:** `src/services/payment/orchestration/releaseRefundService.ts:3755,99114`
**Status:** Open
`validateReleaseAvailability` reads a balance via aggregation, checks `availableBalance >= amount`, then `appendFundsLedgerEntry` inserts the entry. Two concurrent release/refund calls can both read the same balance snapshot, both pass the availability check, and both insert entries — resulting in a double-spend from the ledger.
The `idempotencyKey` on `FundsLedgerEntry` prevents exact duplicates when the same `txHash` is used, but does not prevent two different `txHash` values from both passing the availability check concurrently.
**Remediation:** Wrap the check + insert in a MongoDB transaction, or add a single-release constraint: before inserting a `release` entry, verify no prior `release` entry exists for the same `paymentId`.
---
### MED-2 — `updatePurchaseRequestStatus` Bypasses the State Machine Validator
**File:** `src/services/marketplace/PurchaseRequestService.ts:551589`
**Status:** Open
`updatePurchaseRequestStatus` calls `PurchaseRequest.findByIdAndUpdate` directly, without calling `isValidStatusProgression`. The webhook path (`paymentCoordinator.ts:208`) uses this method, meaning payment-triggered state transitions skip the state machine guard entirely.
**Remediation:** Add `isValidStatusProgression(currentStatus, newStatus)` check inside `updatePurchaseRequestStatus` before writing.
---
### MED-3 — `getUserPayments` Queries Non-Existent Field `userId`
**File:** `src/services/payment/paymentService.ts:342346`
**Status:** Open
```ts
query.userId = userId;
```
The `Payment` schema has no top-level `userId` field. The buyer is stored as `buyerId` and the seller as `sellerId`. This query will always return zero results for current payments; only legacy payments with `metadata.legacyUserId` are returned (none for new users). The endpoint appears non-functional for all users.
**Remediation:**
```ts
query.$or = [{ buyerId: userId }, { sellerId: userId }];
```
---
### MED-4 — Payout Created Without Verifying an Inbound Completed Payment Exists
**File:** `src/services/payment/shkeeper/shkeeperPayoutService.ts:4280`
**Status:** Open
`createPayoutTask` takes `buyerId`, `sellerId`, `purchaseRequestId`, and `amount` from the request body. The service checks for duplicate payouts (`existingPayout` idempotency) but does not verify that a corresponding inbound `Payment` with `status: 'completed'` and `direction: 'in'` exists. An admin could accidentally initiate a payout for a purchase request that has never been paid.
**Remediation:**
```ts
const inboundPayment = await Payment.findOne({
purchaseRequestId, direction: 'in', status: 'completed'
});
if (!inboundPayment) throw new AppError(400, 'No completed inbound payment for this request');
```
---
### MED-5 — Unauthenticated `/payment/callback` Endpoint Can Mutate Payment Status
**File:** `src/services/payment/paymentControllerRoutes.ts:20`
**Status:** Open
`POST /callback` is mounted with no authentication middleware. The handler accepts `{ transactionId, status, amount }` and updates a payment's status to any value without verification. Unlike the SHKeeper webhook (which has optional HMAC), this endpoint has zero authentication.
**Remediation:** Either remove the endpoint (the SHKeeper webhook path supersedes it), restrict to internal network only (Nginx `allow` rules), or add HMAC verification.
---
### MED-6 — In-Memory Payment Coordinator State Not Shared Across Replicas
**File:** `src/services/payment/paymentCoordinator.ts:2425`
**Status:** Open
The `coordinationState` Map is process-local. In a multi-process deployment (PM2 cluster, k8s with >1 replica), the 10-second debounce window provides no protection — each process has its own empty map at startup, and webhooks routed to different replicas will both process. The existing DB `status === 'completed'` guard is the only durable protection.
*(The code comment at line 24 acknowledges this. Fix is the same as CRIT-1 — atomic DB conditional update makes this moot.)*
---
### MED-7 — Seller Offer Notification Uses Undefined `offer.title` Field
**File:** `src/services/marketplace/SellerOfferService.ts:403,415`
**Status:** Open
`SellerOffer` does not have a `title` field directly — the title belongs to the associated `PurchaseRequest`. `offer.title` resolves to `undefined` in the notification message body, resulting in broken notification text for buyers and sellers.
**Remediation:** Either populate the `purchaseRequestId` and use `purchaseRequest.title`, or populate the field name from the offer's own descriptive field.
---
## LOW
### LOW-1 — Duplicate Callback Route Definitions
**File:** `src/services/payment/paymentRoutes.ts:29,256`
**Status:** Open
`POST /callback` is defined at line 29 (mapped to `handlePaymentCallback`) and again as `POST /payments/callback` at line 256. These resolve to different URL paths given the router mount, but suggest dead code. No security impact; creates maintenance confusion.
**Remediation:** Remove the dead route definition.
---
### LOW-2 — `paymentCoordinator.ts` Map Grows Without Bound Until Cleanup Fires
**File:** `src/services/payment/paymentCoordinator.ts:35`
**Status:** Open
The cleanup function fires after each coordination cycle but only on active payments. Under a webhook flood before cleanup, the Map accumulates entries. Not a crash risk at current scale; becomes a concern at high webhook volume.
**Remediation:** The atomic DB fix in CRIT-1 eliminates the need for the Map entirely. Until then, add a size cap and a periodic cleanup interval independent of payment processing.
---
## Confirmed PASS
| Check | Result | Source |
|-------|--------|--------|
| TempVerification TTL index | PASS | `TempVerification.ts:59``expireAfterSeconds: 0` on expiry field |
| Legacy email token cleared on first use | PASS | `authController.ts:667` — token set to undefined before save |
| SHKeeper HMAC timing-safe comparison | PASS | `shkeeperWebhook.ts:84``crypto.timingSafeEqual` |
| Socket event DB as source of truth | PASS | DB write committed before emit; emit failure is non-fatal |
| `FundsLedgerEntry` idempotency key (unique) | PASS | `FundsLedgerEntry.ts:86` — sparse unique index |
| `TelegramSession` TTL index | PASS | `TelegramSession.ts:66` |
| Password change clears refresh tokens | PASS | `authController.ts:887` |
---
## Summary
| Severity | Count |
|----------|-------|
| CRITICAL | 4 |
| HIGH | 5 |
| MEDIUM | 7 |
| LOW | 2 |
**Top priority:** CRIT-1 (concurrent webhook double-processing) and CRIT-2 (Telegram race condition) require atomic DB operations. CRIT-3 (simulation bypass) and CRIT-4 (hardcoded credential) are one-line fixes.
---
## Related
- [[Security Audit - 2026-05-24]]
- [[Performance Audit - 2026-05-24]]
- [[Funds Ledger and Escrow State Machine Specification]]
- [[Payment Provider Adapter Spec]]
- [[Authentication Flow]]
- [[Platform Logical Audit - 2026-05-24]]

View File

@@ -0,0 +1,173 @@
---
title: Payment Provider Adapter Spec
tags: [adapters, payments, specification, architecture]
created: 2026-05-24
status: advisory
reviewers: [backend, security, product]
---
# Payment Provider Adapter Spec
This specification standardizes how payment providers are plugged in so platform logic
does not depend on SHKeeper or any single webhook implementation.
The contract below replaces provider-specific branching in domain services and should
be used by all pay-in, payout, release, and reconciliation logic.
> Canonical implementation note: this is an advisory ADR for tasks in `task 4.6`.
> It maps to [[Funds Ledger and Escrow State Machine Specification]] and [[Webhook Security Spec]].
## 1. Core provider contract
Implementations expose one typed adapter:
```ts
interface PaymentProviderAdapter {
readonly provider: "shkeeper" | "request_network" | "manual_wallet" | "admin_wallet" | string;
createPayInIntent(input: PayInIntentInput): Promise<PayInIntentResult>;
getPayInStatus(input: PayInStatusInput): Promise<PayInStatusResult>;
handleProviderWebhook(input: ProviderWebhookInput): Promise<ProviderWebhookResult>;
createHostedPaymentLink(input: HostedLinkInput): Promise<HostedLinkResult>;
createReleaseInstruction(input: ReleaseInstructionInput): Promise<ReleaseInstructionResult>;
createRefundInstruction(input: RefundInstructionInput): Promise<RefundInstructionResult>;
getPayoutStatus(input: PayoutStatusInput): Promise<PayoutStatusResult>;
searchProviderPayments(input: ProviderSearchInput): Promise<ProviderPaymentRecord[]>;
}
```
All adapters must return a normalized result shape:
```ts
type NormalizedProviderStatus = "pending" | "processing" | "confirmed" | "completed" | "failed" | "cancelled" | "released" | "refunded";
type NormalizedProviderEvent = {
providerPaymentId: string;
purchaseRequestId?: string;
requestId?: string;
providerReference?: string;
amount: string; // decimal string
currency: string;
status: NormalizedProviderStatus;
transactionHash?: string;
providerEventType: string;
receivedAt: string; // ISO timestamp
rawFingerprint: string; // provider payload hash
};
```
## 2. Method semantics
### 2.1 `createPayInIntent`
- Create a provider-specific payment intent from a canonical request.
- Must return:
- `providerPaymentId` (source of truth for future reconciliation),
- canonical `status`,
- `payInUrl` when redirect/payment-page flow is used,
- an expiry timestamp.
- Must persist provider metadata under `payment.providerData.<provider>`.
### 2.2 `getPayInStatus`
- Query provider status for an existing intent.
- Must map provider statuses into `NormalizedProviderStatus` and include a provider-specific raw snapshot.
- Must be idempotent and side-effect free.
### 2.3 `handleProviderWebhook`
- Input must include raw body bytes, headers, provider identifier, and parsed envelope.
- Must verify signatures before parsing business fields.
- On success, emit canonical domain events and return an idempotency decision:
- `processed` for first apply,
- `duplicate` for replay,
- `ignored` for unknown payment / no-op transitions.
### 2.4 `createHostedPaymentLink`
- Return the user-visible payment URL + optional redirect/callback endpoints.
- Should support provider aliases (for migration aliasing, e.g., `request-network` vs `request_network`).
### 2.5 `createReleaseInstruction` and `createRefundInstruction`
- Produce signed/payload instructions and pre-check:
- account/release eligibility,
- dispute hold not active,
- sufficient releasable balance (ledger-derived),
- admin approval requirements if configured.
- Must never directly mutate release state.
- Must be idempotent by `(paymentId, actionType)` where action type is `release|refund`.
### 2.6 `getPayoutStatus`
- Return state of pending/processing payout tasks and chain/on-chain confirmation status.
- Return normalized status to domain services:
- `processing` for queued/broadcast not-finalized,
- `completed` for finalized payment,
- `failed` with provider error code when rejected.
### 2.7 `searchProviderPayments`
- Used for reconciliation and manual verification.
- Must support:
- `providerPaymentId`/`requestId` lookup,
- time-window pagination,
- optional min/max amount filtering.
- Must never be the primary source for state transitions without reconciliation checks.
## 3. Routing and selection
Provider selection follows environment-configured capability flags:
- `PAYMENT_ENABLED_PROVIDERS` (comma-separated allowlist),
- `PAYMENT_DEFAULT_PROVIDER` (read-first fallback),
- `PAYMENT_ROLLBACK_PROVIDER` (read-only fallback target for cutbacks),
- `PAYMENT_MODE`:
- `standard`: normal provider routing,
- `dry_run`: no writes, status-only,
- `read_only`: no new pay-in/intent writes.
Selection rules:
1. Validate provider support and provider license/credential validity.
2. Route legacy requests to `shkeeper` when explicit migration window is active.
3. For unknown `provider`, return a `400 Bad Request` with explicit operator-visible error code.
4. If requested provider is disabled, return `409` with migration explanation and owner-visible hint for operator override.
## 4. Canonical metadata contract
Payment documents keep provider-specific data namespaced under:
- `metadata.providers.<provider>.rawPayload`
- `metadata.providers.<provider>.rawEvents[]`
- `metadata.providers.<provider>.providerPaymentId`
- `metadata.providers.<provider>.lastWebhookAt`
Domain services must never read `metadata.providers.*` as mutable funds state. They must use ledger-derived balances and canonical status fields only.
## 5. Error contract
All adapter methods return standard failure modes:
- `retryable: true` for transient provider errors (timeouts, 5xx, queue backpressure).
- `retryable: false` for invalid payloads, invalid signatures, and authorization failures.
- `errorCode` must be stable across retries for auditability.
## 6. Test coverage required
- Contract tests per adapter:
- `createPayInIntent`, status polling, webhook handling
- invalid/absent signature behavior
- duplicate webhook idempotency
- unknown payment reference behavior
- rollback selection and read-only mode behavior.
- Reconciliation tests:
- provider backfill for missing payment references,
- status drift correction,
- duplicate/missing event merge.
## Related
- [[Webhook Security Spec]]
- [[Funds Ledger and Escrow State Machine Specification]]
- [[Backend Core Stack Decision Record - 2026-05-24]]
- [[Backend Funds Migration and Operational Runbooks]]

View File

@@ -0,0 +1,338 @@
---
title: Performance Audit — 2026-05-24
tags: [audit, performance, database, findings]
created: 2026-05-24
status: open
---
# Performance Audit — 2026-05-24
Performance review of MongoDB queries, indexes, in-memory data structures, Socket.IO emission patterns, and pagination. Triggered by completion of Request Network integration, Telegram auth, and the internal funds ledger. Every finding is derived from actual source code.
> [!warning] Scale risk
> H3 (unbounded seller fan-out) and H4 (full chat document in memory) are time-bombs that will cause outages once user counts and message volumes grow past modest scale. Both have zero performance headroom at their current implementation.
---
## HIGH
### H1 — N+1 Query: `getPurchaseRequestsByBuyer` Issues One Payment Lookup Per Request Row
**File:** `src/services/marketplace/PurchaseRequestService.ts:516534`
**Status:** Open
```ts
await Promise.all(requests.map(async (request) =>
Payment.findOne({ purchaseRequestId: request._id })
))
```
A page of 10 requests produces 11 DB round-trips (1 list + 10 payment lookups). With a 50-item page, that is 51 round-trips on every buyer dashboard load.
**Remediation:**
```ts
const requestIds = requests.map(r => r._id);
const payments = await Payment.find({ purchaseRequestId: { $in: requestIds } }).lean();
const paymentMap = new Map(payments.map(p => [p.purchaseRequestId.toString(), p]));
// then: requestWithPayment = { ...r, payment: paymentMap.get(r._id.toString()) }
```
---
### H2 — Missing Index on `Payment.purchaseRequestId`
**File:** `src/models/Payment.ts:190196`
**Status:** Open
The Payment schema indexes `buyerId`, `sellerId`, `status`, `transactionHash`, and `providerPaymentId` — but not `purchaseRequestId`. Every call to `Payment.findOne({ purchaseRequestId })` (used in `getPurchaseRequestsByBuyer`, `getPurchaseRequestById`, payment coordinator, notification handlers) causes a full collection scan.
**Remediation:** Add to `Payment.ts`:
```ts
paymentSchema.index({ purchaseRequestId: 1, status: 1 });
```
---
### H3 — Unbounded Seller Fan-Out on New Public Purchase Request
**File:** `src/services/marketplace/PurchaseRequestService.ts:190191`
**Status:** Open
```ts
sellers = await User.find({ role: "seller", status: "active" }).select(...)
```
When a public purchase request is created, every active seller is fetched with no limit. For 10 000 sellers:
- 10 000 `Notification` documents are inserted (not bulk-inserted — individual creates in a loop).
- Each insert calls `emitRealTimeNotification``getUnreadCount` (an additional `countDocuments` query per seller = 10 000 extra queries).
- A 50 ms staggered `setTimeout` per seller means the event loop is managing 500 seconds of scheduled callbacks.
**Remediation:**
1. Cap the fan-out with a configurable batch limit (e.g., 500 most-relevant sellers by category).
2. Use `Notification.insertMany(docs)` for bulk creation.
3. Push a single room-broadcast (`io.to('sellers').emit(...)`) rather than per-user socket emits.
4. Move heavy fan-out to a background job queue (Bull/BullMQ).
---
### H4 — Full Chat Document with All Embedded Messages Loaded for Pagination
**File:** `src/services/chat/ChatService.ts:370408`
**Status:** Open
```ts
const chat = await Chat.findById(chatId).populate("messages.senderId", ...)
const messages = chat.messages.sort(...).slice(skip, skip + limit)
```
A chat with 50 000 messages (~5 KB each = 250 MB) is fully loaded into Node.js memory on every paginated message request. Pagination happens in JavaScript after the full document is fetched.
**Remediation (minimum):** Use MongoDB's `$slice` projection:
```ts
const chat = await Chat.findById(chatId, {
messages: { $slice: [skip, limit] },
participants: 1,
unreadCounts: 1
}).lean();
```
**Recommended long-term:** Extract messages into a separate `Message` collection with server-side pagination (`Message.find({ chatId }).sort({ timestamp: -1 }).skip(skip).limit(limit).lean()`).
---
### H5 — Extra `countDocuments` Query on Every Single Notification Creation
**File:** `src/services/notification/NotificationService.ts:131152`
**Status:** Open
`emitRealTimeNotification` calls `emitUnreadCountUpdate`, which calls `getUnreadCount` (a full `countDocuments` query) synchronously within the notification creation hot path. When H3's 10 000-seller fan-out fires, this produces 10 000 additional `countDocuments` queries in rapid succession.
**Remediation:** Maintain an unread count field directly on the `User` or in a Redis hash. Increment on notification insert rather than re-counting. Or skip the re-query and have the client increment its local counter on socket event receipt.
---
### H6 — `getUserPayments` Queries Non-Indexed Field and Has No Default Limit
**File:** `src/services/payment/paymentService.ts:331370`
**Status:** Open
The query builds `query.userId = userId` (line 343), but the Payment schema stores buyers as `buyerId` (ObjectId) — not `userId`. The query hits `metadata.legacyUserId`, which has no index. Additionally, if `options.limit` is not supplied the query returns all matching documents with no limit.
**Remediation:**
1. Fix field: `query.$or = [{ buyerId: userId }, { sellerId: userId }]`
2. Add default limit: `.limit(options.limit ?? 100)`
---
## MEDIUM
### M1 — Missing Compound Index `(buyerId, createdAt)` on PurchaseRequest
**File:** `src/models/PurchaseRequest.ts:360369`
**Status:** Open
`getPurchaseRequestsByBuyer` queries `{ buyerId }` with `.sort({ createdAt: -1 })`. The existing single-field index on `buyerId` satisfies the filter but forces MongoDB to sort results post-scan. A compound index eliminates the sort entirely.
**Remediation:** Add to `PurchaseRequest.ts`:
```ts
PurchaseRequestSchema.index({ buyerId: 1, createdAt: -1 });
PurchaseRequestSchema.index({ status: 1, createdAt: -1 });
PurchaseRequestSchema.index({ categoryId: 1, status: 1, createdAt: -1 });
```
---
### M2 — Unanchored Case-Insensitive Regex Search — Full Collection Scan
**File:** `src/services/marketplace/PurchaseRequestService.ts:703720`
**Status:** Open
```ts
{ title: { $regex: searchTerm, $options: "i" } }
```
An unanchored, case-insensitive regex cannot use a B-tree index. MongoDB performs a full collection scan with in-memory string matching on every search request.
**Remediation:** Create a text index:
```ts
PurchaseRequestSchema.index({ title: 'text', description: 'text', tags: 'text' });
```
Query with `{ $text: { $search: searchTerm } }` and sort by `{ score: { $meta: 'textScore' } }`.
---
### M3 — Double-Fetch Pattern in Update Methods (No `.lean()` on Pre-Check)
**File:** `src/services/marketplace/PurchaseRequestService.ts:406425,557572`
**Status:** Open
Both `updatePurchaseRequest` and `updatePurchaseRequestStatus` call `PurchaseRequest.findById(requestId)` to validate current status, then call `findByIdAndUpdate`. The pre-check `findById` instantiates a full Mongoose document that is immediately discarded.
**Remediation:** Use `findByIdAndUpdate` with `{ new: false }` to get the pre-update document in a single round-trip, or use `.lean().select('status')` on the pre-check to minimize overhead.
---
### M4 — Regex on Embedded `messages.content` Array — Full Array Deserialization
**File:** `src/services/chat/ChatService.ts:318320`
**Status:** Open
```ts
{ "messages.content": { $regex: filters.search, $options: "i" } }
```
MongoDB must deserialize the entire embedded `messages` array for every chat document to evaluate this filter. There is no index support for nested array field regex searches.
**Remediation:** Move to a dedicated `Message` collection with a text index on `content`, then query `Message.find({ chatId: { $in: userChatIds }, $text: { $search: searchTerm } })`.
---
### M5 — In-Memory Coordination State Not Shared Across Replicas
**File:** `src/services/payment/paymentCoordinator.ts:25`
**Status:** Open
The `coordinationState` Map is process-local. In a multi-replica deployment, each process starts with an empty map; the 10-second debounce provides no cross-process deduplication. The DB guard in CRIT-1 (Logic Audit) is the correct production fix; this Map is supplementary at best.
*(Code comment at line 24 already acknowledges this — a Redis migration is planned.)*
---
### M6 — `getSellers()` Is Unbounded (No Limit)
**File:** `src/services/marketplace/PurchaseRequestService.ts:728741`
**Status:** Open
```ts
await User.find({ role: "seller", isEmailVerified: true }).select(...).lean()
```
No `.limit()`. Returns every seller in the collection. This method is exposed via API.
**Remediation:** Add `.limit(500)` at minimum, or paginate with a cursor.
---
### M7 — `user-online` Event Broadcast to All Connected Sockets Globally
**File:** `src/app.ts:300`
**Status:** Open
```ts
socket.broadcast.emit('user-status-change', { userId, status: 'online', ... })
```
This pushes the online-presence event to every connected socket regardless of whether they have a relationship with the user. With 10 000 concurrent connections, this is 10 000 individual socket writes per login event.
**Remediation:** Restrict to relevant rooms. For a marketplace, "relevant" means chat participants and active purchase request counterparties:
```ts
userChatIds.forEach(chatId => {
socket.to(`chat-${chatId}`).emit('user-status-change', { userId, status: 'online' });
});
```
---
### M8 — Post-Filter After Pagination Causes Wrong `totalItems` Count
**File:** `src/services/marketplace/PurchaseRequestService.ts:258337`
**Status:** Open
The code paginates the base query, then applies visibility filtering (offer ownership, anonymization) after pagination. This means `totalItems` in the response is the pre-filter count, not the count of items the caller can actually see. Users may see fewer items than indicated by the pagination metadata (e.g., "showing 8 of 10" when only 6 are visible).
**Remediation:** Move visibility filtering into the MongoDB query (aggregation pipeline with `$match` conditions) so `countDocuments` reflects the actual visible set.
---
## LOW
### L1 — `getUserChats` Runs Count Query Sequentially After Find
**File:** `src/services/chat/ChatService.ts:324335`
**Status:** Open
`Chat.find(query)` and `Chat.countDocuments(query)` are independent queries run sequentially. They should run in parallel.
**Remediation:**
```ts
const [chats, total] = await Promise.all([
Chat.find(query).lean(),
Chat.countDocuments(query)
]);
```
*(The notification service at `NotificationService.ts:5361` already does this correctly — apply the same pattern.)*
---
### L2 — `findByIdAndUpdate` Results Not Using `.lean()`
**File:** `src/services/marketplace/PurchaseRequestService.ts:418425,566572`
**Status:** Open
`findByIdAndUpdate` with `.populate(...)` returns full Mongoose document objects for data that is only read and returned to the client. `.lean()` saves ~1530% memory and processing per returned document.
**Remediation:** Add `.lean()` to read-only `findByIdAndUpdate` calls.
---
### L3 — Telegram Replay Maps Are Process-Local (Multi-Replica Gap, Same as M5)
**File:** `src/services/telegram/telegramService.ts:395396`
**Status:** Open
```ts
const initDataReplayWindow = new Map<string, number>();
const webhookReplayWindow = new Map<string, number>();
```
`cleanupReplayMap` is called lazily before each check — entries are evicted correctly once the window expires, so the Maps do not grow unboundedly under normal load. However, like `coordinationState`, they are process-local and will not catch replays across multiple Node replicas.
**Remediation:** Redis `SET NX EX` (same as Security Audit H-1 recommendation).
---
### L4 — PasskeyService Challenge Map: 5-Minute Cleanup Is Correct, Process-Local Only
**File:** `src/services/auth/passkeyService.ts:5663`
**Status:** Open
Cleanup is implemented and the code comment (lines 5055) already notes the Redis migration path. Not a performance risk at current scale. Same multi-replica caveat as L3.
---
## Confirmed PASS
| Check | Result | Source |
|-------|--------|--------|
| `TempVerification` TTL index | PASS | `TempVerification.ts:59` — MongoDB-native TTL, no app polling |
| `TelegramSession` TTL index | PASS | `TelegramSession.ts:66` |
| `Notification` 90-day TTL index | PASS | `Notification.ts:77``expireAfterSeconds: 7776000` |
| `FundsLedgerEntry` indexes (purchaseRequestId, paymentId, idempotencyKey) | PASS | `FundsLedgerEntry.ts:86107` |
| SHKeeper polling rate bounded | PASS | `shkeeperSimpleAuto.ts:64` — 2-minute interval, self-cleaning |
| Socket.IO room cleanup on disconnect | PASS | Socket.IO removes socket from all rooms automatically on disconnect |
| `getUserNotifications` count + find in parallel | PASS | `NotificationService.ts:5361` — already uses `Promise.all` |
| Telegram replay map bounded (lazy eviction) | PASS | `telegramService.ts:100``cleanupReplayMap` evicts expired entries |
---
## Index Additions Summary
The following indexes should be added to eliminate collection scans identified in this audit:
```ts
// Payment.ts
paymentSchema.index({ purchaseRequestId: 1, status: 1 });
// PurchaseRequest.ts
PurchaseRequestSchema.index({ buyerId: 1, createdAt: -1 });
PurchaseRequestSchema.index({ status: 1, createdAt: -1 });
PurchaseRequestSchema.index({ categoryId: 1, status: 1, createdAt: -1 });
// PurchaseRequest.ts (text search)
PurchaseRequestSchema.index({ title: 'text', description: 'text', tags: 'text' });
```
Caution: adding indexes increases write latency and storage. Run `db.collection.getIndexes()` before adding to avoid duplicating existing indexes (some `unique: true` fields already create indexes automatically).
---
## Summary
| Severity | Count |
|----------|-------|
| HIGH | 6 |
| MEDIUM | 8 |
| LOW | 4 |
**Highest urgency:** H3 (seller fan-out) and H4 (full chat in memory) will cause visible degradation with modest growth. H1 (N+1 payments) and H2 (missing index) are easy wins with high query-count impact.
---
## Related
- [[Security Audit - 2026-05-24]]
- [[Logic Audit - 2026-05-24]]
- [[Backend Architecture]]
- [[Data Model Overview]]
- [[Funds Ledger and Escrow State Machine Specification]]

View File

@@ -0,0 +1,153 @@
---
title: Realtime Authorization Spec
tags: [security, realtime, socketio, authorization]
created: 2026-05-24
status: advisory
---
# Realtime Authorization Spec
This document defines the target authorization model for Socket.IO events in the
escrow platform. It closes Taskmaster subtask 4.4 alongside
[[Authorization Matrix - REST and Socket.IO]].
## 1. Decision
Socket.IO must use the same trust boundary as REST:
- every socket connection is authenticated during the handshake,
- room membership is derived by the server,
- clients cannot subscribe to rooms by supplying arbitrary user, request, chat,
seller, or buyer IDs,
- server-to-client emissions are targeted to authorized rooms only,
- sensitive payment, payout, dispute, delivery-code, and chat payloads are never
sent through global broadcasts.
## 2. Handshake Authentication
Client connects with an access token in `handshake.auth.token`.
Server requirements:
1. verify the JWT signature and standard claims,
2. reject expired, malformed, revoked, or missing tokens,
3. attach `{ userId, roles, sessionId, jti }` to `socket.data`,
4. disconnect immediately when authentication fails,
5. log authentication failures without recording token values.
Refresh tokens are not accepted by Socket.IO. Clients must refresh through REST
and reconnect with a fresh access token.
## 3. Server-Derived Base Rooms
On successful connection the server may join only rooms derivable from the
authenticated principal:
| Room | Eligibility | Source |
|---|---|---|
| `user-{userId}` | authenticated user | JWT subject |
| `seller-{userId}` | authenticated user with seller role | JWT roles or user record |
| `buyer-{userId}` | authenticated user with buyer role | JWT roles or user record |
| `sellers` | authenticated seller | JWT roles or user record |
| `buyers` | authenticated buyer | JWT roles or user record |
Clients must not provide `userId`, `sellerId`, or `buyerId` to join these rooms.
## 4. Resource Rooms
Resource rooms require database authorization before join.
| Room | Eligibility | Authorization Query |
|---|---|---|
| `request-{requestId}` | buyer, selected seller, assigned admin/moderator | purchase request participant check |
| `chat-{chatId}` | chat participant or assigned support/admin user | chat participant check |
| `dispute-{disputeId}` | dispute party, assigned moderator, admin | dispute participant/assignment check |
| `template-checkout-{checkoutId}` | checkout owner or service-controlled UI session | checkout ownership check |
Membership must be rechecked when ownership or state changes. If a request,
chat, or dispute loses a participant, the server must remove that user's sockets
from the associated room.
## 5. Client Event Policy
Allowed client-originated events:
| Event | Required Authorization | Notes |
|---|---|---|
| `join-request-room` | participant check | May remain only as a request for server validation. |
| `leave-request-room` | current membership | User may leave an allowed room. |
| `join-chat-room` | participant check | May remain only as a request for server validation. |
| `leave-chat-room` | current membership | User may leave an allowed room. |
| `typing-start` / `typing-stop` | current `chat-{chatId}` membership | `userId` in payload is ignored; server derives sender. |
Removed or deprecated client-originated events:
| Event | Replacement |
|---|---|
| `join-user-room` | server auto-join on handshake |
| `join-seller-room` / `join-buyer-room` | server auto-join from authenticated role |
| `user-online` | server emits presence after authenticated connection |
## 6. Emission Policy
Server emissions must target the narrowest authorized room:
| Data Class | Allowed Target |
|---|---|
| user notifications | `user-{recipientId}` |
| buyer/seller offer updates | relevant `user-*`, `buyer-*`, `seller-*`, or `request-*` room |
| payment status | buyer and seller user rooms, request room if both parties may see it |
| payout status | seller user room and admin operations room only |
| delivery code | seller user room only |
| chat messages | `chat-{chatId}` |
| dispute events | `dispute-{disputeId}` and assigned admin/moderator room |
Global payment and payout events are prohibited because they expose financial
metadata to unrelated users.
## 7. Payload Rules
- Never trust `userId`, `role`, `sellerId`, or `buyerId` from socket payloads.
- Derive sender identity from `socket.data.userId`.
- Do not emit delivery verification codes to buyer-visible rooms.
- Redact wallet addresses, tx hashes, and provider references unless the target
user is a party to the transaction or an authorized operator.
- Keep payload schemas consistent with REST read permissions.
## 8. Rate Limiting and Audit
Socket event rate limits:
| Event Class | Limit |
|---|---|
| room join attempts | 30 per 15 minutes per user |
| typing events | 120 per minute per socket |
| chat message events | same policy as REST chat message creation |
| failed authorization checks | 10 per 15 minutes per user, then disconnect |
Audit log required for:
- failed room authorization checks,
- admin/moderator joins to dispute or request rooms,
- attempts to join user/seller/buyer rooms for another principal,
- global payment or payout emission rejection.
## 9. Tests
Minimum verification before launch:
1. invalid or missing JWT cannot connect,
2. user cannot join another user's `user-*`, `seller-*`, or `buyer-*` room,
3. user cannot join a request/chat/dispute room without participant status,
4. removed participant is evicted from the resource room,
5. payment and payout events are not emitted globally,
6. delivery code is emitted only to the seller,
7. socket event rate limits disconnect abusive clients,
8. audit events are written for denied room joins.
## Related
- [[Authorization Matrix - REST and Socket.IO]]
- [[Threat Model - Amanat Escrow Platform]]
- [[Session and Authentication Architecture Decision]]
- [[Backend Stack Security and Refactor Assessment - 2026-05-24]]

View File

@@ -0,0 +1,571 @@
---
title: Secure Build and Supply-Chain Policy
tags: [audit, security, supply-chain, build, policy, ci-cd]
created: 2026-05-24
status: active-policy
---
# Secure Build and Supply-Chain Policy
## 1. Policy Statement
Amanat is a financial escrow platform handling cryptocurrency pay-in, payout, dispute-sensitive fund movement, and provider webhook processing. A single compromised dependency in either the frontend (Next.js) or backend (Express/Node.js) application can result in unauthorized fund release, private data exfiltration, or webhook forgery.
The Node.js/npm ecosystem has demonstrated real and recurring supply-chain risk. In the first five months of 2026 alone, four significant incidents directly affected packages used by this platform:
- Express/Multer high-severity vulnerability (February 2026).
- Node.js security releases across active release lines (March 2026).
- Axios npm supply-chain compromise reported by Microsoft (March 2026).
- TanStack npm supply-chain compromise postmortem (May 2026).
This policy establishes mandatory controls for dependency management, vulnerability response, build integrity, and secrets handling across both the frontend and backend codebases. Compliance with this policy is required for all engineers, CI/CD pipelines, and deployment processes.
**Scope:** This policy covers the Amanat frontend (`nickapp-frontend`, Next.js) and backend (`nickapp-backend`, Express/Node.js). It applies to all environments: local development, CI, staging, and production.
**Ownership:** The engineering lead or designated security owner is accountable for enforcing this policy. If a dedicated security owner has not been assigned, the backend lead shall act as default policy owner until one is named. See [[Security Architecture]] for the broader security model.
**References:** [[Backend Stack Security and Refactor Assessment - 2026-05-24]], [[Platform Logical Audit - 2026-05-24]], [[Backend Architecture]], [[Frontend Architecture]], [[Infrastructure]], [[Environment Variables]]
---
## 2. Package Manager and Lockfile Policy
### 2.1 Lockfile commits are mandatory
Every repository must commit its lockfile to version control. The lockfile is the authoritative record of resolved dependency versions.
- Backend: `package-lock.json` must be committed.
- Frontend: `package-lock.json` (or `yarn.lock` / `pnpm-lock.yaml` if the frontend uses an alternative package manager) must be committed.
A PR that modifies `package.json` without a corresponding lockfile update shall be rejected.
### 2.2 Frozen install in CI/CD
All CI/CD pipelines and production build processes must use frozen install mode. This prevents silent resolution changes and ensures the build matches the committed lockfile exactly.
```bash
# Required in CI
npm ci
# Forbidden in CI
npm install
```
For non-npm package managers:
- Yarn: `yarn install --frozen-lockfile`
- pnpm: `pnpm install --frozen-lockfile`
If the frozen install fails due to a lockfile mismatch, the CI job must fail. The developer must regenerate the lockfile locally and commit it.
### 2.3 Lockfile diff review in PRs
Any PR that adds, removes, or updates dependencies must include the lockfile diff in the PR description or as an attached artifact. Reviewers must verify:
- No unexpected transitive dependency additions.
- No version downgrades.
- No substitution of a known package with an identically named package from a different registry or scope.
### 2.4 npm install forbidden in production builds
The `npm install` command (non-frozen mode) must never appear in:
- Dockerfile production stages.
- CI build steps.
- Deployment scripts.
Production Dockerfiles must use multi-stage builds where the install stage runs `npm ci` and the runtime stage copies `node_modules` from the builder. Both current Dockerfiles (`backend/Dockerfile.prod`, `frontend/Dockerfile`) already follow this pattern and must continue to do so.
---
## 3. Dependency Update Cadence
### 3.1 Routine updates
Routine dependency updates (minor and patch versions with no known security implications) shall be performed on a biweekly cadence. The responsible engineer must:
1. Run `npm outdated` on both repositories.
2. Update non-breaking packages.
3. Run the full test suite.
4. Submit a PR with the lockfile diff for review.
5. Obtain approval from at least one other engineer before merging.
### 3.2 Security-critical updates
Security-critical updates (packages with published CVEs or advisories rated Critical or High) must be applied immediately, within the SLAs defined in Section 4. The responsible engineer must:
1. Identify the minimum version that resolves the vulnerability.
2. Update the package and lockfile.
3. Run the full test suite.
4. Submit and merge the PR with expedited review (no more than 24 hours).
5. Deploy to production within the SLA window.
### 3.3 Major version bumps
Major version bumps (semver major) require:
1. A dedicated issue documenting the reason for the upgrade.
2. Review of the package changelog and migration guide.
3. Identification of breaking changes affecting Amanat code.
4. Testing in a dedicated branch or environment before merge.
5. Approval from the engineering lead or security owner.
Major version bumps must not be bundled with other changes. Each major bump is a separate PR.
### 3.4 Approval authority
| Update type | Approver |
|---|---|
| Patch (no security advisory) | Any engineer (one approval) |
| Minor (no security advisory) | Any engineer (one approval) |
| Patch or minor (with security advisory) | Engineering lead or security owner |
| Major (any) | Engineering lead or security owner |
| New dependency addition | Engineering lead or security owner |
---
## 4. Security Advisory Monitoring
### 4.1 Automated vulnerability scanning
Both repositories must be enrolled in automated vulnerability scanning. At minimum, one of the following must be active:
- **GitHub Dependabot** (if hosted on GitHub) or equivalent Gitea-native scanning.
- **npm audit** as a required CI step (exit on Critical or High).
- **Snyk** or equivalent commercial scanner (recommended for production use).
The CI pipeline must run vulnerability scanning on every push and on a daily scheduled basis.
### 4.2 Monitoring sources
Engineers must monitor the following sources for security advisories:
| Source | URL or method | Frequency |
|---|---|---|
| GitHub Security Advisories | `github.com/advisories` or Dependabot alerts | Automated |
| npm audit | `npm audit --audit-level=high` in CI | Every build |
| Snyk vulnerability DB | Snyk dashboard or CLI | Continuous |
| Dependabot / Renovate | Automated PRs for vulnerable deps | Continuous |
| Express security page | `https://expressjs.com/en/security.html` | Manual review monthly |
| Node.js security blog | `https://nodejs.org/en/blog/vulnerability` | Manual review monthly |
| Project-specific package feeds | TanStack, Axios, MUI, wagmi release notes | As released |
### 4.3 Response SLAs
When a vulnerability is identified in a dependency used by either repository, the following response timelines apply:
| Severity | Maximum time to patched version in production | Required action |
|---|---|---|
| Critical | 24 hours | Immediate patch or pin, deploy within SLA |
| High | 72 hours | Patch or pin within 48 hours, deploy within SLA |
| Medium | 1 week | Schedule in next sprint or maintenance window |
| Low | Next routine update cycle (biweekly) | Track, no expedited action required |
Severity ratings follow the advisory source (CVSS score or npm audit severity). When multiple sources disagree, the highest severity wins.
### 4.4 Vulnerability response process
When a vulnerability is discovered:
1. **Assess:** Determine severity, affected versions, and whether the Amanat codebase is actually impacted (some vulnerabilities require specific configuration or usage patterns).
2. **Patch or pin:** Update to the fixed version if available. If no fix exists, pin or remove the dependency. If pinning is not feasible, implement a compensating control (input validation, route restriction, feature flag) and document the exception.
3. **Verify:** Run the full test suite. Perform manual verification if the vulnerability affects payment, auth, or webhook flows.
4. **Deploy:** Follow the expedited deployment path if the SLA requires it. Bypass non-critical CI gates only with engineering lead approval.
All vulnerability responses must be recorded in the Known Exposure Register (Section 5).
---
## 5. Known Exposure Register
This register tracks supply-chain exposures identified during the security assessment dated 2026-05-24. It must be updated whenever a new exposure is discovered or an existing one is resolved.
| # | Package | Current Version | Vulnerability | Severity | Status | Action Required | SLA Deadline | Date Opened |
|---|---|---|---|---|---|---|---|---|
| 1 | `multer` | `^2.0.2` | Express Feb 2026 security release: high-severity Multer issues affecting versions before 2.1.0. Reference: https://expressjs.com/2026/02/27/security-releases.html | High | Open | Update to `>=2.1.0`. Verify resolved lockfile version. Run full test suite. | 2026-05-27 | 2026-05-24 |
| 2 | `axios` | In use (frontend + backend) | npm supply-chain compromise reported by Microsoft, March 2026. Reference: https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/ | Critical | Open | Verify installed version is post-compromise clean build. If affected version was installed, rotate all credentials transmitted via Axios interceptors and review access logs for anomalous outbound requests. Update to latest clean version. | 2026-05-25 | 2026-05-24 |
| 3 | `@tanstack/react-query` | In use (frontend) | npm supply-chain compromise, May 2026. Reference: https://tanstack.com/blog/npm-supply-chain-compromise-postmortem | Critical | Open | Verify installed version is post-compromise clean build. If affected version was installed, review browser bundle for injected code, rotate any credentials accessible from the React query context, and audit client-side outbound network requests in production logs. Update to latest clean version. | 2026-05-25 | 2026-05-24 |
| 4 | `express` | In use (backend) | February 2026 security releases affecting Express core. Reference: https://expressjs.com/2026/02/27/security-releases.html | High | Open | Verify installed version is at or above the patched release. Run `npm audit` to confirm no residual advisory. | 2026-05-27 | 2026-05-24 |
| 5 | `node` | `node:22-alpine` (Docker) | Node.js March 2026 security releases across active release lines. Reference: https://nodejs.org/en/blog/vulnerability/march-2026-security-releases | High | Open | Update base Docker image to the latest patched Node.js 22.x release. Rebuild both Docker images. Verify runtime compatibility. | 2026-05-27 | 2026-05-24 |
**Register maintenance rules:**
- Every entry must have an owner, a status (Open, In Progress, Resolved, Accepted Risk), and a target resolution date.
- Resolved entries must not be deleted. Change status to Resolved and record the resolution details.
- Accepted Risk entries require engineering lead approval and a documented justification.
---
## 6. npm Provenance and Signature Policy
### 6.1 Provenance verification
Where npm packages offer provenance attestations (signed build metadata linking a published package to its source repository and build process), engineers must verify provenance before adopting or updating the dependency.
Verification methods:
```bash
# Check npm provenance for a package
npm audit signatures
# Verify provenance attestation (npm 10+)
npm view <package> --json | jq '.attestations'
```
### 6.2 Packages without provenance
Packages that do not offer provenance attestations are not automatically blocked, but they require additional scrutiny:
- Verify the package maintainer identity (GitHub account history, organization membership).
- Check the package download count and publication date for anomalies.
- Review recent version publish history for suspicious patterns (rapid successive publishes, new maintainers added before a release).
- Prefer packages with provenance, two-factor authentication on the maintainer account, and a history of responsible disclosure.
### 6.3 Allowlist and blocklist
The following rules apply:
| Rule | Enforcement |
|---|---|
| Packages from the `@types` scope are permitted without additional review. | Automatic |
| Packages from verified first-party scopes (`@nestjs`, `@mui`, `@tanstack`, `@sentry`, `@next`) are permitted with standard review. | Automatic |
| Any new package not previously used in the project requires engineering lead or security owner approval. | Manual gate |
| Packages known to be compromised or abandoned must be added to the project blocklist. | Immediate upon discovery |
**Blocklist mechanism:** Create a `BLOCKLIST.md` file in each repository root. CI must check that no blocklisted package appears in the dependency tree. This check can be implemented as a CI step:
```bash
# Example CI check
while IFS= read -r pkg; do
if npm ls "$pkg" 2>/dev/null | grep -q "$pkg"; then
echo "BLOCKED: $pkg found in dependency tree"
exit 1
fi
done < BLOCKLIST.md
```
---
## 7. Secrets Handling
### 7.1 Storage
Secrets must be stored exclusively in:
- Environment variables injected at runtime (`.env` files for local development, Compose `env_file` for Docker, or a secrets manager for multi-host deployments).
- A dedicated secrets manager (HashiCorp Vault, AWS SSM, or GCP Secret Manager) for production multi-host deployments.
Secrets must never be stored in:
- Source code (any file committed to git).
- Docker image layers (environment variables baked into `ENV` directives in production Dockerfiles, except for non-secret `NEXT_PUBLIC_*` values).
- Build artifacts or logs.
- Client-side JavaScript bundles (except intentionally public values prefixed `NEXT_PUBLIC_`).
See [[Environment Variables]] for the complete catalog and classification of all environment variables.
### 7.2 Rotation
| Secret type | Rotation frequency | Method |
|---|---|---|
| `JWT_SECRET` | Annually, or immediately upon suspected compromise | Generate new value (`openssl rand -hex 32`), update in all environments during maintenance window. Invalidates all active sessions. |
| `REFRESH_TOKEN_SECRET` | Annually, or immediately upon suspected compromise | Same process as `JWT_SECRET`. |
| `SHKEEPER_WEBHOOK_SECRET` | Every 6 months | Coordinate with SHKeeper dashboard. Set new secret, verify delivery, remove old. |
| `SHKEEPER_API_KEY` | Every 6 months | Generate new key in SHKeeper dashboard, update backend env. |
| `SMTP_PASS` | Every 6 months | Generate new app password, update backend env. |
| `OPENAI_API_KEY` | Every 6 months | Generate new key in OpenAI dashboard, update backend env. |
| Database passwords | Annually | Update in connection string + database user. |
| `REDIS_PASSWORD` | Annually | Update in compose entrypoint + backend `REDIS_URI`. |
| `NEXT_PUBLIC_ALCHEMY_API_KEY_*` | Every 6 months | Generate new key, rebuild frontend Docker image. |
| `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` | Annually | Generate in WalletConnect dashboard, rebuild frontend. |
### 7.3 Leaked secrets response
If any secret is discovered in a git commit, build log, or publicly accessible location:
1. **Immediate:** Rotate the compromised secret within 1 hour. Do not wait for investigation.
2. **Audit:** Review access logs and audit trails for the compromised secret. Determine whether the secret was used by an unauthorized party.
3. **Assess:** If the secret provided access to financial data, payment endpoints, or admin functions, escalate to the engineering lead and initiate a full incident review.
4. **Clean:** Remove the secret from git history using `git filter-branch` or BFG Repo-Cleaner if the repository is public. Force-push only with engineering lead approval.
5. **Document:** Record the leak and remediation in the incident log.
### 7.4 Frontend public env handling
`NEXT_PUBLIC_*` environment variables are embedded in the browser bundle at build time. The following rules apply:
- Never put secrets (API keys with write access, private keys, webhook secrets) in `NEXT_PUBLIC_*` variables.
- Accept that `NEXT_PUBLIC_*` values (Alchemy keys, WalletConnect ID, Sentry DSN) are visible to any browser user. These services must be configured with domain restrictions and rate limits to mitigate exposure.
- The current Dockerfile hard-codes several `NEXT_PUBLIC_*` values via `ENV` directives. This is acceptable only for non-secret public values. See [[Frontend Architecture]] for the build process and [[Infrastructure]] for Docker configuration.
---
## 8. Production Build Reproducibility
### 8.1 Deterministic builds required
Every production build must be deterministic: the same source code, lockfile, and build environment must produce bit-identical output. This enables verification that a deployed artifact matches its source.
### 8.2 Requirements for reproducible builds
| Requirement | Implementation |
|---|---|
| Frozen dependency resolution | `npm ci` with committed lockfile (see Section 2) |
| Pinned base image | Dockerfile must use a specific Node.js version tag (e.g., `node:22.x.y-alpine`), not `node:22-alpine` or `latest` |
| No floating versions in Dockerfile | Every `RUN npm install` or equivalent uses `npm ci` with the committed lockfile |
| Build-time timestamp normalization | If timestamps matter for output comparison, set `SOURCE_DATE_EPOCH` in the build environment |
| No network calls during build except package install | Build scripts must not fetch remote resources at build time (no `curl`, `wget`, or runtime API calls in Dockerfile build stages) |
### 8.3 Build artifact verification in CI
CI must produce a build artifact (Docker image) with a verifiable identity:
```bash
# Tag the image with the git commit SHA
docker build -t nickapp-backend:${GIT_COMMIT_SHA} .
# Record the image digest
docker inspect --format='{{index .RepoDigests 0}}' nickapp-backend:${GIT_COMMIT_SHA}
```
The production deployment must reference the image by digest, not just by tag. Watchtower must be reconfigured to watch a specific versioned tag (e.g., `nickapp-backend:v2.6.3`) rather than `latest`. See [[Infrastructure]] for the current Watchtower configuration and its known risks.
### 8.4 Current gaps
The current production setup has the following reproducibility gaps that must be resolved:
1. Docker base images use rolling tags (`node:22-alpine`) instead of pinned versions.
2. Watchtower auto-deploys from the `latest` tag with no staging gate.
3. No build digest verification or artifact signing.
---
## 9. Frontend vs Backend Supply-Chain Separation
### 9.1 Frontend npm risk profile
The frontend (Next.js) has a larger dependency tree due to React, MUI, TanStack, wagmi, TipTap, i18next, and their transitive dependencies. The frontend runs in the user's browser, so a compromised dependency can:
- Exfiltrate authentication tokens from `localStorage` or memory.
- Inject malicious code into the rendered page (XSS via dependency).
- Modify API requests via the Axios interceptor chain.
- Access wallet connection state (wagmi/Web3 context).
However, the frontend cannot directly execute server-side operations or access backend secrets. The blast radius of a frontend compromise is limited to the authenticated user's session and data visible to that user.
### 9.2 Backend core risk profile
The backend (Express) has a smaller dependency surface but far higher stakes. A compromised backend dependency can:
- Access database credentials and read/write any document.
- Access SHKeeper API keys and webhook secrets.
- Intercept or forge webhook payloads.
- Bypass authentication or authorization middleware.
- Access OpenAI API keys and SMTP credentials.
- Directly manipulate payment records and escrow state.
The backend must be treated as the higher-risk surface for supply-chain purposes, even though the frontend has more dependencies.
### 9.3 Policy for backend core rewrite
If the backend payment/auth/escrow core is extracted to Go, Kotlin, or Rust as recommended in [[Backend Stack Security and Refactor Assessment - 2026-05-24]], the following supply-chain separation applies:
| Component | Package ecosystem | Supply-chain policy |
|---|---|---|
| Payment/ledger/auth core (Go/Kotlin/Rust) | Go modules / Maven / Cargo | This policy applies analogously: lockfile commits, frozen install, vulnerability scanning, provenance verification |
| Marketplace/chat/notification API (remaining Node.js) | npm | This policy applies in full |
| Frontend (Next.js) | npm | This policy applies in full |
The rewritten core must not share `node_modules` with any remaining Node.js services. Cross-service communication must use authenticated internal APIs, not shared memory or shared filesystem.
### 9.4 Interim policy (both services Node-based)
While both frontend and backend remain on Node.js/npm:
1. The two repositories must maintain separate `package.json` and lockfiles. No shared `node_modules`.
2. A dependency that appears in both repositories (e.g., `axios`) must be independently reviewed and version-pinned in each.
3. Backend dependencies must be reviewed more stringently than frontend dependencies. Any dependency added to the backend must justify why it is necessary and whether a lighter alternative exists.
4. The backend must minimize its dependency count. Before adding a new backend dependency, the engineer must document: (a) what it does, (b) why existing dependencies or the Node.js standard library cannot achieve the same result, (c) the package's maintenance status and security history.
---
## 10. Incident Response for Supply-Chain Compromise
### 10.1 Compromised npm package discovered
**Detection signal:** Automated vulnerability scanner alert, security advisory publication, community report, or anomalous behavior in production.
**Immediate containment (0-1 hour):**
1. Determine which Amanat services (frontend, backend, or both) depend on the compromised package.
2. If the package is in the backend: block the affected routes at the Nginx level if the compromise affects payment, auth, or webhook processing.
3. If the package is in the frontend: assess whether the compromise can exfiltrate user tokens or modify API calls. If so, consider taking the frontend offline or deploying a known-good previous build.
4. Notify the engineering lead and all on-call engineers.
**Assessment (1-4 hours):**
1. Identify the compromised versions and the attack vector (malicious code injection, credential theft, data exfiltration).
2. Determine the clean version range (if available) from the package maintainers or security advisory.
3. Audit production logs and access records for indicators of exploitation specific to the compromise.
4. If the package had access to secrets (API keys, tokens), list all potentially exposed secrets.
**Remediation (4-24 hours):**
1. Update the package to a verified clean version, or remove it if no clean version exists.
2. Rotate all secrets that were accessible to the compromised package (see Section 7.3).
3. Deploy the fix through the standard CI pipeline with expedited review.
4. Verify the fix in staging before production deployment if time permits.
**Communication:**
1. Inform all team members of the compromise, the remediation, and any required user-facing actions.
2. If user data was exfiltrated, notify affected users and comply with applicable data breach regulations.
3. Document the incident in the Known Exposure Register (Section 5) and in the incident log.
**Post-incident review (within 1 week):**
1. Conduct a blameless post-mortem. Identify what detected the compromise (or why detection was delayed).
2. Determine whether additional controls could have prevented or detected the compromise earlier.
3. Update this policy if gaps were identified.
4. Add the compromised package to the blocklist if it remains untrusted.
### 10.2 Leaked API key or secret
**Detection signal:** Secret found in git history, build log, public repository, or unexpected API usage pattern.
**Immediate containment (0-1 hour):**
1. Rotate the leaked secret immediately. Do not wait for assessment. See Section 7.3.
2. If the secret provides database access, consider restricting network access to known IPs as an additional control while the new secret propagates.
3. Notify the engineering lead.
**Assessment (1-4 hours):**
1. Determine the scope of the leak: which environments, which time period, which services.
2. Audit access logs for the compromised secret. Look for access from unrecognized IPs, unusual request patterns, or data exfiltration.
3. Determine whether any financial data, user PII, or payment records were accessed.
**Remediation (4-24 hours):**
1. Remove the secret from git history if the repository is public or shared outside the team.
2. Verify the new secret is active and the old secret is revoked.
3. Scan all code and configuration for other instances of the same secret.
**Communication and post-incident review:** Follow the same process as Section 10.1.
### 10.3 Vulnerable dependency alert (non-compromised)
**Detection signal:** Automated scanner alert, Dependabot PR, npm audit failure.
**Process:**
1. Triage the vulnerability against the SLAs in Section 4.3.
2. Assess whether the Amanat codebase is actually affected (some vulnerabilities require specific configuration or API usage).
3. If affected: patch, test, deploy within the SLA.
4. If not affected: document the assessment in the Known Exposure Register with status Accepted Risk and the justification.
5. No post-incident review required unless the response exceeded the SLA.
---
## 11. CI/CD Enforcement Checklist
Every CI pipeline for both frontend and backend must enforce the following checks. A pipeline that does not pass all checks must not produce a deployable artifact.
| # | Check | Enforcement | Failure action |
|---|---|---|---|
| 1 | **Frozen lockfile install** | Pipeline uses `npm ci` (or `--frozen-lockfile` equivalent). Job fails if lockfile is out of sync with `package.json`. | Block merge |
| 2 | **npm audit / vulnerability scan passes** | `npm audit --audit-level=high` exits 0. No unresolved Critical or High vulnerabilities. | Block merge |
| 3 | **No new Critical/High vulnerabilities** | Delta check: the PR must not introduce new Critical or High vulnerabilities not present in the base branch. | Block merge |
| 4 | **Lockfile diff reviewed if changed** | If `package-lock.json` or `yarn.lock` is modified, require at least one reviewer acknowledgment in the PR. | Block merge |
| 5 | **Build is deterministic** | Build artifacts are tagged with the git commit SHA. Base images use pinned versions, not rolling tags. | Block merge |
| 6 | **No secrets in build output** | Grep the build output and Docker image layers for patterns matching private keys, API key formats (`sk-...`, `AKIA...`), and known secret prefixes. | Block merge and alert |
| 7 | **Dependency count change flagged** | If the total number of resolved dependencies changes by more than 5, flag the PR for additional review. | Require lead approval |
| 8 | **Blocklist check** | Verify no blocklisted package appears in the resolved dependency tree. | Block merge |
| 9 | **Test suite passes** | `npm run test:all` (backend) and equivalent frontend tests exit 0. | Block merge |
| 10 | **Node.js version matches production** | CI build runs on the same Node.js major.minor version as the production Docker image. | Block merge |
### Example CI configuration (Gitea Actions)
```yaml
# .gitea/workflows/security-check.yml
name: Supply-Chain Security Check
on: [push, pull_request]
jobs:
supply-chain:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22.x.y (pin to production version)
uses: actions/setup-node@v4
with:
node-version: '22.x.y'
cache: 'npm'
- name: Install dependencies (frozen)
run: npm ci
# If this fails, lockfile is out of sync. Block merge.
- name: Audit for vulnerabilities
run: npm audit --audit-level=high
# Fails if any High or Critical vulnerability exists.
- name: Check blocklist
run: |
while IFS= read -r pkg; do
if npm ls "$pkg" 2>/dev/null | grep -q "$pkg"; then
echo "BLOCKED: $pkg found in dependency tree"
exit 1
fi
done < BLOCKLIST.md
- name: Scan for secrets in build output
run: |
npm run build 2>&1 | tee build.log
grep -iE '(sk-[a-zA-Z0-9]{20,}|AKIA[A-Z0-9]{16}|-----BEGIN (RSA |EC )?PRIVATE KEY-----)' build.log && exit 1 || true
- name: Flag dependency count change
run: |
CURRENT=$(npm ls --all 2>/dev/null | wc -l)
echo "Resolved dependency count: $CURRENT"
# Compare with main branch baseline if available
- name: Run tests
run: npm run test:all
```
### Deployment gate
In addition to the CI checks, the deployment process must enforce:
1. **No direct deployment from `latest` tag.** Production must deploy from a versioned tag (e.g., `v2.6.3`) or a SHA-pinned image.
2. **Staging deployment before production.** Every production deployment must first succeed in staging with passing health checks.
3. **Rollback capability.** The previous production image must be retained and redeployable within 15 minutes.
---
## Appendix A: References
| Document | Relationship |
|---|---|
| [[Backend Stack Security and Refactor Assessment - 2026-05-24]] | Source audit identifying supply-chain risks and recommending this policy |
| [[Platform Logical Audit - 2026-05-24]] | Cross-cutting audit with security findings |
| [[Security Architecture]] | Authentication, authorization, transport security, and secrets management |
| [[Backend Architecture]] | Backend module structure, middleware chain, and dependency map |
| [[Frontend Architecture]] | Frontend module structure, state management, and build process |
| [[Infrastructure]] | Docker configuration, Watchtower, and deployment topology |
| [[Environment Variables]] | Complete catalog of all environment variables including secrets |
## Appendix B: External references
| Advisory | URL |
|---|---|
| Express security release (2026-02-27) | https://expressjs.com/2026/02/27/security-releases.html |
| Node.js March 2026 security releases | https://nodejs.org/en/blog/vulnerability/march-2026-security-releases |
| Microsoft: Axios npm supply-chain compromise | https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/ |
| TanStack npm supply-chain compromise postmortem | https://tanstack.com/blog/npm-supply-chain-compromise-postmortem |
## Appendix C: Policy revision history
| Date | Version | Author | Description |
|---|---|---|---|
| 2026-05-24 | 1.0 | Security assessment | Initial policy created from supply-chain risk assessment |

View File

@@ -0,0 +1,405 @@
---
title: Security Audit — 2026-05-24
tags: [audit, security, findings]
created: 2026-05-24
status: open
---
# Security Audit — 2026-05-24
Full-codebase security review triggered by the completion of Telegram first-class auth, Request Network payment integration, and rate-limiting enablement. Every finding below was verified against actual source code — no hypothetical issues are included.
> [!danger] Action required
> 6 CRITICAL findings exist. C-3 and C-4 are exploitable in any non-`development` environment right now and must be fixed before staging is accessible to external testers.
---
## CRITICAL
### C-1 — Hardcoded Admin Password as Fallback Default
**File:** `src/infrastructure/database/init-admin.ts:7`
**Status:** Open
```ts
const adminPassword = process.env.ADMIN_PASSWORD || 'Moji6364';
```
If `ADMIN_PASSWORD` is not set, the admin account is seeded with a password that is now committed to version control. Combined with the predictable default email (`admin@marketplace.com`), the admin account is trivially discoverable.
**Remediation:**
- Remove the `|| 'Moji6364'` fallback entirely.
- Add a startup assertion: `if (!process.env.ADMIN_PASSWORD) throw new Error('ADMIN_PASSWORD is required')`.
- Rotate the credential immediately on any environment where the default may have been used.
---
### C-2 — Admin Password Logged to Stdout on Every Fresh Deploy
**File:** `src/infrastructure/database/init-admin.ts:50`
**Status:** Open
```ts
console.log(`🔑 Password: ${adminPassword}`);
```
The raw admin password (env-supplied or hardcoded default) is written to stdout on every seeding run. Container log aggregators, CloudWatch, Sentry breadcrumbs, and anyone with log-viewer access will have the credential.
**Remediation:** Delete line 50 entirely. Log only `"Admin user created successfully"` — never the credential value.
---
### C-3 — Simulated Transaction Bypass Active in Production
**File:** `src/services/payment/paymentRoutes.ts:379396`
**Status:** Open
```ts
if (paymentHash.startsWith('SIM_') ||
(paymentHash.startsWith('0x') && paymentHash.length < 64)) {
isVerified = true;
...
}
```
No environment guard. Any client that sends `paymentHash: "SIM_anything"` in production gets `isVerified = true`, causing a real `Payment` record to be created with `status: 'completed'` and `PurchaseRequest` to advance to `processing` — without any actual funds.
**Remediation:**
```ts
if (process.env.NODE_ENV !== 'production') {
// SIM_ test path
}
```
Or gate on a separate `ENABLE_PAYMENT_SIMULATION=true` env flag that is absent from production env files.
---
### C-4 — `forceVerifyUser` Gate Uses Wrong Condition
**File:** `src/services/auth/authController.ts:11271159` and `src/services/auth/authRoutes.ts:60`
**Status:** Open
```ts
if (process.env.NODE_ENV !== "development") {
return ResponseHandler.forbidden(res, "...");
}
```
`undefined !== "development"` is `true`, so when `NODE_ENV` is unset (common in CI, some staging configs) the guard **passes** and any unauthenticated caller can verify any user's email instantly, bypassing the entire verification flow.
**Remediation:**
- Change condition to `process.env.NODE_ENV === "development"` (allowlist, not denylist).
- Require an admin JWT on the route regardless of env.
- Consider removing the route from `authRoutes.ts` and only mounting it conditionally at the app level.
---
### C-5 — Hardcoded SHKeeper Admin Credential in Source
**File:** `src/services/payment/shkeeper/shkeeperPayoutService.ts:224228`
**Status:** Open
```ts
'Authorization': `Basic ${Buffer.from(`admin:!NMI4WdGkVQ#dQ`).toString('base64')}`
```
A plaintext password is committed to version-controlled source. It will be included in every build artifact, Docker image layer, and any repository fork or export.
**Remediation:**
- Replace with `process.env.SHKEEPER_ADMIN_PASSWORD`.
- Add to required-env-vars startup check.
- Rotate the credential immediately.
---
### C-6 — Access Token and Refresh Token Share the Same Signing Secret
**File:** `src/services/auth/authService.ts:18,54`
**Status:** Open
Both `generateToken` (access) and `generateRefreshToken` (refresh) use `this.JWT_SECRET`. The only distinction between token types is the `type: 'refresh'` payload claim — a field under caller control. Algorithm-confusion or secret-leakage attacks can forge long-lived refresh tokens using the access token secret.
**Remediation:**
- Introduce `REFRESH_TOKEN_SECRET` env var (separate value, ≥32 chars).
- Sign refresh tokens with it; verify refresh tokens only against it.
- This fully segregates the two token families.
---
## HIGH
### H-1 — Telegram Replay Map Lost on Server Restart (In-Memory Only)
**File:** `src/services/telegram/telegramService.ts:395407`
**Status:** Open
```ts
const initDataReplayWindow = new Map<string, number>();
```
`initData` replay protection, the webhook replay window, and the Request Network delivery ID deduplication are all stored in process-local `Map` objects. Any process restart, pod restart, or scale-out creates a fresh empty map. A captured `initData` is replayable immediately after any restart, within the configured max-age window (up to 24 hours).
**Remediation:** Replace with Redis `SET NX EX` pattern:
```ts
const key = `replay:telegram:${fingerprint}`;
const inserted = await redis.set(key, '1', 'NX', 'EX', windowSeconds);
if (!inserted) throw new ReplayError();
```
---
### H-2 — SHKeeper Webhook Authentication Has Spoofable Bypass
**File:** `src/services/payment/shkeeper/shkeeperWebhook.ts:95103`
**Status:** Open
```ts
const isShkeeperWebhook =
req.headers['x-shkeeper-api-key'] ||
req.headers['user-agent']?.includes('python-requests') ||
payload.crypto === 'BNB-USDT';
```
Any caller who sets `User-Agent: python-requests/2.x.x` or includes `"crypto": "BNB-USDT"` in the body can inject arbitrary payment completion events. The `x-shkeeper-api-key` header value is not validated — its presence alone is sufficient.
Additionally, the HMAC failure branch only rejects in `production` environments (`!== 'development'`), meaning unset `NODE_ENV` silently ignores invalid signatures.
**Remediation:**
- Remove the heuristic `isShkeeperWebhook` fallback entirely.
- Require `SHKEEPER_WEBHOOK_SECRET` at startup; fail if absent.
- Change environment guard from `!== 'development'` to `=== 'development'` (or always enforce).
---
### H-3 — Request Network `allowTestMode: true` Hardcoded in Production Route
**File:** `src/services/payment/requestNetwork/requestNetworkRoutes.ts:104105` and `signature.ts:5153`
**Status:** Open
```ts
allowTestMode: true,
// in signature.ts:
if (allowTestMode && isTestHeader(headers, testHeader)) {
return true; // skip verification
}
```
Any request bearing `x-request-network-test: 1` (or `true` / `yes`) completely bypasses HMAC signature verification. The flag is unconditional — there is no environment check.
**Remediation:**
```ts
allowTestMode: process.env.NODE_ENV !== 'production',
```
Or introduce an explicit `ENABLE_RN_TEST_MODE=true` env flag absent from production.
---
### H-4 — Typing Indicator Socket Events Have No Chat Membership Check (IDOR)
**File:** `src/app.ts:267291`
**Status:** Open
`typing-start` and `typing-stop` handlers verify only `data.userId === userId` (the caller is who they claim to be) but do not verify the caller is a participant of `data.chatId`. Any authenticated user who knows a chat ID can broadcast typing presence to that room — information disclosure.
**Remediation:** Check membership before broadcasting:
```ts
const chat = await Chat.findById(data.chatId, { participants: 1 }).lean();
const isMember = chat?.participants.some(p => p.userId.equals(userId));
if (!isMember) return;
```
Or maintain a per-socket set of joined room IDs and validate against it without a DB round-trip.
---
### H-5 — Financial Events Broadcast to All Connected Sockets
**File:** `src/services/payment/shkeeper/shkeeperWebhook.ts:546,560`
**Status:** Open
```ts
global.io?.emit('seller-offer-update', { sellerId: ..., paymentId: ..., transactionHash: ... });
```
`io.emit()` sends to every connected socket. Payment completion events including `paymentId`, `transactionHash`, and `offerId` are sent to all users — any client can observe other users' financial events.
**Remediation:**
```ts
global.io?.to(`seller-${selectedOffer.sellerId}`).emit('seller-offer-update', { ... });
global.io?.to(`buyer-${payment.buyerId}`).emit('payment-completed', { ... });
```
---
## MEDIUM
### M-1 — OTP and Reset Codes Logged in Plaintext (All Environments)
**File:** `src/services/auth/authController.ts:174,203,715,757`
**Status:** Open
```ts
console.log(`🔢 Generated verification code for ${email}: ${emailVerificationCode}`);
console.log(`🔢 Generated password reset code for ${email}: ${resetCode}`);
```
6-digit OTPs and password reset codes are written to stdout with no environment guard. Anyone with log access can take over any unverified account or reset any password.
**Remediation:** Delete all four log lines. Never log secrets or codes in any environment.
---
### M-2 — `Math.random()` Used for OTP Generation (Not a CSPRNG)
**File:** `src/services/auth/authService.ts:226`
**Status:** Open
```ts
return Math.floor(100000 + Math.random() * 900000).toString();
```
`Math.random()` is not cryptographically secure. Replace with:
```ts
const bytes = crypto.randomBytes(3);
return (100000 + (bytes.readUIntBE(0, 3) % 900000)).toString();
```
---
### M-3 — Refresh Token Rotation Has No Theft Detection
**File:** `src/services/auth/authController.ts:510529`
**Status:** Open
Token rotation works (old is removed, new is issued). However, if an attacker steals a refresh token and rotates it first, the legitimate user's subsequent refresh gets `403` but the attacker's session continues. There is no "token already rotated" detection that would invalidate all sessions for that user.
**Remediation:** Implement refresh token families. If a token that has already been rotated is presented again, treat it as a theft signal and set `user.refreshTokens = []` (force re-login everywhere).
---
### M-4 — Profile Update Uses `validateBeforeSave: false` with Full Object Spread
**File:** `src/services/auth/authController.ts:921938`
**Status:** Open
```ts
user.profile = { ...user.profile, ...profile };
user.preferences = { ...user.preferences, ...preferences };
await user.save({ validateBeforeSave: false });
```
The entire `profile` and `preferences` objects from the request body are spread without an explicit allowlist. `validateBeforeSave: false` additionally skips schema-level guards. A user could inject unexpected fields into their document.
**Remediation:** Use an explicit pick:
```ts
const allowedProfile = pick(profile, ['phone', 'bio', 'website']);
user.profile = { ...user.profile, ...allowedProfile };
```
Remove `{ validateBeforeSave: false }`.
---
### M-5 — Telegram Login Widget Path Has No Replay Protection
**File:** `src/services/auth/authController.ts:110116`
**Status:** Open
The Mini App flow correctly calls `checkMiniAppReplay` / `rememberMiniAppInitData`. The Login Widget flow (the `else` branch) calls only `verifyTelegramLoginWidget` — no replay check. The Login Widget payload contains a static `hash` valid for up to `miniAppMaxAgeMs` (up to 24 hours). An intercepted payload can be replayed freely within that window.
**Remediation:** Apply the same replay-map (or Redis key) logic to the Login Widget path, keying on `loginWidget.hash`.
---
### M-6 — No JWT Secret Strength Enforcement at Startup
**File:** `src/shared/config/index.ts:42`
**Status:** Open
`JWT_SECRET` is read with `process.env.JWT_SECRET!`. An empty string, `"secret"`, or `"changeme"` is silently accepted, producing trivially forgeable JWTs.
**Remediation:** Add to startup checks:
```ts
if (config.jwtSecret.length < 32) throw new Error('JWT_SECRET must be at least 32 characters');
```
---
### M-7 — Legacy `verifyEmail` Token Route Has No Expiry Check
**File:** `src/services/auth/authController.ts:667692`
**Status:** Open
The code-based flow (`/verify-email-code`) enforces a 15-minute expiry. The legacy URL-based flow (`GET /verify-email/:token`) queries only by token value, with no `emailVerificationTokenExpires` check. If the token field is persisted without an expiry date, it never becomes invalid.
**Remediation:** Either add `emailVerificationTokenExpires: { $gt: new Date() }` to the query, or deprecate and remove this route if the code-based flow has fully replaced it.
---
## LOW
### L-1 — Passkey Challenge Debug Logs Expose All Active Challenges
**File:** `src/services/auth/passkeyService.ts:128130,207212`
**Status:** Open
```ts
console.log('🔍 Available challenges:', Array.from(this.storedChallenges.keys()));
// ...
allUsers.forEach(u => { console.log(`User ${u.email}:`, u.passkeys.map(pk => pk.id)); });
```
On any failed passkey assertion, every registered user's email and all their passkey IDs are dumped to logs. An attacker who triggers many failed assertions can enumerate the passkey corpus from log infrastructure.
**Remediation:** Remove both blocks entirely. Log only the failed assertion's credential ID.
---
### L-2 — In-Memory Login Attempt Counters Not Shared Across Replicas
**File:** `src/services/auth/authService.ts:112`
**Status:** Open
`authAttempts: Map<string, ...>` is process-local. In a multi-replica deployment, an attacker distributing login attempts across replicas bypasses per-user lockout.
**Remediation:** Move to Redis with TTL-expiring keys (same pattern as the rate-limiter Redis adapter that is already planned).
---
### L-3 — CORS Origin: Unset `FRONTEND_URL` Allows All Origins
**File:** `src/app.ts:332`
**Status:** Open
`cors({ origin: process.env.FRONTEND_URL })` — if `FRONTEND_URL` is unset, `cors` treats `undefined` as "allow all origins."
**Remediation:** Add a startup assertion that `FRONTEND_URL` is a non-empty string.
---
### L-4 — Auth Rate-Limit Counters Are In-Memory (Multi-Replica Gap)
**File:** `src/app.ts` (rate-limit middleware configuration)
**Status:** Open
Same class as L-2. The rate-limit counters are in-memory. Distributing requests across replicas bypasses per-IP limits. A Redis store adapter is already planned.
---
## Confirmed PASS (Verified Correctly Handled)
| Check | Result | Source |
|-------|--------|--------|
| HMAC timing-safe comparison (SHKeeper + RN) | PASS | `shkeeperWebhook.ts:84`, `signature.ts:74` |
| Telegram HMAC derivation (Mini App + Login Widget) | PASS | `telegramService.ts:223-278` |
| Bot account rejection | PASS | `telegramService.ts:278,353` |
| Blocked Telegram user check (`status: 'blocked'`) | PASS | `authController.ts:355-363` |
| Refresh token rotation (old removed before new issued) | PASS | `authController.ts:527-529` |
| Password change clears all refresh tokens | PASS | `authController.ts:887` |
| Password reset clears all refresh tokens | PASS | `authController.ts:797,846` |
| Socket.IO JWT enforcement on connect | PASS | `app.ts:76-94` |
| `join-user-room` IDOR prevention | PASS | `app.ts:114` |
| `join-chat-room` membership check | PASS | `app.ts:241-247` |
| bcrypt work factor = 12 | PASS | `authService.ts` |
| WebAuthn challenge consumed on first use | PASS | `passkeyService.ts:87` |
| Telegram `auth_date` freshness enforcement | PASS | `telegramService.ts:208-223` |
---
## Summary
| Severity | Count |
|----------|-------|
| CRITICAL | 6 |
| HIGH | 5 |
| MEDIUM | 7 |
| LOW | 4 |
**Immediate priority:** C-3 (simulation bypass) and C-4 (forceVerify gate) are one-line fixes and are exploitable today. C-1, C-2, C-5 (hardcoded/logged credentials) must be resolved and rotated before any external access to staging or production.
---
## Related
- [[Security Architecture]]
- [[Authentication Flow]]
- [[Webhook Security Spec]]
- [[Threat Model - Amanat Escrow Platform]]
- [[Logic Audit - 2026-05-24]]
- [[Performance Audit - 2026-05-24]]

View File

@@ -0,0 +1,244 @@
---
title: Security Ownership and Launch Decision Criteria
tags: [audit, security, governance, launch, raci]
created: 2026-05-24
status: decision
---
# Security Ownership and Launch Decision Criteria
**Decision document.** Answers open questions 9 and 10 from [[Backend Stack Security and Refactor Assessment - 2026-05-24]]: who owns security decisions, and what must be true before public launch.
This document is binding for the Amanat platform launch cycle. Changes require written sign-off from the roles listed in Section 1.
---
## 1. Security Ownership RACI
Roles: **PO** = Product Owner, **BL** = Backend Lead, **DI** = DevOps/Infra, **FL** = Frontend Lead, **SO** = Security Owner (if designated), **CTO** = CTO/Leadership.
R = Responsible (does the work), A = Accountable (final decision authority), C = Consulted, I = Informed.
| Decision Area | PO | BL | DI | FL | SO | CTO |
|---|---|---|---|---|---|---|
| Authentication changes (token storage, session model, passkey scope) | I | R | C | C | A | I |
| Payment/funds changes (ledger, state machine, release/refund logic) | C | R | I | I | A | I |
| Provider integrations (SHKeeper, Request Network, new providers) | C | R | C | I | A | I |
| Webhook handling (signature verification, idempotency, DLQ) | I | R | C | I | A | I |
| Rate limiting (tiers, thresholds, enforcement points) | I | R | A | I | C | I |
| Admin access (role definitions, step-up auth, audit logging) | C | R | I | C | C | A |
| Dependency updates (lockfile policy, provenance, vulnerability triage) | I | R | A | C | C | I |
| Incident response (runbook ownership, escalation, postmortem) | I | C | R | I | A | I |
| Cross-cutting security architecture (service split, stack migration) | C | R | C | C | C | A |
| External penetration testing (scope, timing, vendor selection) | I | C | C | I | R | A |
### RACI rules
- If no Security Owner is designated, accountability for rows marked **SO** defaults to **CTO**.
- **BL** is responsible for all implementation work on backend security items. **FL** is responsible for frontend-side changes (cookie migration, CSP hardening, token storage) and is consulted on rows that affect the frontend.
- **DI** owns rate limiting configuration, dependency pipeline, and infrastructure-level controls.
- A role marked **A** must approve in writing (PR review, doc sign-off, or Slack confirmation logged in the decision register) before the change ships.
- Any role marked **R** or **A** can escalate to **CTO** for final arbitration.
---
## 2. Launch Safety Gate Checklist
Each item is classified as:
- **Required** -- blocks launch. Must be verified complete before any public-facing deployment.
- **Strongly Recommended** -- should block launch. Can be accepted with a documented risk entry (risk description, owner, remediation deadline) signed by the accountable role from Section 1.
- **Deferred** -- explicitly deferred to post-launch. Must appear in Section 5 (Deferred Decisions Register).
### 2.1 Authentication and Session Hardening
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.1.1 | All financial endpoints require Bearer JWT authentication | Required | [[Platform Logical Audit - 2026-05-24]] item 3 |
| 2.1.2 | Ownership checks enforced on all `:userId` parameterized endpoints | Required | [[Platform Logical Audit - 2026-05-24]] item 3 |
| 2.1.3 | Admin role checks enforced on all admin routes | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
| 2.1.4 | Test/demo payment and email endpoints disabled or auth-protected in production | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
| 2.1.5 | Access token lifetime reduced to 60 minutes or less | Strongly Recommended | [[Platform Logical Audit - 2026-05-24]] item 10 |
| 2.1.6 | Refresh tokens moved to `httpOnly` cookies (or risk accepted with documented rationale) | Strongly Recommended | [[Security Architecture]] section 11 |
| 2.1.7 | Passkey/WebAuthn disabled in production until real cryptographic implementation is complete | Required | [[Platform Logical Audit - 2026-05-24]] item 2 |
| 2.1.8 | Passkey RP ID set to production domain (not `localhost`) | Required | [[Security Architecture]] section 2.3 |
| 2.1.9 | Device/session revocation functional | Deferred | Post-launch auth hardening |
### 2.2 Payment and Funds Integrity
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.2.1 | Dispute creation enforces escrow hold (`disputed` state) that blocks release and refund | Required | [[Platform Logical Audit - 2026-05-24]] item 1 |
| 2.2.2 | Web3 verification decodes Transfer event and validates recipient, token contract, and amount | Required | [[Platform Logical Audit - 2026-05-24]] item 4 |
| 2.2.3 | Payment mutations route through centralized service methods only (no direct controller mutation) | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
| 2.2.4 | Release/refund eligibility enforced through escrow state, not controller-level flags | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] payment risks |
| 2.2.5 | Seller cannot update offer price after acceptance | Strongly Recommended | [[Platform Logical Audit - 2026-05-24]] item 18 |
| 2.2.6 | Immutable funds ledger operational for new payments | Deferred | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 2 |
| 2.2.7 | Provider-neutral payment abstraction layer | Deferred | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 2 |
| 2.2.8 | Payment state enums unified across data model, API, and flow documents | Required | [[Platform Logical Audit - 2026-05-24]] item 9 |
### 2.3 Authorization Enforcement
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.3.1 | Every endpoint mapped to required role (public, authenticated, owner, admin) in authorization matrix | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] doc requirement 4 |
| 2.3.2 | `assertRole` or equivalent guard present in all admin and payment service methods | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
| 2.3.3 | Arbitrary `userId` from client no longer accepted for private data; server derives identity from JWT | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
### 2.4 Rate Limiting
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.4.1 | Global rate limiting enabled | Required | [[Platform Logical Audit - 2026-05-24]] item 13 |
| 2.4.2 | Auth endpoints: 5 req/5 min/IP | Required | [[Security Architecture]] section 9 |
| 2.4.3 | Payment endpoints: 20 req/15 min/IP | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
| 2.4.4 | AI endpoints: 10 req/15 min/authenticated-user | Required | [[Platform Logical Audit - 2026-05-24]] item 3 |
| 2.4.5 | File upload endpoints: 10 req/15 min/authenticated-user | Strongly Recommended | -- |
| 2.4.6 | Delivery confirmation code: max 5 verification attempts per 15 min per request | Required | [[Platform Logical Audit - 2026-05-24]] item 8 |
### 2.5 Webhook Security
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.5.1 | SHKeeper webhook uses raw-body HMAC verification (not reconstructed JSON) | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] webhook risks |
| 2.5.2 | Webhook handler is idempotent (duplicate delivery = no-op) | Required | [[Security Architecture]] section 5 |
| 2.5.3 | Webhook returns proper HTTP codes: 400 for bad input, 500 for server error, 200 for success | Required | [[Platform Logical Audit - 2026-05-24]] item 11 |
| 2.5.4 | Webhook failures logged to dead-letter storage or alerting channel | Strongly Recommended | [[Platform Logical Audit - 2026-05-24]] item 11 |
| 2.5.5 | Provider callbacks create reconciliation events; do not directly release funds | Strongly Recommended | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] webhook risks |
### 2.6 Socket.IO Authorization
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.6.1 | Socket.IO room membership derived from authenticated socket identity, not client-supplied user IDs | Required | [[Platform Logical Audit - 2026-05-24]] item 12 |
| 2.6.2 | Socket handshake requires valid JWT | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] realtime risks |
### 2.7 Supply-Chain Controls
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.7.1 | Lockfile reviewed and updated for known vulnerable packages (Multer <2.1.0, Axios compromise, TanStack compromise) | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] supply-chain risks |
| 2.7.2 | `npm audit` / `yarn audit` run and all high/critical CVEs triaged | Required | [[Security Architecture]] section 12 |
| 2.7.3 | CI install mode uses frozen lockfile | Strongly Recommended | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] doc requirement 10 |
| 2.7.4 | No test/demo routes in production builds | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
### 2.8 Monitoring, Alerting, and Runbooks
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.8.1 | Backend error monitoring active (Sentry or equivalent with source maps) | Strongly Recommended | [[Security Architecture]] section 12 |
| 2.8.2 | Structured logging for payment state transitions (actor, target, before/after) | Strongly Recommended | [[Security Architecture]] section 10 |
| 2.8.3 | Runbook exists for: failed webhook, duplicate payment, stuck release, compromised admin, leaked API key | Strongly Recommended | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] doc requirement 11 |
| 2.8.4 | Alerting for: repeated webhook signature failures, unusual payment volume, admin actions on own disputes | Strongly Recommended | -- |
### 2.9 External Penetration Testing
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.9.1 | External pentest of payment + dispute + auth flows completed before general public launch | Strongly Recommended | [[Security Architecture]] section 12, Open question 9 |
| 2.9.2 | Pentest findings triaged; all critical/high items resolved or risk-accepted before launch | Required (if pentest performed) | -- |
### 2.10 Infrastructure and Operations
| # | Condition | Classification | Source |
|---|---|---|---|
| 2.10.1 | All dev-seeded credentials rotated | Required | [[Security Architecture]] section 12 |
| 2.10.2 | `NODE_ENV=production` confirmed in production backend | Required | [[Security Architecture]] section 12 |
| 2.10.3 | `NEXT_PUBLIC_IS_DEVELOPMENT` and `ENABLE_DEBUG` disabled in production | Required | [[Security Architecture]] section 12 |
| 2.10.4 | Production Watchtower pinned to versioned tag (not `latest`) | Strongly Recommended | [[Platform Logical Audit - 2026-05-24]] item 27 |
| 2.10.5 | Committed or publicly visible secrets rotated | Required | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 0 |
---
## 3. Launch Priority Decision
**Decision: launch prioritizes immediate hardening of the current Node/Express stack. Backend-core redesign is deferred to post-launch.**
### Rationale
The audit findings in [[Backend Stack Security and Refactor Assessment - 2026-05-24]] and [[Platform Logical Audit - 2026-05-24]] identify the dominant risks as domain-level security failures, not framework-level weaknesses:
1. **The most dangerous issues are authorization and state-machine bugs**, not Node/Express itself. Unauthenticated financial endpoints, client-controlled socket room membership, missing dispute-escrow holds, and broken Web3 verification are independent of the backend language.
2. **A rewrite does not fix the core problems.** Moving to Go or Kotlin without first specifying the funds ledger, escrow state machine, and authorization matrix would transplant the same logic gaps into a new codebase. The audit explicitly states: "the larger issue is that the current backend mixes high-risk financial state transitions... in one Express application" -- but a rewrite that does not first solve the domain model problem is wasted effort.
3. **Hardening is faster.** The Phase 0 actions from [[Backend Stack Security and Refactor Assessment - 2026-05-24]] (disable unsafe routes, add auth checks, enable rate limiting, fix Web3 verification, fix Socket.IO auth) are discrete, testable tasks that can be completed in days, not months.
4. **The rewrite carries re-introduction risk.** The product has working business flows. A full or partial rewrite risks reintroducing escrow and payment bugs that have already been found and can be fixed in place.
### Concrete launch sequence
| Phase | Work | Timeline |
|---|---|---|
| **Phase 0: Containment** | Complete all Required items from Section 2 checklist. Disable unsafe routes, add auth/ownership enforcement, enable rate limiting, fix dispute-escrow hold, fix Web3 verification, fix Socket.IO auth, disable passkeys, rotate secrets. | Immediate |
| **Phase 1: Documentation** | Produce the 11 required documents listed in [[Backend Stack Security and Refactor Assessment - 2026-05-24]] (threat model, funds ledger spec, escrow state machine, authorization matrix, payment provider adapter spec, webhook security spec, session/auth architecture, realtime auth spec, migration plan, supply-chain policy, operational runbooks). | Parallel with Phase 0 |
| **Phase 2: Controlled launch** | Public launch proceeds once all Required checklist items pass verification. Strongly Recommended items are either completed or have documented risk acceptances. | After Phase 0 |
| **Phase 3: Payment/ledger extraction** | Build provider-neutral payment layer and immutable ledger. This is the first post-launch engineering priority. | Post-launch |
| **Phase 4: Core migration evaluation** | Decide on Go/Kotlin backend-core rewrite based on team capacity, Phase 3 outcomes, and operational experience. No migration begins until Phase 3 is stable. | Post-launch, after Phase 3 |
---
## 4. External Penetration Testing Decision
**Decision: yes, commission an external penetration test before general public launch.**
### Rationale
- Amanat is a financial escrow platform handling crypto payments. The attack surface includes webhook processing, payment state machines, Web3 transaction verification, and fund release flows. This is materially different from a typical web application.
- The audit identified critical findings (unauthenticated financial endpoints, Web3 verification bypass, dispute-escrow race condition) that an external tester would also find. An external pentest validates that the Phase 0 hardening actually closed these gaps.
- Supply-chain compromise evidence from 2026 (Axios, TanStack, Express Multer) demonstrates active threat against the npm ecosystem the platform depends on.
### Timeline and scope
| Attribute | Value |
|---|---|
| **When** | After Phase 0 hardening is complete, before Phase 2 public launch |
| **Scope** | Payment flows (SHKeeper pay-in, Web3 verification, payout/release/refund), dispute/escrow state transitions, authentication (login, token refresh, OAuth, session management), admin operations, webhook handling, Socket.IO authorization |
| **Out of scope** | Marketplace browsing/listing, blog, points/leaderboard, file upload (assessed via code review instead) |
| **Depth** | Black-box or grey-box at tester's discretion, with access to API documentation and a funded test environment |
| **Deliverable** | Report with severity ratings, reproduction steps, and remediation recommendations. Findings mapped to checklist items in Section 2. |
| **Gate** | All critical and high findings must be resolved or risk-accepted (with CTO sign-off) before launch proceeds |
### If pentest is delayed or unavailable
If the external pentest cannot be scheduled before the desired launch date, the following compensating controls must be in place:
1. Complete internal code review of all payment, auth, and webhook code paths by someone other than the original author.
2. Automated security test suite covering: unauthenticated access denial on all financial endpoints, webhook signature rejection, dispute-escrow hold enforcement, Web3 verification with wrong recipient/amount, Socket.IO unauthorized room join.
3. Documented risk acceptance signed by CTO acknowledging that external validation was not performed.
---
## 5. Deferred Decisions Register
Every item deferred from the launch checklist is recorded here with an owner, risk statement, and decision deadline.
| # | Decision | Risk | Owner | Decision Deadline |
|---|---|---|---|---|
| D-1 | Move access/refresh tokens from `localStorage` to `httpOnly` cookies | XSS in any frontend dependency or user-generated content leads to full session hijack. Access token at 60 min expiry limits window, but refresh token at 30 days is high value. | SO (or BL if no SO) | Within 30 days post-launch |
| D-2 | Implement immutable funds ledger for new payments | Without a ledger, payment state is mutable and auditable only through application logs. Reconciliation depends on provider records. Overpayments, partial refunds, and fee calculations have no single source of truth. | BL | Phase 3 start (within 60 days post-launch) |
| D-3 | Build provider-neutral payment abstraction layer | Current SHKeeper coupling means changing providers requires modifying core business logic. Provider-specific metadata may become canonical state by accident. | BL | Phase 3 start (within 60 days post-launch) |
| D-4 | Implement real WebAuthn/passkey authentication | Passkeys remain disabled. Users limited to password + OAuth. No phishing-resistant second factor available. | BL | Within 90 days post-launch |
| D-5 | Device and session revocation | Users cannot revoke individual sessions. Compromised refresh token remains valid until natural expiry or password change. | BL | Within 60 days post-launch |
| D-6 | Admin step-up authentication for payouts and role changes | Admin with compromised session can approve payouts or escalate roles without additional verification. | CTO | Before platform processes real funds at volume |
| D-7 | Production staging pipeline (replace Watchtower auto-deploy on `latest`) | Unvalidated images promoted to production. No health check gate, no rollback automation. | DI | Within 30 days post-launch |
| D-8 | Frontend Docker image runtime configuration injection | Same image cannot be promoted across environments without rebuild. Increases risk of configuration drift or misbuilt production images. | FL | Within 45 days post-launch |
| D-9 | Webhook dead-letter queue and structured failure alerting | Failed webhooks are silently swallowed. Reconciliation depends on manual monitoring or provider retry behavior. | BL | Within 30 days post-launch |
| D-10 | Backend-core stack migration decision (Go, Kotlin, or remain TypeScript) | Continued npm supply-chain exposure for payment core. Express flexibility allows route-level exceptions to accumulate. Decision delayed until payment layer is stable and team capacity is assessed. | CTO | After Phase 3 stability milestone (target: 120 days post-launch) |
| D-11 | Append-only audit log for payment/payout/role-change operations | Payment actions are logged via ad-hoc logger calls, not a tamper-evident audit trail. Required for dispute resolution and regulatory confidence. | BL | Within 45 days post-launch |
| D-12 | ClamAV or equivalent virus scanning on user-uploaded files | Uploaded dispute evidence and attachments served to other users without content scanning. | DI | Within 60 days post-launch |
### Governance
- The accountable owner for each deferred item is responsible for tracking progress and raising blockers.
- Items past their decision deadline without resolution escalate to CTO.
- This register is reviewed at each engineering standup or weekly review until all items are resolved or reassigned.
---
## Cross-references
- [[Backend Stack Security and Refactor Assessment - 2026-05-24]] -- primary audit, open questions 9 and 10
- [[Platform Logical Audit - 2026-05-24]] -- detailed findings referenced in checklist items
- [[Security Architecture]] -- current security architecture and pre-launch hardening checklist
- [[PRD - Platform Audit Remediation Plan (2026-05-24)]] -- tactical remediation plan (if available)

View File

@@ -0,0 +1,706 @@
---
title: Session and Authentication Architecture Decision
tags: [audit, security, adr, authentication, session, passkey, webauthn, admin, step-up]
created: 2026-05-24
status: decided
reviewers: [backend, security, frontend, cto]
---
# Session and Authentication Architecture Decision
**Architecture Decision Record.** This document resolves deferred decisions D-1, D-4, D-5, and D-6 from [[Security Ownership and Launch Decision Criteria]] and addresses threats T04, T10, T13, T15, and T22 from [[Threat Model - Amanat Escrow Platform]].
All decisions in this document are binding for the Amanat platform. Changes require sign-off from the accountable role per the RACI in [[Security Ownership and Launch Decision Criteria]] Section 1.
---
## Section 1: Decision Summary
| Decision | Chosen Option | Rejected Alternatives | Status |
|---|---|---|---|
| Access token storage | In-memory only (JavaScript variable), not persisted to any browser storage | localStorage (current), httpOnly cookie | Decided |
| Refresh token storage | httpOnly, Secure, SameSite=Strict cookie | localStorage (current), response body | Decided |
| Access token lifetime | 15 minutes | 7 days (current), 60 minutes, 30 minutes | Decided |
| Refresh token lifetime | 7 days with rotation on every use | 30 days (current), 14 days, 1 day | Decided |
| CSRF strategy | SameSite=Strict on refresh cookie; no CSRF token needed because access token is never in a cookie | Double-submit cookie, synchronizer token pattern | Decided |
| Passkey/WebAuthn | Option C: feature-flagged off in production; stubs remain for development | Remove entirely, implement production WebAuthn pre-launch | Decided |
| OAuth requirements | Authorization code with PKCE; same session model as email/password; implicit account linking by email | Implicit flow, separate session model for OAuth | Decided |
| Device/session revocation | user.refreshTokens[] with device metadata, revocation endpoints, max 5 sessions per user | No revocation (current), Redis-only session store | Decided |
| Admin step-up auth | Required for high-risk actions; 5-minute elevated session via re-authentication (password) | No step-up (current), hardware 2FA mandatory, passkey-only | Decided |
| Two-person approval | Required for payouts above $1,000 USD equivalent; second admin must confirm | Single-admin approval for all amounts, multi-sig wallet only | Decided |
---
## Section 2: Access Token Storage and Lifetime
### Decision
**We will store the access token in JavaScript memory only (a module-level variable or React context), not in localStorage, sessionStorage, IndexedDB, or any cookie.** The access token is never written to any persistent browser storage.
### Rejected alternatives
| Alternative | Rejection reason |
|---|---|
| **localStorage** (current state) | Fully accessible to any XSS payload. Threat T13: any script injection in any dependency, user-generated content field, or uploaded file exfiltrates the token. Threat T04: 7-day lifetime means a stolen token grants access for up to a week without triggering refresh rotation. |
| **httpOnly cookie** | Eliminates XSS theft but introduces CSRF risk on every API call. Requires CSRF token infrastructure for all state-changing endpoints. Adds CORS complexity because cookies are sent automatically by the browser on cross-origin requests if SameSite is not Strict. The access token must be sent on every API call, so the CSRF surface is every endpoint. |
### Rationale
In-memory storage is immune to XSS-based persistent theft. An attacker who achieves XSS can read the token during the lifetime of the page session, but the token evaporates on tab close, navigation away, or page refresh. Combined with a 15-minute expiry, the maximum exploitation window from any single XSS event is 15 minutes, not 7 days.
The access token is only needed for the duration of a browser session. The Axios interceptor holds the token in a closure variable. On page refresh, the refresh-token flow re-establishes the access token transparently.
### Token lifetime
**15 minutes** from issuance.
Rationale: Threat T04 identifies the 7-day access token lifetime as a critical risk. A stolen access token is usable for its entire lifetime without triggering refresh-token rotation. Reducing to 15 minutes limits the damage window to 15 minutes maximum. This is short enough that an attacker who exfiltrates the token via a transient XSS event (e.g., a reflected XSS that closes the page) loses access almost immediately, while long enough to avoid excessive refresh calls on a normal browsing session.
### Token format
JWT with HS256 signing (same algorithm as current). Claims:
| Claim | Value | Purpose |
|---|---|---|
| `sub` | User `_id` (ObjectId string) | Subject identification |
| `role` | `buyer`, `seller`, `admin`, `support` | Authorization enforcement |
| `iat` | Unix timestamp | Issued-at |
| `exp` | `iat + 900` (15 minutes) | Expiry enforcement |
| `jti` | UUID v4 | Token-specific identifier for audit trail and future revocation |
| `iss` | `marketplace-backend` | Issuer verification |
| `aud` | `marketplace-users` | Audience verification |
No change to the signing algorithm or secret management. The `jti` claim is new and required for audit logging and future deny-list capabilities.
### Renewal strategy
**Silent refresh with rotation.** The Axios response interceptor detects `401 TOKEN_INVALID` or `403` responses. It attempts a refresh using the httpOnly cookie (sent automatically by the browser). On success, the backend returns a new access token in the response body and sets a new refresh cookie. The interceptor updates the in-memory access token and retries the original request.
Concurrent requests during refresh: implement a refresh mutex (single inflight refresh promise). If multiple requests fail with 401 simultaneously, the first triggers the refresh; subsequent requests await the same promise and retry with the new token.
**Absolute expiry:** The refresh token has a maximum lifetime of 7 days (see Section 3). After 7 days, the user must re-authenticate. There is no sliding window that extends beyond 7 days.
### Token theft detection and response
1. **Refresh-token reuse detection** (already exists, remains in place): If a previously-used refresh token is presented, the backend invalidates all sessions for that user and forces re-authentication. The user receives an email notification: "Your session was terminated due to suspicious activity. Please sign in again."
2. **Access token theft**: Because the token is in-memory only, persistent theft requires a sustained XSS presence. The 15-minute window limits exposure. No server-side detection is possible for access-token theft alone (the token is valid until expiry). The primary defense is the short lifetime.
3. **Audit logging**: Every token issuance and refresh is logged with `jti`, `userId`, `ip`, `userAgent`, and timestamp. Abnormal patterns (e.g., refresh from a new IP geolocation far from the previous one) trigger an alert for investigation.
---
## Section 3: Refresh Token Storage, Rotation, and Revocation
### Storage location
**httpOnly, Secure, SameSite=Strict cookie** named `__Host-refresh-token`.
Cookie attributes:
| Attribute | Value | Rationale |
|---|---|---|
| `httpOnly` | `true` | Not accessible to JavaScript; immune to XSS theft |
| `secure` | `true` | Transmitted only over HTTPS |
| `sameSite` | `Strict` | Not sent on cross-origin requests; CSRF protection |
| `path` | `/api/auth/refresh-token` | Cookie sent only on the refresh endpoint, not on every API call |
| `domain` | (omitted; host-only) | Scoped to the exact origin |
| `maxAge` | `604800000` (7 days in ms) | Matches refresh token lifetime |
The `__Host-` prefix ensures the cookie cannot be set by a subdomain and requires `Secure`.
### Refresh token lifetime
**7 days** with rotation on every use.
Rationale: The current 30-day lifetime is excessive for a financial platform. Seven days provides a reasonable balance between user convenience (users are not forced to re-authenticate daily) and security (a compromised refresh token is only exploitable for 7 days maximum, and rotation reduces the window further). The deferred decision D-1 in [[Security Ownership and Launch Decision Criteria]] accepts that the migration from localStorage to httpOnly cookies must happen within 30 days post-launch. This ADR specifies the target state.
### Rotation strategy
On every refresh:
1. Backend receives the refresh token from the cookie.
2. Verifies the JWT signature and expiry.
3. Looks up the user and checks the token hash is present in `user.refreshTokens[]`.
4. Removes the old token from the array.
5. Issues a new access token (in response body) and a new refresh token (in `Set-Cookie` header).
6. Pushes the new refresh token hash to `user.refreshTokens[]`.
### Token reuse detection
If a previously-consumed refresh token is presented (i.e., the token was already rotated away and is no longer in `user.refreshTokens[]`):
1. Invalidate ALL sessions for that user: set `user.refreshTokens = []`.
2. Revoke all Redis session records for that user.
3. Send an email to the user: "A potential security issue was detected. All your sessions have been terminated. Please sign in again and change your password if you did not initiate this activity."
4. Log the event with both token hashes, the IP addresses of both the legitimate and reuse attempts, and timestamps.
This is the existing behavior documented in [[Authentication Flow]] and [[Security Architecture]] section 2.4. It remains unchanged.
### MongoDB storage schema
The `user.refreshTokens[]` array currently stores raw token strings. We will migrate to a subdocument array with metadata:
```typescript
refreshTokens: [{
tokenHash: String, // SHA-256 hash of the refresh token (not the raw token)
deviceInfo: String, // User-Agent string (truncated to 200 chars)
ipAddress: String, // IP at time of token issuance
createdAt: Date, // When the token was issued
lastUsedAt: Date, // Updated on each refresh
}]
```
The raw refresh token is never stored in MongoDB. Only its SHA-256 hash is stored. Verification compares `sha256(receivedToken) === stored.tokenHash`.
Migration: existing plain-string entries in `user.refreshTokens[]` are invalidated on the first refresh after deployment. Users with existing sessions are force-re-authenticated once.
### Revocation endpoints
**`POST /api/auth/revoke-session`**
Requires Bearer JWT. Body: `{ sessionTokenHash: string }` (the hash of the session to revoke, obtained from the session listing endpoint). The backend removes the matching entry from `user.refreshTokens[]` and deletes any Redis session keyed by tokens associated with that refresh token.
**`POST /api/auth/revoke-all-sessions`**
Requires Bearer JWT. Removes all entries from `user.refreshTokens[]` except the one used by the current request (identified by the refresh cookie). Deletes all Redis sessions for that user. The user remains logged in on the current device.
Both endpoints are audited: action, actor, target session hash, timestamp, IP, user-agent.
---
## Section 4: CSRF Strategy
### Current state
JWT is sent in the `Authorization: Bearer` header. Browsers do not attach `Authorization` headers on cross-origin requests. CSRF is currently mitigated by design (Threat T15: "Mitigated").
### Decision: no CSRF token needed for access tokens
Because the access token is stored in JavaScript memory and sent via the `Authorization` header, CSRF is not a concern for the majority of API calls. The browser will not include the `Authorization` header on a forged cross-origin request.
### CSRF protection for the refresh endpoint
The refresh token is stored in an httpOnly cookie. However, the cookie attributes provide CSRF protection:
- **`SameSite=Strict`**: The cookie is not sent on any cross-origin request, including top-level navigations from external sites. This eliminates CSRF on the refresh endpoint.
- **`Path=/api/auth/refresh-token`**: The cookie is only sent on requests to the refresh endpoint, not on any other API call.
Combined, these attributes mean an attacker cannot trigger a refresh from a cross-origin page. The `SameSite=Strict` policy is appropriate here because the refresh endpoint is never called from an external context (no OAuth callback, no payment provider redirect targets the refresh endpoint).
### If the architecture migrates access tokens to cookies later
If a future decision moves the access token to a cookie (which we explicitly reject in this ADR), CSRF tokens become mandatory. The recommended approach would be:
- Double-submit cookie pattern: set a CSRF token in a non-httpOnly cookie; the frontend reads it and includes it in a custom header (`X-CSRF-Token`). The backend verifies the header matches the cookie.
- Apply to all state-changing endpoints (POST, PUT, PATCH, DELETE).
This fallback is documented but not implemented. No action is needed unless the access token storage decision is revisited.
### Web3 wallet interactions
Web3 wallet connections (wagmi, WalletConnect) open popup windows or browser extensions for signing. These interactions do not involve the platform's cookies or tokens. The signed transaction or message is returned to the platform's JavaScript context and sent to the backend via the normal Axios interceptor with the in-memory access token. CSRF is not a concern for Web3 interactions.
---
## Section 5: Passkey/WebAuthn Decision
### Decision: Option C -- feature-flagged off in production
We will keep the current stubbed passkey implementation in the codebase, gate it behind a feature flag (`ENABLE_PASSKEYS`), and set this flag to `false` in production environments.
### Rejected alternatives
| Alternative | Rejection reason |
|---|---|
| **Option A: Remove passkeys entirely** | The frontend UI and backend routes already exist. Removing them means rebuilding the registration and sign-in UI later. The stubbed code does not pose a security risk when disabled via feature flag. |
| **Option B: Implement production WebAuthn pre-launch** | Per [[Platform Logical Audit - 2026-05-24]] Finding 2, the current implementation has three critical flaws (stubbed attestation, in-memory challenges, missing refresh-token persistence). Fixing all three, testing across platforms, and auditing the result would take 2-3 weeks of focused engineering. This is not justifiable before launch when password + OAuth authentication is sufficient. |
### Rationale
The launch gate in [[Security Ownership and Launch Decision Criteria]] Section 2.1.7 requires: "Passkey/WebAuthn disabled in production until real cryptographic implementation is complete." A feature flag is the cleanest way to comply: the code exists but cannot be reached in production. The deferred decision D-4 sets a deadline of 90 days post-launch for real WebAuthn.
### Feature flag implementation
- Backend env var: `ENABLE_PASSKEYS=false` in production, `true` in development.
- The passkey routes (`/api/auth/passkey/*`) return `404 Not Found` when the flag is `false`. The route registration itself is conditional.
- Frontend: the Passkey UI components are hidden when `NEXT_PUBLIC_ENABLE_PASSKEYS` is not `"true"`.
- Both flags are `false` by default; must be explicitly enabled.
### Target WebAuthn implementation (for D-4 resolution)
When the team implements production WebAuthn within 90 days post-launch, the following specifications apply:
**Library:** `@simplewebauthn/server` (server-side) and `@simplewebauthn/browser` (client-side).
**Relying Party configuration:**
| Parameter | Value |
|---|---|
| RP ID | Production eTLD+1 domain (e.g., `amn.gg`), NOT `localhost` |
| RP Name | `Amanat` |
| Origins | `https://amn.gg`, `https://www.amn.gg` |
| Timeout | 60 seconds |
**Challenge storage:** Redis-backed with 5-minute TTL. Key: `webauthn:challenge:{challengeHash}`. Value: `{ userId, type: 'registration' | 'authentication', createdAt }`. The in-process `Map` is removed.
**Attestation type:** `none`. Rationale: Amanat does not need to verify the authenticator manufacturer or model. Direct or indirect attestation adds complexity (managing attestation certificates, privacy concerns) without security benefit for this platform. We rely on the authenticator's signature, not its attestation.
**Credential storage (passkeys[] subdocument):**
| Field | Type | Description |
|---|---|---|
| `id` | String | Base64url-encoded credential ID |
| `publicKey` | Buffer | COSE public key (actual bytes, not a stub string) |
| `counter` | Number | Monotonic signature counter; incremented on each authentication |
| `deviceType` | String | `platform` or `cross-platform` |
| `deviceName` | String | User-provided label or auto-generated from user-agent |
| `transports` | String[] | Authenticator transports (e.g., `['internal', 'hybrid']`) |
| `registeredAt` | Date | Timestamp |
**Authentication flow integration:** Passkey login issues the same JWT pair as password login. The refresh token is persisted in `user.refreshTokens[]` using the same schema as all other authentication methods. This closes the gap identified in [[Passkey (WebAuthn) Flow]] where passkey-issued tokens were not added to the allow-list.
**Counter enforcement:** On each authentication, the received counter must be strictly greater than the stored counter. If the counter is less than or equal, the authentication is rejected, the event is logged as a potential cloned authenticator, and the user is notified.
**Cross-device authentication:** Allowed. The `transports` field in registration options includes `hybrid` to support cross-device flows (e.g., phone authenticator on desktop login).
**Migration from stubbed to real implementation:**
1. Deploy feature flag change: `ENABLE_PASSKEYS=true` in staging only.
2. Run migration: delete all entries in `user.passkeys[]` (they contain the stub `'simulated-public-key'` and are not valid credentials). Notify users that passkeys must be re-registered.
3. Deploy `@simplewebauthn/server` integration with Redis challenge store.
4. QA: test registration and authentication on Chrome (Touch ID / YubiKey), Firefox, Safari, Android, iOS.
5. Enable in production after QA sign-off.
---
## Section 6: OAuth Requirements
### Google OAuth
The current Google OAuth implementation documented in [[Google OAuth Flow]] is largely compatible with the new session model. The following adjustments apply:
**Token exchange flow:** The current implementation uses Google Identity Services (GIS) with `initTokenClient` and `requestAccessToken`. The frontend receives an ID token (Google-signed JWT). This is sent to the backend for verification. This flow is already correct -- it is equivalent to an authorization code with PKCE flow where Google handles the code exchange client-side. No change is needed.
If additional OAuth providers are added in the future (GitHub, Apple), we will use the authorization code flow with PKCE. The frontend obtains an authorization code via redirect and sends it to the backend. The backend exchanges the code for tokens server-side. This prevents the client from ever seeing the provider's access token.
**Session integration:** After Google token verification succeeds, the backend issues the same JWT access token (15-minute, in-memory) and refresh token (7-day, httpOnly cookie) as the email/password flow. There is no separate session type for OAuth users. This is the current behavior and it is correct.
**Account linking:** Account linking is implicit by email match (current behavior). If `googleUser.email` matches an existing user, the existing account is used. Risks and mitigations:
| Risk | Mitigation |
|---|---|
| Attacker creates a Google account with a victim's email before the victim signs up | Google accounts are pre-verified; the attacker must control the email address at Google. This is a standard OAuth risk. |
| Victim signs up with email/password; attacker later creates Google account with same email and gains access | The backend checks for existing users on Google sign-in and does NOT create a new account. The attacker would need the victim's Google credentials. |
| User changes Google account email to match a different user | Google tokens are verified per-request; the backend trusts the `email` from the verified ID token. If Google allows email changes (they do not for gmail.com), this could be a vector. Mitigation: consider storing `googleId` (the `sub` claim) as a separate field in the future for multi-provider identity. |
For launch, the current email-based linking is acceptable. Post-launch, we should store `providers[].providerId` (e.g., `google:123456789`) for robust multi-provider identity.
**Token storage for OAuth sessions:** Same as email/password. Access token in memory, refresh token in httpOnly cookie.
**Logout behavior for OAuth sessions:** Logout invalidates the refresh token in `user.refreshTokens[]`, clears the cookie, and deletes Redis session records. The Google session on Google's side is not terminated (we do not call Google's revoke endpoint). This is standard practice. The user must sign out of Google separately if desired.
---
## Section 7: Admin Step-Up Authentication
### Decision
High-risk admin actions require re-authentication. Upon successful re-authentication, the admin receives a short-lived elevated session. Payouts above $1,000 USD equivalent also require two-person approval.
### Definition of high-risk admin actions
| Action | Step-Up Required | Two-Person Approval | Rationale |
|---|---|---|---|
| Payout/release escrow <= $1,000 USD | Yes (password) | No | Financial action; compromised session could release funds |
| Payout/release escrow > $1,000 USD | Yes (password) | Yes | High-value financial action; dual control per Threat T18 |
| Manual wallet signing (any amount) | Yes (password) | Yes (if > $1,000) | Direct access to escrow wallet |
| Refund escrow > $500 USD | Yes (password) | No | Irreversible financial action |
| User suspension or deletion | Yes (password) | No | Account impact; potential for abuse |
| Role change (any) | Yes (password) | No | Privilege escalation vector |
| Dispute override (admin resolves against recommendation) | Yes (password) | No | Financial side-effect; high dispute value |
| API key rotation (`JWT_SECRET`, webhook secrets) | Yes (password) | No | Invalidates all sessions or compromises integrity |
| Disable rate limiting or security features | Yes (password) | No | Reduces platform security posture |
| Export user data (bulk) | Yes (password) | No | Privacy-sensitive bulk operation |
| View escrow wallet private key (if applicable) | Yes (password) | Yes | Critical asset exposure |
### Step-up mechanism
**Re-authentication with password.** The admin must enter their password to obtain an elevated session. No additional 2FA at launch (passkeys are disabled; TOTP is not yet implemented). Post-launch, when WebAuthn is production-ready (D-4), the step-up will also accept passkey authentication as a second factor.
**Elevated session:**
| Attribute | Value |
|---|---|
| Duration | 5 minutes |
| Storage | Server-side only (Redis key `stepup:{userId}` with TTL 300s) |
| Scope | Grants elevated permissions for the specific action categories listed above |
| Renewal | Re-authentication required after 5 minutes; no automatic renewal |
| Verification | Middleware `requireStepUp()` checks Redis key existence before allowing the action |
### Step-up flow
1. Admin attempts a high-risk action (e.g., `POST /api/admin/payouts/release`).
2. Middleware `requireStepUp()` checks for an active elevated session in Redis.
3. If no elevated session exists, the backend returns `403 STEP_UP_REQUIRED` with `{ challengeId: uuid }`.
4. Frontend displays a password prompt (modal dialog).
5. Frontend sends `POST /api/auth/step-up` with `{ password, challengeId }`.
6. Backend verifies the password against `user.password` using bcrypt.
7. On success, backend creates Redis key `stepup:{userId}` with TTL 300s and returns `{ elevated: true, expiresAt: timestamp }`.
8. Frontend retries the original high-risk action.
9. The action proceeds.
### Traceability to Authorization Matrix
This matrix maps to:
- `AUTH-R025` (`POST /api/auth/step-up`) for the step-up API entry point.
- `AUTH-R026` (`GET /api/auth/sessions`), `AUTH-R027` (`POST /api/auth/revoke-session`), `AUTH-R028` (`POST /api/auth/revoke-all-sessions`) for session controls.
- `APV-R001`, `APV-R002`, `APV-R003` for approval queue + confirm/reject workflow.
Status: these rows are marked **Not implemented** in the matrix while this ADR remains in planning/rollout state.
### Two-person approval flow
For actions requiring two-person approval:
1. Admin A completes the step-up flow above.
2. Admin A initiates the action (e.g., `POST /api/admin/payouts/release`).
3. The action is created in a `PendingApproval` state (stored in MongoDB).
4. The system notifies all other admin users via Socket.IO and email.
5. Admin B navigates to the pending approval, completes their own step-up flow, and confirms (`POST /api/admin/approvals/{id}/confirm`).
6. The action executes.
7. If Admin B rejects (`POST /api/admin/approvals/{id}/reject`), the action is cancelled.
**Fallback when second admin is unavailable:**
If no second admin has acted on a pending approval within 4 hours, the CTO (or designated fallback) receives an email and Slack notification. The CTO can approve directly. If no CTO action within 24 hours, the approval expires and must be re-initiated.
This fallback addresses the realistic scenario where Amanat has a small team with few admins. As the team grows, the 4-hour and 24-hour windows should be tightened.
### Audit logging for step-up events
All step-up and two-person approval events are logged to an append-only audit collection:
| Field | Value |
|---|---|
| `action` | `step-up.attempt`, `step-up.success`, `step-up.failed`, `approval.created`, `approval.confirmed`, `approval.rejected`, `approval.expired` |
| `actorId` | ObjectId of the admin performing the action |
| `targetAction` | The high-risk action being performed (e.g., `payout.release`) |
| `targetEntity` | ObjectId or identifier of the entity (e.g., Payment ID) |
| `ip` | Request IP |
| `userAgent` | Request user-agent |
| `timestamp` | ISO 8601 |
| `metadata` | JSON object with action-specific details (e.g., payout amount) |
This collection is not writable by the application after insert (no updates, no deletes). Access is restricted to admin read-only and system write-only.
---
## Section 8: Session Management and Device Tracking
### Session tracking
Sessions are tracked via `user.refreshTokens[]` subdocuments (see Section 3 schema). Each entry represents one authenticated device.
### Device fingerprinting
We will use lightweight, non-invasive device identification:
| Signal | Source | Storage | Notes |
|---|---|---|---|
| User-Agent | `req.headers['user-agent']` | `refreshTokens[].deviceInfo` | Truncated to 200 characters |
| IP address | `req.ip` (behind CloudFlare: `req.headers['x-forwarded-for']`) | `refreshTokens[].ipAddress` | Used for geolocation approximation |
| Platform hint | Derived from user-agent parsing | Display only | Not stored separately |
We will NOT use browser fingerprinting (Canvas, WebGL, font enumeration), device IDs, or any tracking technique that requires user consent under privacy regulations. The user-agent and IP are already sent with every HTTP request.
### Session listing
**`GET /api/auth/sessions`** (requires Bearer JWT)
Returns the list of active sessions for the current user:
```json
{
"sessions": [
{
"id": "sha256-hash-of-token",
"device": "Chrome on macOS",
"lastActive": "2026-05-24T14:30:00Z",
"ip": "203.0.113.42",
"location": "Tehran, Iran (approximate)",
"isCurrent": true
}
]
}
```
- `device` is a parsed, human-readable string derived from the user-agent (e.g., "Chrome 125 on macOS", "Safari on iPhone").
- `location` is derived from IP geolocation (city-level, approximate). We will use a local GeoIP database (MaxMind GeoLite2 or equivalent) to avoid sending user IPs to third-party services.
- `isCurrent` identifies the session making the request (matched by the refresh cookie).
### Session revocation
**`POST /api/auth/revoke-session`** (see Section 3).
Users can revoke any non-current session. Revoking the current session is equivalent to logout.
**`POST /api/auth/revoke-all-sessions`** (see Section 3).
Revokes all sessions except the current one. Useful if the user suspects compromise.
### Maximum sessions per user
**5 sessions.** When a user attempts to create a 6th session (login from a new device), the oldest session (by `createdAt`) is automatically revoked. The user is notified via email: "A new sign-in was detected on [device] from [location]. If this was not you, please change your password immediately."
Rationale: 5 sessions accommodates typical usage (desktop, laptop, phone, tablet, one more) while preventing unbounded session accumulation.
### Password change behavior
When a user changes their password:
1. All existing sessions are revoked (`user.refreshTokens = []`).
2. A new session is created for the current device.
3. All Redis session records for the user are deleted.
4. Email notification: "Your password was changed. If you did not make this change, contact support immediately."
This is the current behavior documented in [[Authentication Flow]] and it is correct.
### Account lock/suspension behavior
When an admin suspends or deletes a user account:
1. `user.status` is set to `suspended` or `deleted`.
2. `user.refreshTokens` is set to `[]`.
3. All Redis session records for the user are deleted.
4. Any in-flight requests with tokens for that user return `403 ACCOUNT_SUSPENDED` or `403 ACCOUNT_DELETED` on the next request (the `authMiddleware` already checks `user.status`).
---
## Section 9: Migration Plan
### Current state
| Component | Current | Target | Change Level |
|---|---|---|---|
| Access token storage | localStorage | In-memory variable | Frontend only |
| Access token lifetime | 7 days | 15 minutes | Backend config |
| Refresh token storage | localStorage | httpOnly cookie (backend set) | Full stack |
| Refresh token lifetime | 30 days | 7 days | Backend config |
| Refresh token schema | `String[]` | Subdocument array with metadata | Backend + DB migration |
| CSRF protection | Not needed (header-based) | Not needed (header-based + SameSite cookie) | None |
| Passkey status | Stubbed, accessible | Feature-flagged off in production | Backend + Frontend |
| Session revocation | Not implemented | Endpoints + device listing | Backend + Frontend |
| Admin step-up | Not implemented | Password re-auth + elevated session | Backend + Frontend |
| Two-person approval | Not implemented | Pending approval workflow | Backend + Frontend |
### Migration steps (in order)
**Step 1: Backend -- reduce access token lifetime to 15 minutes**
- Change `JWT_EXPIRES_IN` default from `7d` to `15m`.
- Deploy. Existing 7-day tokens remain valid until they expire naturally (no force-invalidations).
- Risk: users with long-lived sessions will notice more frequent refreshes. This is expected and acceptable.
**Step 2: Backend -- refresh token lifetime to 7 days**
- Change `REFRESH_TOKEN_EXPIRES_IN` default from `30d` to `7d`.
- Deploy. Existing 30-day refresh tokens remain valid until they expire or are rotated.
**Step 3: Backend -- add refresh token metadata to refreshTokens[]**
- Deploy new schema: `user.refreshTokens` becomes a subdocument array with `tokenHash`, `deviceInfo`, `ipAddress`, `createdAt`, `lastUsedAt`.
- Migration script: convert existing `String[]` entries to `{ tokenHash: sha256(entry), deviceInfo: 'Unknown (pre-migration)', ipAddress: 'unknown', createdAt: Date.now(), lastUsedAt: Date.now() }`.
- Login and refresh endpoints updated to write new schema.
- Deploy. Old-format entries continue to work during migration.
**Step 4: Backend -- set refresh token as httpOnly cookie**
- On login, refresh, and OAuth sign-in: set `Set-Cookie` header with the refresh token in an httpOnly cookie. Also return the refresh token in the response body for backward compatibility.
- Add `POST /api/auth/refresh-token-cookie` endpoint that accepts the refresh token from the body and sets it as a cookie (migration helper for existing sessions).
- Deploy. Frontend still works with body-based refresh tokens.
**Step 5: Frontend -- move access token to in-memory storage**
- Replace `localStorage.getItem('accessToken')` and `localStorage.setItem('accessToken', ...)` with an in-memory store (module-level variable or React context).
- On app load: check for refresh cookie. If present, call refresh endpoint to obtain a new access token. If no cookie, redirect to login.
- Remove `localStorage` writes for both tokens. On logout, clear the in-memory token and the cookie (by calling the logout endpoint which sets an expired cookie).
- Deploy frontend.
**Step 6: Frontend -- send refresh via cookie instead of body**
- Modify the Axios interceptor to NOT send `refreshToken` in the body of `POST /api/auth/refresh-token`. The refresh token is sent automatically via the cookie.
- Backend: accept refresh token from either cookie or body (backward compatible). Deprecate body-based refresh with a log warning.
- Deploy both.
**Step 7: Backend -- add session management endpoints**
- `GET /api/auth/sessions` -- list active sessions.
- `POST /api/auth/revoke-session` -- revoke a specific session.
- `POST /api/auth/revoke-all-sessions` -- revoke all other sessions.
- Deploy. No frontend change yet (endpoints are available but unused).
**Step 8: Frontend -- add session management UI**
- Account settings page: "Active Sessions" section listing devices, locations, and last active times.
- "Revoke" button per session. "Revoke all other sessions" button.
- Deploy frontend.
**Step 9: Backend -- feature flag for passkeys**
- Add `ENABLE_PASSKEYS` env var (default `false`).
- Gate all `/api/auth/passkey/*` routes behind the flag.
- Return `404` when disabled.
- Deploy.
**Step 10: Frontend -- feature flag for passkey UI**
- Add `NEXT_PUBLIC_ENABLE_PASSKEYS` env var (default `false`).
- Hide passkey UI components when disabled.
- Deploy frontend.
**Step 11: Backend -- admin step-up authentication**
- Add `POST /api/auth/step-up` endpoint.
- Add `requireStepUp()` middleware.
- Apply middleware to high-risk admin routes.
- Add Redis-based elevated session store.
- Deploy.
**Step 12: Frontend -- admin step-up UI**
- Password prompt modal for step-up challenges.
- Intercept `403 STEP_UP_REQUIRED` responses and show modal.
- Retry original request after successful step-up.
- Deploy frontend.
**Step 13: Backend -- two-person approval**
- Add `PendingApproval` collection.
- Add approval workflow endpoints.
- Apply to payout/release actions above $1,000.
- Add notification logic for other admins.
- Deploy.
**Step 14: Frontend -- two-person approval UI**
- Pending approvals list in admin dashboard.
- Confirm/reject actions with step-up.
- Deploy frontend.
**Step 15: Backend -- remove body-based refresh token acceptance**
- After all frontends are migrated (Step 6 + reasonable buffer of 2 weeks), stop accepting refresh tokens from the request body.
- Accept refresh tokens only from the cookie.
- Deploy.
### Feature flags
| Flag | Default | Environments | Purpose |
|---|---|---|---|
| `ENABLE_PASSKEYS` | `false` | All | Controls passkey route registration |
| `NEXT_PUBLIC_ENABLE_PASSKEYS` | `false` | All | Controls passkey UI visibility |
| `COOKIE_REFRESH_MIGRATION` | `false` | All | Enables cookie-based refresh token issuance |
| `REQUIRE_STEP_UP` | `false` | Staging, Production | Enables step-up auth for admin actions |
### Rollback plan
If any migration step causes issues:
1. **Steps 1-2 (token lifetimes):** Revert `JWT_EXPIRES_IN` and `REFRESH_TOKEN_EXPIRES_IN` to previous values. Redeploy. No data migration to undo.
2. **Steps 3-4 (refresh token schema + cookies):** Backend continues to accept body-based refresh tokens. Frontend can revert to `localStorage` storage. The httpOnly cookie is additive; removing it does not break existing sessions.
3. **Step 5 (in-memory access token):** Frontend can revert to `localStorage`. The backend does not care where the access token comes from.
4. **Steps 7-8 (session management):** These are additive endpoints and UI. Rolling back means removing the UI and endpoints. No data is affected.
5. **Steps 9-10 (passkey feature flag):** Set flags to `true` to restore passkey access (though passkeys remain stubbed and insecure). Rolling back is simply changing env vars.
6. **Steps 11-14 (step-up and two-person approval):** Remove `requireStepUp()` middleware. Admin actions proceed without step-up. This is a security regression but not a functional outage.
### Timeline estimate
| Phase | Steps | Duration | Dependencies |
|---|---|---|---|
| Token hardening | 1-2 | 1 day | None |
| Cookie migration | 3-6 | 3-5 days | Frontend + backend coordination |
| Session management | 7-8 | 2-3 days | Cookie migration complete |
| Passkey feature flag | 9-10 | 1 day | None |
| Admin step-up | 11-12 | 3-4 days | None |
| Two-person approval | 13-14 | 3-5 days | Admin step-up complete |
| Cleanup (step 15) | 15 | 1 day (after 2-week buffer) | All frontends migrated |
| **Total** | | **14-21 days** | |
---
## Section 10: Threat Mitigation Traceability
| Decision | Threats Addressed | Risk Reduction |
|---|---|---|
| Access token in memory (not localStorage) | T13 (XSS token theft) | XSS cannot persistently steal the token; it is lost on page unload |
| Access token lifetime reduced to 15 min | T04 (stolen token reuse) | Stealable token valid for 15 min instead of 7 days (672x reduction in exposure window) |
| Refresh token in httpOnly cookie | T04, T13 | XSS cannot read the refresh token; it is not accessible to JavaScript |
| Refresh token lifetime reduced to 7 days | T04 | Maximum exploitation window from a compromised refresh token is 7 days instead of 30 days |
| Refresh token rotation with reuse detection | T04 | Reuse of a rotated token triggers full session invalidation; attacker and legitimate user are forced to re-authenticate |
| SameSite=Strict on refresh cookie | T15 (CSRF) | Cookie not sent on cross-origin requests; CSRF on refresh endpoint is eliminated |
| Refresh cookie scoped to `/api/auth/refresh-token` path | T15 | Cookie sent only on the refresh endpoint; not on any state-changing endpoint |
| Passkey feature flag disabled in production | T10 (passkey bypass) | Stubbed passkey implementation is unreachable in production; cannot be exploited |
| Session revocation endpoints | T04 | Users can terminate compromised sessions immediately; admins can revoke sessions for suspended users |
| Max 5 sessions per user | T04 | Limits blast radius of session accumulation; oldest sessions auto-revoked |
| Admin step-up authentication | T09 (admin privilege escalation), T18 (insider fund manipulation) | Compromised admin session cannot perform high-risk actions without re-authenticating; elevated session lasts only 5 minutes |
| Two-person approval for large payouts | T05 (double payout), T18 | No single admin can release high-value escrow; second admin must independently verify and approve |
| Audit logging for step-up and approval events | T09, T18 | All elevated-access events are recorded in tamper-evident audit trail |
| Password change revokes all sessions | T04 | If user detects compromise, password change immediately terminates all attacker sessions |
| Account suspension revokes all sessions | T09 | Compromised admin accounts are immediately locked out when suspended |
| Device/session listing | T04 | Users can detect unfamiliar sessions and revoke them; early detection of compromise |
| Email notification on new device login | T04 | User is alerted to unauthorized access within minutes |
| Verification code removal from production logs | T22 (verification code leakage) | Codes are no longer loggable in production; only non-production environments may log them for debugging |
| Access token `jti` claim | T04 | Each token has a unique identifier; enables future deny-listing of individual tokens |
| OAuth token storage same as email/password | T04, T13 | OAuth sessions receive the same protections (in-memory access, httpOnly refresh) |
### Coverage analysis
| Threat | Mitigated by this ADR? | Residual risk |
|---|---|---|
| T04 (stolen token reuse) | Yes -- 15-min access token, httpOnly refresh cookie, rotation, session revocation | Physical access to an unlocked device with an active session; keylogger capturing password during step-up |
| T10 (passkey bypass) | Yes -- feature flag disabled in production | None (passkeys are unreachable) |
| T13 (XSS token theft) | Yes -- in-memory access token, httpOnly refresh cookie | Transient XSS can read in-memory token for up to 15 minutes; XSS cannot access refresh token |
| T15 (CSRF) | Yes -- access token in Authorization header (unchanged), SameSite=Strict on refresh cookie | None |
| T22 (verification code leakage) | Partially -- ADR documents the requirement; implementation is a separate task | Codes still logged until code change is deployed |
### Threats NOT addressed by this ADR (addressed elsewhere)
| Threat | Document |
|---|---|
| T01 (fake payment proof) | [[Funds Ledger and Escrow State Machine Specification]], [[Payment Provider Adapter Spec]] |
| T02 (webhook replay) | [[Webhook Security Spec]] |
| T03 (arbitrary socket room join) | [[Realtime Authorization Spec]] |
| T05 (double payout) | [[Funds Ledger and Escrow State Machine Specification]] |
| T06 (dispute bypass) | [[Funds Ledger and Escrow State Machine Specification]] |
| T07 (email abuse) | Rate limiting implementation |
| T08 (AI cost abuse) | Rate limiting + auth implementation |
| T09 (admin privilege escalation) | [[Authorization Matrix - REST and Socket.IO]] + step-up auth (this ADR) |
| T11 (unauthenticated payment endpoints) | Auth middleware implementation |
| T12 (rate limit bypass) | Rate limiting implementation |
| T14 (supply-chain) | [[Secure Build and Supply-Chain Policy]] |
| T16 (deep-link tampering) | Telegram initData verification |
| T17 (provider outage) | [[Backend Funds Migration and Operational Runbooks]] |
| T18 (insider manipulation) | Multi-sig wallet + funds ledger + two-person approval (this ADR) |
| T19 (price manipulation) | Offer status enforcement |
| T20 (delivery brute force) | Rate limiting + code entropy |
| T21 (data exfiltration) | Auth middleware implementation |
| T23 (state machine inconsistency) | Canonical state machine specification |
---
## Cross-references
- [[Threat Model - Amanat Escrow Platform]] -- T04, T10, T13, T15, T22
- [[Security Ownership and Launch Decision Criteria]] -- D-1 (cookie migration), D-4 (real WebAuthn), D-5 (session revocation), D-6 (admin step-up)
- [[Security Architecture]] -- current authentication implementation
- [[Authentication Flow]] -- current token lifecycle
- [[Passkey (WebAuthn) Flow]] -- current passkey implementation (stubbed)
- [[Google OAuth Flow]] -- current OAuth implementation
- [[Platform Logical Audit - 2026-05-24]] -- Findings 2, 8, 10, 12
- [[Backend Stack Security and Refactor Assessment - 2026-05-24]] -- Phase 0 hardening requirements
---
*This document was created on 2026-05-24 as part of the Taskmaster task 4 (authentication and session architecture) for the Amanat escrow platform. It must be reviewed by Backend Lead, Frontend Lead, and CTO before implementation begins. Changes to any decision in this document require sign-off per the RACI in [[Security Ownership and Launch Decision Criteria]] Section 1.*

View File

@@ -0,0 +1,106 @@
---
title: Task 4 Backend Security Architecture Verification Report
tags: [taskmaster, verification, security, backend]
created: 2026-05-24
status: complete
---
# Task 4 Backend Security Architecture Verification Report
Taskmaster task 4 is complete as an advisory architecture and handoff package.
The task defines how the backend security/refactor assessment is converted into
implementation criteria without rewriting or disrupting the current backend
model.
## 1. Deliverable map
| Taskmaster item | Deliverable |
|---|---|
| 4.1 Security ownership and launch criteria | [[Security Ownership and Launch Decision Criteria]] |
| 4.2 Escrow platform threat model | [[Threat Model - Amanat Escrow Platform]] |
| 4.3 Funds ledger and escrow state machine | [[Funds Ledger and Escrow State Machine Specification]] |
| 4.4 REST and Socket.IO authorization matrix | [[Authorization Matrix - REST and Socket.IO]], [[Realtime Authorization Spec]] |
| 4.5 Session, passkey, and admin step-up architecture | [[Session and Authentication Architecture Decision]] |
| 4.6 Webhook security and payment adapter contracts | [[Webhook Security Spec]], [[Payment Provider Adapter Spec]] |
| 4.7 Secure build and supply-chain policy | [[Secure Build and Supply-Chain Policy]] |
| 4.8 Backend-core stack decision | [[Backend Core Stack Decision Record - 2026-05-24]] |
| 4.9 Migration and operational runbooks | [[Backend Funds Migration and Operational Runbooks]] |
## 2. Architecture decisions verified
- The current TypeScript/Node backend remains the production delivery path for
the next security-hardening phase.
- A full backend rewrite is explicitly out of scope until ledger, webhook,
provider, auth/session, and reconciliation contracts are stable and observable.
- Payment providers are optional and provider-neutral behind adapter contracts.
- Webhooks must use raw-body signature verification, replay prevention,
idempotency, and dead-letter capture.
- Funds movement must be derived from the canonical ledger and escrow state
machine, not provider metadata.
- Admin release, refund, payout, role, and destructive account operations require
step-up authentication and audit logging; high-risk payouts require
two-person approval.
- Socket.IO room membership must be server-derived and authorization checked,
with global financial broadcasts prohibited.
## 3. Verification commands
Executed from `nick-doc` on 2026-05-24:
```bash
npx task-master show 4
npx task-master set-status --id=4.6 --status=done
npx task-master set-status --id=4.7 --status=done
npx task-master set-status --id=4.8 --status=done
npx task-master set-status --id=4.9 --status=done
npx task-master set-status --id=4.3 --status=done
npx task-master set-status --id=4.4 --status=done
npx task-master set-status --id=4.5 --status=done
npx task-master set-status --id=4 --status=done
node - <<'NODE'
const fs=require('fs');
const data=JSON.parse(fs.readFileSync('.taskmaster/tasks/tasks.json','utf8'));
const t=data.master.tasks.find(x=>String(x.id)==='4');
console.log(JSON.stringify({
task:t.status,
subtasks:t.subtasks.map(s=>({id:s.id,status:s.status,title:s.title}))
}, null, 2));
NODE
```
A one-off Node link checker also parsed Task 4 wiki links and verified they
resolve to markdown files; threat IDs such as `[[T05]]` were treated as allowed
shorthand references.
## 4. Verification result
- Taskmaster JSON reports task 4 as `done`.
- Taskmaster JSON reports subtasks 4.1 through 4.9 as `done`.
- `Authorization Matrix - REST and Socket.IO` now links directly to [[Realtime Authorization Spec]].
- Task 4 wiki links resolve to existing markdown files, excluding threat-ID
shorthand references such as `[[T05]]`.
- Incident ownership in the task 4 runbook was replaced with explicit role owners
that can be mapped to named responders before production launch.
- Remaining implementation tests belong to follow-up backend tasks because task
4 is a documentation and architecture handoff task.
## 5. Follow-up implementation test requirements
Implementation tasks derived from task 4 must include:
- ledger invariant unit tests for every escrow transition,
- payment provider adapter contract tests for SHKeeper, Request Network, manual
wallet, and disabled-provider modes,
- webhook signature, replay, duplicate, and DLQ tests,
- REST authorization tests for every gap listed in the authorization matrix,
- Socket.IO handshake, room authorization, targeted emission, and rate-limit
tests,
- session rotation, revocation, passkey disabled/enabled, and admin step-up
tests,
- runbook drills for provider outage, leaked webhook secret, stuck release,
suspicious payment proof, and compromised admin.
## 6. Residual risk
This report verifies the Task 4 architecture package, not production behavior.
Backend implementation work must still enforce these controls before launch.

View File

@@ -0,0 +1,110 @@
---
title: Task 5 Telegram-Native Verification Report
tags: [taskmaster, verification, telegram]
created: 2026-05-24
status: draft
---
# Task 5 Telegram-Native Verification Report
## 1) Deliverable map
| Subtask | Artifact | Location |
|---|---|---|
| 5.1 | Product surface map | `09 - Audits/Task 5.1 Telegram Product Surface and Flow Map.md` |
| 5.2 | Identity linking and session model | `09 - Audits/Task 5.2 Telegram Identity Linking and Session Model.md`, backend Telegram service/model/routes |
| 5.3 | Bot command and notification foundation | `09 - Audits/Task 5.3 Telegram Bot Command and Notification Foundation.md`, backend Telegram webhook skeleton |
| 5.4 | Mini App shell | `09 - Audits/Task 5.4 Telegram Mini App Shell Implementation.md`, frontend `/telegram` route |
| 5.5 | Payment/wallet strategy | `09 - Audits/Task 5.5 Telegram Payment and Wallet Strategy.md` |
| 5.6 | Escrow/delivery/dispute/release action plan | `09 - Audits/Task 5.6 Telegram Escrow Delivery Dispute Release Actions.md` |
| 5.7 | Admin/support operating surface plan | `09 - Audits/Task 5.7 Telegram Admin Support Surface.md` |
| 5.8 | Security/compliance controls | `09 - Audits/Task 5.8 Telegram Security, Compliance, and Abuse Controls.md` |
| 5.9 | QA/rollout/analytics/runbooks | `09 - Audits/Task 5.9 QA Rollout Analytics and Launch Runbooks.md` |
## 2) Documentation verification executed
Commands run while producing this report:
```bash
cd nick-doc && task-master next
cd nick-doc && task-master show 5.1
cd nick-doc && task-master show 5.5
cd nick-doc && task-master show 5.8
cd nick-doc && task-master show 5.9
rg --files nick-doc/.taskmaster/docs
npm run typecheck --silent
npx jest __tests__/telegram-service.test.ts __tests__/telegram-routes.test.ts --runInBand --silent
npx eslint src/services/telegram/telegramService.ts src/services/telegram/telegramRoutes.ts src/models/TelegramLink.ts src/models/TelegramSession.ts __tests__/telegram-service.test.ts __tests__/telegram-routes.test.ts
npx jest __tests__/utils-test/telegram-webapp.test.ts __tests__/sections/telegram/telegram-mini-app-shell.test.tsx --runInBand
npx eslint src/utils/telegram-webapp.ts src/sections/telegram/telegram-mini-app-shell.tsx src/app/telegram/page.tsx
npx tsc --noEmit
```
## 2.1 Code artifacts added
Backend:
- `src/models/TelegramLink.ts`
- `src/models/TelegramSession.ts`
- `src/services/telegram/telegramService.ts`
- `src/services/telegram/telegramRoutes.ts`
- `src/services/telegram/index.ts`
- `/api/telegram` route mount behind feature flag
- `__tests__/telegram-service.test.ts`
- `__tests__/telegram-routes.test.ts`
Frontend:
- `src/app/telegram/page.tsx`
- `src/sections/telegram/telegram-mini-app-shell.tsx`
- `src/sections/telegram/index.ts`
- `src/utils/telegram-webapp.ts`
- `paths.telegram.root`
- `__tests__/utils-test/telegram-webapp.test.ts`
- `__tests__/sections/telegram/telegram-mini-app-shell.test.tsx`
## 3) Constraint checks against Task 4 docs
- Canonical ledger and escrow states as authority: verified and referenced in all 4 Task 5 documents.
- Provider adapters kept optional in the first phase: explicitly stated in Task 5.5.
- Dispute hold and authorization gates preserved for Telegram-initiated financial transitions: repeated across all documents with links to:
- `Funds Ledger and Escrow State Machine Specification`
- `Payment Provider Adapter Spec`
- `Webhook Security Spec`
- `Security Ownership and Launch Decision Criteria`
## 3.1 Test results
Backend:
- `npm run typecheck --silent` passed.
- `npx jest __tests__/telegram-service.test.ts __tests__/telegram-routes.test.ts --runInBand --silent` passed: 2 suites, 12 tests.
- Scoped backend ESLint passed on Telegram service/model/route/test files.
Frontend:
- `npx jest __tests__/utils-test/telegram-webapp.test.ts __tests__/sections/telegram/telegram-mini-app-shell.test.tsx --runInBand` passed: 2 suites, 6 tests.
- Scoped frontend ESLint passed on Telegram utility/route/component files.
- `npx tsc --noEmit` is blocked by two pre-existing non-Telegram errors:
- `src/components/payment/shkeeper-payment-widget.tsx` missing `sellerId` in an `IPaymentIntentPayload`.
- `src/web3/components/provider-payment.tsx` uses unregistered `solar:wallet-money-bold` icon.
## 4) Remaining gaps and open questions
1. Bot command actions are still a skeleton: updates are classified and deduped,
but commands/callbacks do not yet execute product workflows.
2. Outbound Telegram notifications, preferences, blocked-bot handling, retry
metrics, and quiet mode are not implemented.
3. Mini App shell is present, but request/offer/payment/dispute data loading and
action execution are still pending.
4. Deep-link token schema still needs final claim set and signing key rotation schedule.
5. Analytics schema and dashboard ownership for Telegram events remains to be implemented in code and monitoring tooling.
6. Runbook automation checks for replay checks and payment mismatch drilldown are not yet scripted in operations tooling.
## 5) Recommendation
This pass completes the Task 5 foundation for product mapping, Telegram identity
linking/session verification, Mini App shell, payment/wallet strategy, security
controls, and launch QA documentation. Keep Task 5 open until bot actions,
notifications, real Mini App marketplace workflows, Telegram escrow actions, and
admin/support surfaces are implemented and verified.

View File

@@ -0,0 +1,122 @@
---
title: Task 5.1 Telegram Product Surface and Flow Map
tags: [taskmaster, telegram, product, flow-map, canonical-state]
created: 2026-05-24
status: draft
---
# Task 5.1 Telegram Product Surface and Flow Map
Source: `/.taskmaster/docs/prd-telegram-native-app-bot-wallet.md`
This map defines the first-release Telegram-native surface for Task 5.1 and keeps the financial core bound to the canonical
[`Funds Ledger and Escrow State Machine Specification`](Funds%20Ledger%20and%20Escrow%20State%20Machine%20Specification.md) and
Task 4 authorization/risk controls.
## 1) Channel split (first release)
| Area | Bot messages | Mini App | Web/Admin-only (first release) |
|---|---|---|---|
| Discovery & onboarding | `/start`, `/help`, `/link`, `/settings` | Onboarding cards, link prompts, fallback CTA | Full self-serve onboarding docs |
| Request browse/create | summary list + quick actions only | Full in-app list, create/edit request form | Full advanced catalog/search UI |
| Offers | receive offer summary, accept/reject shortcuts | full offer list and detail, offer accept/reject | Seller negotiation detail with admin context |
| Payment start | payment shortcut button + deep link payload | payment state UI + provider launch from in-app | Payment method setup, advanced failure handling |
| Delivery evidence | status update nudges + evidence deep link | upload evidence from in-app flow | Full media manager + admin-side attachments |
| Dispute | dispute open/summary + status check | open/respond/track dispute in app | Admin moderation, manual evidence review |
| Release/Refund | only status blockers and support handoff | confirmation request + release/refund request | Execute actions through canonical payment API |
| Notification settings | toggle on/off, blocked-state view | mirror of notification status and quiet mode | Full support/notification audit UI |
| Support | support contact + report IDs | support request and context handoff | Admin/operator console and action audit |
## 2) Canonical state mapping (all actions)
All Telegram actions must map to existing canonical states and API-side checks:
- `PurchaseRequest.status`: `pending``received_offers``in_negotiation``payment``processing``delivery``delivered``confirming``completed` / `cancelled`
- `Payment.escrowState`: `FUNDED` / `PARTIALLY_FUNDED` / `RELEASABLE` / `DISPUTED` / `RELEASING` / `RELEASED` / `REFUNDING` / `REFUNDED` / `FAILED` / `CANCELLED`
- `Dispute.status`: `OPEN` / `UNDER_REVIEW` / `RESOLVED_*` / `CLOSED` as appropriate
The only transitions that may modify funds, release eligibility, or dispute holds are those enforced by the canonical service
layer from the
[`Funds Ledger and Escrow State Machine Specification`](Funds%20Ledger%20and%20Escrow%20State%20Machine%20Specification.md).
## 3) Journey mapping by persona
### 3.1 Unauthenticated user
- See bot start/help and public support command.
- Any authenticated action routes to account-link flow.
- No request write actions without auth link.
### 3.2 Unlinked Telegram user
1. Start in bot or Mini App.
2. Prompt account linking (`/link` equivalent) using signed callback token.
3. Complete account link or return to web flow.
4. After link success, deep link returns user to prior action context (request, offer, payment, or dispute).
### 3.3 Linked buyer
- Browse/create requests, send offer review decision, start payment, upload delivery evidence, open disputes, monitor request state, request release/refund.
- No direct fund release path: Telegram actions read state and submit intent only.
### 3.4 Linked seller
- Receive offer and request notifications, update offer availability, review buyer disputes, upload shipping/proof artifacts.
- No direct payout mutation from Telegram; actions remain canonical requests to existing payment/dispute endpoints.
### 3.5 Admin/support
- Dedicated support context in admin view only (first release).
- Telegram-origin IDs and linked wallet/payment provenance shown in existing admin interfaces.
- Any manual override for Telegram-originated cases follows the same step-up/two-person policy as web flows when configured in
[`Security Ownership and Launch Decision Criteria`](Security%20Ownership%20and%20Launch%20Decision%20Criteria.md).
## 4) Deep-link and context contract
Deep-link entry points in order of priority:
1. `request` detail
2. `offer-review` (accept/reject)
3. `payment-start`
4. `dispute-open` / `dispute-track`
5. `delivery-evidence`
6. `account-link`
Rules:
- No raw IDs in clear URLs when not needed.
- Use short opaque or signed context IDs.
- Expire context tokens and bind to Telegram user + wallet/session.
- Every context hit verifies canonical ownership/state before rendering any action.
### Example startapp routes (illustrative)
- `.../startapp=request&rid=<opaque>`
- `.../startapp=offer&oid=<opaque>`
- `.../startapp=payment&pid=<opaque>`
- `.../startapp=dispute&did=<opaque>`
- `.../startapp=verify`
## 5) Release phasing
| Phase | Telegram surface | Backend dependency |
|---|---|---|
| Phase 0 (MVP) | bot + limited Mini App for state-safe buyer/seller actions; no Telegram-only financial mutations | Payment and ledger gates already enforced |
| Phase 1 | richer navigation, richer filters, deeper support surfaces | Identity linking + event fanout |
| Phase 2 | admin operator actions in Telegram | Additional ops hardening and audit scope |
## 6) Acceptance traceability
Directly mapped to PRD section "Task 1":
- Buyer/seller/admin/support journeys and entry contexts documented: **yes**
- Deep links and entry points defined with phase split: **yes**
- Canonical API/state mapping in table + journey sections: **yes**
- Non-native and admin surfaces identified: **yes**
- Backward compatibility with existing web flows retained: **yes**
## 7) Open items for implementation
1. Finalize concrete deep-link payload schema (opaque token claim set).
2. Finalize Mini App navigation IDs and route-to-state matrix in frontend routing.
3. Confirm unsupported actions for `DISPUTED`/`FAILED` states at Telegram action layer.

View File

@@ -0,0 +1,127 @@
---
title: Task 5.10 Telegram First-Class Authentication
tags: [audit, taskmaster, telegram, auth, testing]
---
# Task 5.10 Telegram First-Class Authentication
Date: 2026-05-24
## Scope
Task 5.10 implemented Telegram as a first-class Amanat auth provider.
- Backend now exposes `POST /api/auth/telegram`.
- The endpoint accepts Telegram Mini App `initData` or Telegram Login Widget payloads.
- Telegram signatures are verified server-side.
- Telegram-only users can be created without email/password.
- Standard Amanat JWT and refresh tokens are returned.
- Frontend Mini App sessions auto-authenticate from raw signed `initData`.
- The web login view includes a Telegram continuation button.
## Backend Implementation
Changed project: `backend`
- `src/models/User.ts`
- `email` is now optional with a sparse unique index.
- Added `authProvider: "email" | "google" | "telegram"`.
- Added `telegramVerified`.
- `src/models/TelegramLink.ts`
- Added `login_widget` as a supported source.
- `src/services/telegram/telegramService.ts`
- Added Telegram Login Widget verification.
- Added normalized Telegram auth identity helpers.
- `src/services/auth/authController.ts`
- Added `telegramAuth`.
- Auto-provisions Telegram users with nullable email.
- Rejects replayed Mini App data, stale auth dates, invalid signatures, bot accounts, blocked links, and inactive users.
- `src/services/auth/authRoutes.ts`
- Added public `POST /telegram`, mounted externally as `/api/auth/telegram`.
## Frontend Implementation
Changed project: `frontend`
- `src/lib/axios.ts`
- Added `/auth/telegram` as a public endpoint.
- Added `endpoints.auth.telegram`.
- `src/auth/context/jwt/action.ts`
- Added `signInWithTelegram`.
- Stores returned access/refresh tokens using the existing auth storage path.
- `src/sections/telegram/telegram-mini-app-shell.tsx`
- Auto-authenticates Mini App sessions once from raw `initData`.
- Shows an onboarding dialog for newly provisioned Telegram accounts.
- Keeps standard sign-in/dashboard fallbacks.
- `src/auth/components/form-socials.tsx`
- Added Telegram icon button.
- `src/auth/view/jwt/jwt-sign-in-view.tsx`
- Added Telegram sign-in handler for both Mini App `initData` and browser Login Widget payloads.
## Security Notes
- Frontend never trusts `initDataUnsafe` for auth; it only sends raw signed `initData`.
- Backend validates Telegram HMAC signatures using the configured bot token.
- Mini App `auth_date` freshness and replay window are enforced before JWT issuance.
- Login Widget `auth_date` freshness is enforced.
- Telegram bot accounts are rejected.
- Blocked `TelegramLink` records are rejected with 403.
- Telegram phone numbers are neither requested nor persisted.
- High-risk escrow and wallet actions still use the existing protected API and step-up rules.
## Verification
Backend:
```bash
npm run typecheck
npx jest __tests__/telegram-auth.test.ts __tests__/telegram-service.test.ts --runInBand --forceExit
```
Result:
- Backend typecheck passed.
- `__tests__/telegram-auth.test.ts` passed.
- `__tests__/telegram-service.test.ts` passed.
- 14 targeted backend tests passed.
Frontend:
```bash
npm test -- __tests__/auth/telegram-auth-action.test.ts __tests__/sections/telegram/telegram-mini-app-shell.test.tsx --runInBand
npx tsc --noEmit -p tsconfig.json
```
Result:
- Targeted frontend Jest tests passed.
- 7 targeted frontend tests passed.
- Full frontend typecheck still reports unrelated pre-existing errors in payment UI code outside Task 5.10:
- `src/components/payment/shkeeper-payment-widget.tsx`
- `src/web3/components/provider-payment.tsx`
## Acceptance Coverage
| Requirement | Status |
| --- | --- |
| New Telegram user auto-provisions and receives JWT | Covered by backend test |
| Returning user authenticates via Mini App initData | Covered through same route and link lookup |
| Returning user authenticates via Login Widget | Covered by backend test |
| Replayed Mini App initData rejected | Covered by backend test |
| Stale `auth_date` rejected | Covered by backend test |
| Blocked Telegram account returns 403 | Covered by backend test |
| Existing email/password users unaffected | Covered by backend test |
| Email optional for Telegram users | Covered by backend test and sparse user model |
| `isNewUser` triggers onboarding overlay | Covered by frontend shell behavior |
| High-risk actions retain step-up requirements | No high-risk action code changed |
## Operational Follow-up
If a deployed MongoDB already has an old non-sparse unique `email` index, replace it with the sparse unique index before creating multiple Telegram-only users:
```js
db.users.dropIndex("email_1")
db.users.createIndex({ email: 1 }, { unique: true, sparse: true })
```
Run this only after confirming the existing index name in the target database.

View File

@@ -0,0 +1,62 @@
---
title: Task 5.2 Telegram Identity Linking and Session Model
tags: [taskmaster, telegram, identity, session]
created: 2026-05-24
status: implemented-foundation
---
# Task 5.2 Telegram Identity Linking and Session Model
This document captures the first backend implementation pass for Task 5.2.
## Implemented foundation
- `TelegramLink` model maps one active Telegram user ID to one Amanat user.
- `TelegramSession` model stores Mini App session tokens, Telegram user ID,
optional Amanat user ID, initData fingerprint, auth date, source, expiry, and
active state.
- `/api/telegram/miniapp/verify` validates Telegram Mini App `initData` without
creating an Amanat session.
- `/api/telegram/miniapp/session` creates a Telegram session after verified
`initData`.
- `/api/telegram/link` lets an authenticated Amanat user link, read, or unlink a
Telegram account.
## Security model
- Backend verifies Mini App `initData` with Telegram's server-side signature
scheme before trusting Telegram identity.
- `initDataUnsafe` remains client-only display context and is not trusted for
backend authorization.
- Bot accounts are rejected.
- Stale `auth_date` values are rejected by configured TTL.
- Replayed Mini App payloads are rejected inside the configured replay window.
- Duplicate active Telegram-to-Amanat links are rejected.
## Configuration
| Variable | Purpose |
|---|---|
| `TELEGRAM_FEATURE_ENABLED` | Master feature flag |
| `TELEGRAM_MINIAPP_ENABLED` | Mini App session/linking flag |
| `TELEGRAM_BOT_TOKEN` | Server-side token used to verify Mini App signatures |
| `TELEGRAM_INITDATA_MAX_AGE_SEC` | Maximum accepted `auth_date` age |
| `TELEGRAM_INITDATA_REPLAY_WINDOW_MS` | Replay window for duplicate Mini App submissions |
| `TELEGRAM_SESSION_TTL_SEC` | Telegram session lifetime |
## Tests
- `backend/__tests__/telegram-service.test.ts`
- `backend/__tests__/telegram-routes.test.ts`
Coverage includes valid/invalid signature, expired initData, replay detection,
session persistence, link/unlink behavior, duplicate link rejection, route-level
Mini App validation, and secret non-leakage.
## Remaining work
- Move replay protection from in-memory maps to Redis or another shared store for
multi-instance deployments.
- Bind Telegram session tokens to first-party web session controls once the
final session architecture is implemented.
- Add admin/support controls for revoking or blocking Telegram links.

View File

@@ -0,0 +1,48 @@
---
title: Task 5.3 Telegram Bot Command and Notification Foundation
tags: [taskmaster, telegram, bot, notifications]
created: 2026-05-24
status: partial-foundation
---
# Task 5.3 Telegram Bot Command and Notification Foundation
This document captures the first backend bot foundation pass.
## Implemented foundation
- `/api/telegram/status` reports feature and webhook readiness without leaking
bot or webhook secrets.
- `/api/telegram/webhook` is mounted only when Telegram features are enabled.
- Webhook requests require `x-telegram-bot-api-secret-token` to match
`TELEGRAM_WEBHOOK_SECRET_TOKEN`.
- Webhook update handling dedupes by `update_id` or callback ID.
- Webhook handler classifies updates as:
- `command`
- `callback`
- `noop`
- `duplicate`
## Configuration
| Variable | Purpose |
|---|---|
| `TELEGRAM_WEBHOOK_ENABLED` | Enables webhook route |
| `TELEGRAM_WEBHOOK_SECRET_TOKEN` | Telegram webhook secret token |
| `TELEGRAM_WEBHOOK_REPLAY_WINDOW_MS` | Duplicate update replay window |
## Tests
- Service-level tests cover command/callback/noop classification and duplicate
handling.
- Route-level tests cover secret-token rejection and accepted command webhook
processing.
## Remaining work
- Implement actual command actions for `/start`, `/help`, `/link`, `/status`,
`/request`, `/offer`, `/payment`, `/dispute`, and `/settings`.
- Add signed opaque callback payload issuance and resolution.
- Add outbound notification delivery, blocked-bot detection, retry metrics, and
notification preferences.
- Persist webhook dedupe keys in shared storage instead of process memory.

View File

@@ -0,0 +1,42 @@
---
title: Task 5.4 Telegram Mini App Shell Implementation
tags: [taskmaster, telegram, mini-app, frontend]
created: 2026-05-24
status: partial-foundation
---
# Task 5.4 Telegram Mini App Shell Implementation
This document captures the first frontend implementation pass for the Telegram
Mini App surface.
## Implemented foundation
- Added `/telegram` route.
- Added `src/utils/telegram-webapp.ts` for safe Telegram WebApp context parsing.
- Added `TelegramMiniAppShell` with three first-pass states:
- unsupported browser context,
- Telegram detected but unlinked,
- Telegram detected and authenticated web user present.
- Added basic marketplace/escrow action cards for requests, request creation,
payment/escrow, chat, and account.
- Added Telegram chrome attachment helper for MainButton, BackButton, and haptic
feedback when available.
- Added safe-area and Telegram theme helpers.
## Tests
- `frontend/__tests__/utils-test/telegram-webapp.test.ts`
- `frontend/__tests__/sections/telegram/telegram-mini-app-shell.test.tsx`
Coverage includes standard browser fallback, Telegram context parsing,
safe-area/theme fallback, start parameter parsing, unsupported state, unlinked
state, and linked state rendering.
## Remaining work
- Wire Mini App shell to backend Telegram session endpoints.
- Add request/offer/payment/dispute data loading inside the Mini App route.
- Add upload and dispute action surfaces that call canonical backend APIs.
- Add Telegram BackButton routing behavior once final navigation stack is known.
- Add client matrix QA across iOS, Android, Desktop, and Web Telegram clients.

View File

@@ -0,0 +1,105 @@
---
title: Task 5.5 Telegram Payment and Wallet Strategy
tags: [taskmaster, telegram, payment, wallet, provider-adapter]
created: 2026-05-24
status: draft
---
# Task 5.5 Telegram Payment and Wallet Strategy
Source: `/.taskmaster/docs/prd-telegram-native-app-bot-wallet.md`
## 1) Strategy principle for this track
For first release, Telegram-native payment initiation must feed the existing canonical payment system and must **not** bypass
ledger-led state transitions. This means:
- payment proof and funds movement remain authorized by
[`Funds Ledger and Escrow State Machine Specification`](Funds%20Ledger%20and%20Escrow%20State%20Machine%20Specification.md),
- release/refund actions remain governed by service-layer guards and dispute holds,
- Telegram provider adapters are optional extensions, not a hard dependency.
In practice: Telegram can open payment flows through one canonical path first, and optional providers are layered later.
## 2) Option comparison
| Option | Fit for Telegram UX | Funds correctness risk | Regulatory / abuse risk | Complexity | Decision |
|---|---|---|---|---|---|
| Bot API Payments / Telegram Stars | Moderate | High (revenue and settlement mapping differs from existing ledger assumptions) | Medium | Medium | Deferred |
| Wallet Pay | Moderate | Medium-High (token-specific proof path) | High | High | Deferred |
| TON Pay | Medium | Medium-High (provider proof consistency + chain mapping) | High | High | Deferred |
| TON Connect + direct wallet pay | Medium | Medium if proven; requires strict recipient+amount+memo checks | High | High | Deferred |
| Request Network links | High (already introduced) | Low-Medium (existing adapter/service contract) | Medium | Low | **Primary for phase 1** |
| Existing Web3/crypto checkout path | High | Medium (requires recipient/amount/network proof hardening) | High | Medium | Keep as fallback only |
## 3) Selected phase-1 path
### Primary phase-1 strategy
- Telegram bot/Mini App starts the payment by invoking the same canonical payment intent creation API used for web flows.
- Payment provider is written as metadata; provider-neutral adapter remains optional and feature-flagged.
- Telegram identity metadata is persisted with the intent:
- `provider` (e.g., `request_network` or `shkeeper`)
- `telegramUserId`
- `telegramChatId` (if available)
- `launchSource` (`bot`, `mini_app`, `web_fallback`)
- `paymentReference`
- `invoiceId` / `orderId` / `requestId`
- `currency`, `amount`, `expiresAt`
- idempotency key (request-scoped)
### Optional providers
- Wallet Pay / TON Pay / TON Connect paths are documented as optional and must be implemented behind explicit
feature flags and dedicated adapter configuration only when:
- replay-safe proofs are standardized,
- canonical refund/release reconciliation supports provider-native evidence,
- monitoring/reporting has parity with existing paths.
- Until that date, these options are not required to merge for Task 5.1-5.9 handoff.
## 4) Data integrity requirements for any Telegram payment/Wallet path
Before any pay-in is accepted into escrow, enforce:
1. recipient validation (exact canonical recipient for the active escrow policy),
2. asset validation (supported chain + token),
3. amount validation against payment intent and currency precision,
4. memo/reference validation (idempotent and tamper-evident),
5. confirmation proof validation (`confirmed`, `on-chain`, or provider webhook),
6. canonical reconciliation step before mark-complete,
7. invariant re-check with ledger balances before release/refund operations.
These map directly to:
- [`Webhook Security Spec`](Webhook%20Security%20Spec.md)
- [`Payment Provider Adapter Spec`](Payment%20Provider%20Adapter%20Spec.md) (if optional provider is enabled)
- [`Funds Ledger and Escrow State Machine Specification`](Funds%20Ledger%20and%20Escrow%20State%20Machine%20Specification.md) (all financial state transitions).
## 5) Anti-divergence record model
Every Telegram payment intent should write a small canonical trace record with:
- canonical `paymentId`
- `canonicalSourceChannel = telegram`
- `telegramContext` payload hash
- `provider` and `providerReference`
- `idempotencyKey`
- `createdFrom` deep-link/command/callback identifier
- reconciliation status (`created`, `started`, `confirmed`, `failed`, `reconciled`)
This avoids duplicate checkout creation and makes Telegram callback replay and callback mismatch explainable in operations.
## 6) Refund/release compatibility
Refund/release logic remains unchanged from the canonical engine:
- cannot release funds not in releasable state,
- no release/release-amount mutation from Telegram without canonical precondition checks,
- disputed states block direct payout operations,
- release/refund events are only reflected after ledger updates succeed.
## 7) Open follow-up decisions
1. Decide whether to onboard `Wallet Pay` as phase-2 provider once adapter-level proof validation and reconciliation parity are met.
2. Decide whether to expose optional "wallet direct top-up" button in Mini App from day one or keep explicit link-first flow.
3. Finalize provider timeout and retry policy for Telegram-triggered intents under slow-network conditions.

View File

@@ -0,0 +1,38 @@
---
title: Task 5.6 Telegram Escrow Delivery Dispute Release Actions
tags: [taskmaster, telegram, escrow, disputes, release]
created: 2026-05-24
status: planned
---
# Task 5.6 Telegram Escrow Delivery Dispute Release Actions
Task 5.6 is not complete in this first Task 5 pass. This document defines the
implementation boundary required before Telegram shortcuts can affect escrow
state.
## Required behavior
- Telegram users can view current escrow state and next allowed actions.
- Delivery confirmation, evidence upload, refund request, dispute open/respond,
and release approval route through existing backend precondition checks.
- High-risk actions require fresh confirmation and audit logging with Telegram
context.
- Disputed or held funds cannot be released through Telegram shortcuts.
## Required backend constraints
- Use canonical purchase request, payment, dispute, and ledger state.
- Reject release/refund actions unless the funds state machine says the action is
allowed.
- Apply the same step-up and two-person policy as web/admin flows.
- Record Telegram user ID, chat/update ID, deep-link source, and callback token
ID in audit metadata.
## Required tests
- Buyer cannot confirm delivery before delivery state.
- Disputed funds cannot be released.
- Replayed Telegram callback cannot create a second action.
- Stale callback token is rejected.
- Telegram release/refund action emits the same audit fields as web release.

View File

@@ -0,0 +1,40 @@
---
title: Task 5.7 Telegram Admin Support Surface
tags: [taskmaster, telegram, admin, support]
created: 2026-05-24
status: planned
---
# Task 5.7 Telegram Admin Support Surface
Task 5.7 is not complete in this first Task 5 pass. This document defines the
admin/support scope required for Telegram-originated cases.
## Required admin/support visibility
- Telegram linked identity on user profile.
- Bot notification status and blocked-bot state.
- Mini App launch source and latest Telegram session metadata.
- Payment provider and wallet/payment references for Telegram-originated intents.
- Telegram webhook/callback event history for support investigation.
## Required admin/support actions
- Resend link prompt.
- Revoke Telegram link.
- Block Telegram bot access for a user.
- Inspect Telegram-originated event history.
- Escalate payment/dispute issues to canonical admin workflows.
## Security requirements
- Admin overrides remain gated by Task 4 step-up/two-person controls.
- Support can inspect Telegram context but cannot mutate funds state.
- Every support/admin action writes structured audit metadata.
## Required tests
- Support can read Telegram link metadata but cannot release/refund funds.
- Admin link revocation invalidates active Telegram link.
- Blocked Telegram user cannot create a new Mini App session.
- Admin override paths still require step-up when configured.

View File

@@ -0,0 +1,138 @@
---
title: Task 5.8 Telegram Security, Compliance, and Abuse Controls
tags: [taskmaster, telegram, security, compliance, abuse]
created: 2026-05-24
status: draft
---
# Task 5.8 Telegram Security, Compliance, and Abuse Controls
Source: `/.taskmaster/docs/prd-telegram-native-app-bot-wallet.md`
## 1) Threat model alignment
Threats are prioritized by impact to funds and identity:
- forged `initData`
- callback/replay abuse
- deep-link tampering
- phishing / link substitution
- bot token leakage
- spam / command flood
- account takeover
- wallet spoofing
- fake payment proof
- support impersonation
These correspond to existing concerns in
[`Threat Model - Amanat Escrow Platform`](Threat%20Model%20-%20Amanat%20Escrow%20Platform.md), especially **T16** (Telegram deep-link tampering).
## 2) Channel-specific controls
### 2.1 Telegram Web App identity
- Validate every Mini App session using Telegram `initData` with server-side signature verification.
- Never trust raw Telegram identifiers from client payload.
- Reject `initData` outside active bot context.
- Enforce short TTL and replay window for session and deep-link context tokens.
### 2.2 Bot callback and command surface
- Signed opaque callback payloads only; no mutable business state in query strings.
- Update callback handling is idempotent with dedupe key `(callbackId, telegramUpdateId, telegramUserId)`.
- Unknown callback payloads are rejected with audit logging and zero state mutation.
- Hard rate limits on `/api/telegram/webhook` per IP and per Telegram user.
### 2.3 Deep-link and redirect controls
- Signed link payloads with short expiry and bound context.
- Reject tampered/deprecated deep links by schema/nonce/version checks.
- No direct redirect to non-whitelisted hosts.
- Re-validate request ownership after deep-link state resolution.
### 2.4 Wallet and payment evidence controls
- Recipient, token, amount, and network checks must run before trust decisions.
- Payment proof replay rejected when payload hash differs from signed first-seen proof.
- Require provider-normalized reconciliation confirmation before marking funds complete.
### 2.5 Bot token and secrets
Secrets must never be in client-side code and must not be logged:
- `TELEGRAM_BOT_TOKEN`
- `TELEGRAM_WEBHOOK_SECRET`
- `TELEGRAM_WALLET_PAY_TOKEN` (if Wallet Pay enabled)
- `TON_CONNECT_MANIFEST_URL` / TON secret artifacts
- provider API secrets and webhook keys for request-network/SHKeeper paths
Apply Task 4 secret principles:
- separate sandbox and production tokens/environments,
- short rotation cadence,
- strict allowlist for secret-bearing hosts.
## 3) Compliance and secure configuration
### 3.1 CORS / CSP / framing
- Restrict Telegram endpoints to required app origins only.
- CSP in Telegram surfaces should avoid wildcard connect/script allowances.
- Add explicit `frame-ancestors` constraints for web entry surfaces that can be embedded.
### 3.2 Access and authorization
- Apply existing ownership rules from
[`Authorization Matrix - REST and Socket.IO`](Authorization%20Matrix%20-%20REST%20and%20Socket.IO.md).
- Telegram-originated actions use the same actor checks as web actions, including admin step-up and two-person control for high-value override actions.
- Dispute hold checks remain blocking for release/refund.
## 4) Abuse controls
1. **Spam / abuse**
- per-user command/chat throttle,
- per-IP flood detection on webhook.
2. **Account takeover**
- suspicious Telegram session behavior triggers notification disable and forced re-link.
3. **Wallet spoofing**
- signed wallet ownership checks + reconciliation proof checks.
4. **Fake proof**
- malformed proofs logged; never auto-complete payment with unverifiable proof.
5. **Support impersonation**
- support tools require signed admin context, with explicit role and actor logs.
## 5) Monitoring and alerting
Signals (and baseline targets):
- `telegram_update_failures_total` (high if sustained >0.5%)
- `telegram_initdata_invalid_total`
- `telegram_callback_replay_total`
- `telegram_payment_proof_mismatch_total`
- `telegram_notification_blocked_total`
- `telegram_payment_abnormal_status_jump_total`
Alert rules (initial):
- warning after 10 minutes, critical after 30 minutes or sustained spike above baseline.
- any sudden increase in payment mismatch ratio requires incident triage.
Where to emit:
- Sentry for exception/error trending,
- backend logs for structured events,
- existing dashboard metrics hooks and runbook scripts where available.
## 6) Operational controls required before launch
- Secrets rotation checklist completed (bot token and payment/provider keys).
- CORS/CSP reviewed and restricted to Telegram and known frontend origin.
- webhook secrets configured separately for sandbox and production.
- callback replay suppression validated in QA scenarios.
- support impersonation checks and audit trails validated.
## 7) Outstanding controls not yet implemented in this task layer
- Two-way admin alerting channel for Telegram-specific abuse currently depends on operations integration.
- Formal cryptographic replay store for deep-link one-time tokens (recommended before general rollout).
- Telegram payment-specific policy page and end-user risk consent copy.

View File

@@ -0,0 +1,156 @@
---
title: Task 5.9 QA, Rollout, Analytics, and Launch Runbooks
tags: [taskmaster, telegram, qa, rollout, analytics, runbook]
created: 2026-05-24
status: draft
---
# Task 5.9 QA, Rollout, Analytics, and Launch Runbooks
Source: `/.taskmaster/docs/prd-telegram-native-app-bot-wallet.md`
## 1) QA scope for launch readiness
### 1.1 Client matrix (required)
- Telegram iOS
- Telegram Android
- Telegram Desktop
- Telegram Web
- Light / dark themes
- Compact / fullscreen modes
- Normal and slow network
- Blocked bot scenario
- Expired / stale session scenario
- Payment cancellation and abort
- Unlinked user and re-link path
### 1.2 Functional QA checklist
1. Identity and linking
2. Request listing/detail in both bot and Mini App
3. Offer review flow
4. Payment initiation and cancel path
5. Delivery evidence upload
6. Dispute open/respond and status progression
7. Notification quiet/error state
8. Error and blocked-bot behavior
9. Support escalation handoff
### 1.3 Security/abuse QA
- forged/invalid `initData` rejection
- callback replay replayed twice: one success one no-op
- deep-link tampering
- wallet proof mismatch
- callback processing under invalid provider secrets
- admin override behavior and audit event capture
## 2) Environments and rollout
### 2.1 Environment separation
- `telegram-dev-bot` and `telegram-prod-bot` tokens and webhook endpoints must be distinct.
- No shared webhook secret between environments.
- QA and production payment fixtures remain isolated.
### 2.2 Feature flag sequence
1. **Development flag off**: no surface exposed
2. **Internal allowlist**: selected users only (buyer/seller/admin)
3. **Beta cohort**: controlled percentage and fixed org list
4. **Production enablement**: after runbook and KPI thresholds pass
### 2.3 Deployment safety
- If new surface increases payment mismatch or callback failure, immediately pause `TELEGRAM_SURFACE_ENABLED` and keep providers in read-only mode.
- Use existing rollback flow from incident operations and deployment runbooks.
## 3) Analytics and launch KPIs
Track these metrics daily for 14 days after stage advancement:
- activation rate (`activatedTelegramUsers / startedTelegramUsers`)
- link completion rate (`linkedUsers / startedLink`)
- request creation from Telegram (`telegramRequestsCreated`)
- offer response completion (`offerResponses / offersOpened`)
- payment started / payment completed (`telegramPaymentStart`, `telegramPaymentComplete`, `telegramPaymentFail`)
- dispute activity (`disputesOpened`, `disputesResolvedInTelegram`)
- release approvals from Telegram context (`telegramReleaseApprovals`)
- notification opt-outs (`notificationsOptOutRate`)
- callback duplicate ratio (`callbackReplay / callbackTotal`)
- average context resume latency (min and p95)
### Reporting destinations
- Sentry for exception and failure spikes
- application logs for workflow events
- existing monitoring dashboards for rate/latency anomalies
## 4) Launch runbooks
All runbooks are mandatory for Stage-1 rollout and post-launch incidents.
### 4.1 Bot outage
1. Validate webhook endpoint response health.
2. Switch status to notification-only mode where possible.
3. Confirm bot token and webhook URL.
4. Re-route urgent flows to web fallback.
5. Restore Telegram webhook + replay backlog after recovery.
### 4.2 Telegram API outage
1. Confirm external Telegram API status.
2. Temporarily disable deep-link / in-app actions that require Telegram callbacks.
3. Notify users of delayed updates.
4. Keep pending payment states in read-only mode until callback channel is restored.
### 4.3 Payment provider outage
1. Identify affected provider via provider mode and provider health flags.
2. Switch to read-only or alternative provider mode where configured.
3. Run reconciliation before re-enabling full writes.
4. Track stale pending payment age and contact support workflow.
### 4.4 Stuck payment
1. Check payment reconciliation queue and provider status.
2. Verify callback proof and on-chain confirmation.
3. Manually reconcile if allowed by protocol and policy.
4. Escalate if stale > 24h in funded or processing state.
### 4.5 Duplicate callback
1. Validate idempotency path executed correctly.
2. Confirm callback dedupe key retention window.
3. Compare event fingerprint for payload divergence.
4. Mark one path as duplicate no-op and keep audit trail.
### 4.6 Suspicious wallet proof
1. Block automated release/refund for the request.
2. Flag payment and mark for manual ops review.
3. Verify recipient, amount, and tx hash against chain/provider data.
4. Resume only after explicit approval.
### 4.7 Compromised bot token
1. Rotate bot token immediately.
2. Disable bot endpoints and clear webhook secret for 1 hour.
3. Validate callback signatures with new secret.
4. Resume in staged rollout mode with monitoring for 24h.
## 5) Stage exit criteria
- All required QA scenarios pass on iOS/Android/desktop/web.
- No critical webhook/payload mismatch regressions in 24h observation window.
- No unresolved payment stuck items > 24h after manual triage.
- Incident owners can execute all seven runbooks.
- Rollout metrics show non-degrading trend for the first two days.
## 6) Known rollout gaps
1. Fine-grained feature toggles for Telegram in existing observability dashboards are pending.
2. Admin analytics for Telegram-originated releases are schema-dependent and need implementation wiring.
3. Deep-link recovery behavior after prolonged Telegram link expiry still needs UX polishing.

View File

@@ -0,0 +1,572 @@
---
title: Threat Model - Amanat Escrow Platform
tags: [audit, security, threat-model, escrow, payments]
created: 2026-05-24
status: living
reviewers: [backend, security, product]
---
# Threat Model - Amanat Escrow Platform
> This document is the canonical threat model for the Amanat (Amn) financial escrow marketplace. All security specifications, authorization matrices, and hardening plans reference this document. It is a living document and must be updated when the attack surface changes.
## Cross-references
- [[Backend Stack Security and Refactor Assessment - 2026-05-24]] -- strategic stack assessment
- [[Platform Logical Audit - 2026-05-24]] -- detailed findings and code-level issues
- [[Security Architecture]] -- current authentication, authorization, and transport security
- [[Backend Architecture]] -- module structure and route registration
- [[Real-time Layer]] -- Socket.IO room model and event catalog
- [[Payment Flow - SHKeeper]] -- SHKeeper pay-in and webhook handling
- [[Payment Flow - DePay & Web3]] -- direct wallet pay-in and on-chain verification
- [[Escrow Flow]] -- escrow state machine and fund custody
- [[Dispute Flow]] -- dispute lifecycle and resolution
- [[Payout Flow]] -- seller payout via SHKeeper or manual admin signing
---
## 1. System Description
### 1.1 What is Amanat
Amanat is a financial escrow marketplace where buyers and sellers transact in cryptocurrency (USDT, USDC on BSC and Ethereum). The platform holds buyer funds in a custodial escrow wallet during order fulfillment and releases or refunds them based on delivery outcome or dispute resolution. It is not a simple CRUD marketplace; it is a financial platform with custody obligations.
### 1.2 Technology stack
| Layer | Technology |
|---|---|
| Frontend | Next.js (React), MUI, wagmi/Web3, Socket.IO client |
| Backend | Express 5, TypeScript, Mongoose, Socket.IO |
| Database | MongoDB (primary), Redis (caching, lockout, rate-limit) |
| Payments - hosted | SHKeeper self-hosted gateway at `pay.amn.gg` |
| Payments - direct | Web3/wagmi wallet transfer to custodial escrow address |
| Blockchain | BSC mainnet (chain ID 56), Ethereum; USDT/USDC BEP-20/ERC-20 |
| Realtime | Socket.IO 4.8 with client-driven room joins |
| Email | Nodemailer via SMTP |
| AI | OpenAI API for content generation, analysis, translation |
| Infrastructure | Docker Compose on single host, Nginx reverse proxy, CloudFlare upstream |
| Deployment | Watchtower auto-pull on `latest` tag (no staging gate) |
### 1.3 Key data flows
**Pay-in to escrow (SHKeeper path):**
Buyer selects offer -> Backend creates Payment intent -> SHKeeper allocates deposit address -> Buyer sends crypto -> SHKeeper webhook confirms -> Backend sets `escrowState=funded` -> Cascade: offer accepted, request status advanced, chat created.
**Pay-in to escrow (Web3 path):**
Buyer connects wallet -> Backend records pending Payment -> Buyer signs on-chain transfer to escrow wallet -> Backend verifies `eth_getTransactionReceipt` -> Sets `escrowState=funded` -> Same cascade.
**Escrow hold:**
Funds sit at custodial BSC wallet. `Payment.escrowState=funded`. No on-chain action occurs. Admin dashboard shows all funded escrows.
**Release to seller:**
Buyer confirms delivery (or auto-release timer) -> Admin initiates payout via SHKeeper Payouts API or manual wallet signing -> On-chain transfer to seller wallet -> `escrowState=released`.
**Refund to buyer:**
Dispute resolved with refund action, or pre-shipment cancellation -> Admin builds refund tx payload -> Admin signs and broadcasts -> `escrowState=refunded`.
**Dispute flow:**
Buyer or seller opens dispute -> Three-way chat created (buyer, seller, admin) -> Admin assigned -> Evidence gathered -> Admin resolves (release/refund/partial/reject) -> Financial side-effect dispatched manually.
**Webhook flow (SHKeeper):**
SHKeeper POSTs to `/api/payment/shkeeper/webhook` with HMAC-SHA256 signature -> Backend verifies raw-body signature -> Deduplicates by `(providerPaymentId, status)` tuple -> PaymentCoordinator serializes state update -> Cascade to offer/request/chat/notification.
---
## 2. Protected Assets
| Asset Class | Examples | Sensitivity | Storage |
|---|---|---|---|
| **User credentials** | bcrypt-hashed passwords, email | Critical | MongoDB `users` |
| **User sessions** | JWT access (7d), refresh (30d) | Critical | `localStorage` (browser), MongoDB `user.refreshTokens[]` |
| **Admin credentials and sessions** | Same as user but with `role: admin` | Critical | Same as user |
| **Payment records and funds state** | `Payment.status`, `escrowState`, amounts, tx hashes | Critical | MongoDB `payments` |
| **Escrow wallet private key** | Key controlling `ESCROW_WALLET_ADDRESS` | Critical | Platform operator custody (not in code) |
| **Wallet addresses** | Buyer wallet, seller payout wallet, escrow wallet | High | MongoDB `users.profile.walletAddress`, `Payment.blockchain` |
| **Webhook secrets** | `SHKEEPER_WEBHOOK_SECRET` | Critical | Env var |
| **API keys** | `SHKEEPER_API_KEY`, `OPENAI_API_KEY`, `JWT_SECRET` | Critical | Env var |
| **User personal data** | Email, name, phone, addresses | High | MongoDB `users` |
| **Transaction history** | All Payment, PurchaseRequest, Dispute records | High | MongoDB |
| **Private messages/chat** | Chat messages, dispute evidence | High | MongoDB `chats` |
| **Realtime event streams** | Socket.IO room broadcasts, notifications | Medium | In-process / Socket.IO |
| **Provider integration configs** | SHKeeper endpoint, callback URLs, CORS origin | High | Env var, hardcoded |
| **AI usage and budgets** | OpenAI call volume per user | Medium | No tracked storage currently |
| **Upload files** | Avatars, product images, dispute evidence | Medium | Host filesystem `uploads/` |
| **Notification preferences** | Per-user notification settings | Low | MongoDB `users` |
| **Blog content** | Posts, categories | Low | MongoDB `blogposts` |
---
## 3. Actors
| Actor | Access Level | Motivation | Risk Profile |
|---|---|---|---|
| **Buyer** | Authenticated (`role: buyer`). Can create requests, pay, confirm delivery, open disputes, rate. | Complete purchase, get refund if unsatisfied, dispute bad service. May attempt to bypass payment or confirm without delivery. | Medium. Motivated to fraud payment or delivery confirmation. |
| **Seller** | Authenticated (`role: seller`). Can create offers, templates, shop settings, receive payouts. | Receive payment for goods/services. May attempt to mark unshipped items as delivered or inflate prices post-acceptance. | Medium. Motivated to receive funds without delivery. |
| **Admin** | Authenticated (`role: admin`). Full access to user management, dispute resolution, manual payouts, payment dashboard. | Platform operations. Single point of trust for fund release. | High. If compromised, can release all escrowed funds. |
| **Support staff** | Authenticated (`role: support`). Read-only access, password resets, escalation. | Customer support operations. | Low-Medium. Limited destructive capability but can read sensitive data. |
| **Unauthenticated attacker** | No auth. Can reach public endpoints, Socket.IO handshake (rejected without JWT), webhook route (requires HMAC). | Financial gain, data theft, service disruption, cost abuse. | High. Broad attack surface including unauthenticated endpoints. |
| **Compromised user** | Valid JWT for a buyer/seller account. | Attacker uses stolen session to redirect payouts, open fraudulent disputes, exfiltrate data. | High. Hard to distinguish from legitimate activity. |
| **Compromised admin** | Valid JWT with admin role. | Full platform compromise: release escrows, manipulate payments, exfiltrate all user data. | Critical. Single compromised admin session can drain the escrow wallet. |
| **Malicious payment provider / webhook sender** | Can POST to webhook endpoint. | Inject false payment confirmations to trigger release. | High. If webhook verification is flawed, funds are at risk. |
| **Malicious blockchain actor** | Can submit any tx hash for verification. | Submit a tx hash for an unrelated successful transfer (wrong recipient/amount) and get it accepted as payment. | High. Current Web3 verification is incomplete. |
| **Insider threat** | Legitimate access to infrastructure, code, or admin tools. | Fund manipulation, data exfiltration, backdoor insertion. | Critical. Trust boundary cannot be fully technical. |
| **Supply-chain attacker** | Can compromise npm packages used by the project. | Remote code execution, credential theft, payment flow manipulation. | High. Real 2026 precedent with Axios, TanStack compromises. |
---
## 4. Trust Boundaries
### 4.1 Diagram
```mermaid
flowchart TB
subgraph Internet["INTERNET (Untrusted)"]
Browser["Browser / Next.js"]
MobileApp["Telegram Mini App"]
Attacker["Attacker"]
end
subgraph Edge["EDGE (Semi-Trusted)"]
CF["CloudFlare / Nginx"]
end
subgraph Backend["BACKEND (Trusted - single Docker host)"]
API["Express API\n:5001"]
SocketIO["Socket.IO Server"]
Uploads["/uploads filesystem"]
end
subgraph Data["DATA LAYER (Trusted)"]
Mongo[("MongoDB\n:27017")]
Redis[("Redis\n:6379")]
end
subgraph External["EXTERNAL SERVICES (Semi-Trusted)"]
SHK["SHKeeper\npay.amn.gg"]
BSC["BSC/Eth RPC\nbsc-dataseed"]
SMTP["SMTP / Email"]
OpenAI["OpenAI API"]
end
Browser -- "HTTPS / REST\nJWT in Authorization" --> CF
Browser -- "WSS / Socket.IO\nJWT in handshake" --> CF
MobileApp -- "HTTPS" --> CF
Attacker -.-> CF
CF -- "HTTP proxy" --> API
CF -- "WS proxy" --> SocketIO
API -- "Mongoose queries" --> Mongo
API -- "Cache / lockout / rate-limit" --> Redis
API -- "File read" --> Uploads
SocketIO -- "Room membership" --> Redis
API -- "POST /api/v1/payment_request\nX-Shkeeper-Api-Key" --> SHK
SHK -- "POST /api/payment/shkeeper/webhook\nHMAC-SHA256" --> API
API -- "eth_getTransactionReceipt" --> BSC
API -- "Nodemailer send" --> SMTP
API -- "GPT calls" --> OpenAI
```
### 4.2 Boundary descriptions
| Boundary | Crossing Protocol | Trust Assumption | Current Gap |
|---|---|---|---|
| Browser <-> Backend API | HTTPS, JWT in `Authorization` header, CORS allow-list | Browser is untrusted; JWT proves identity | Tokens stored in `localStorage` (XSS-exposed); 7-day access token lifetime |
| Browser <-> Socket.IO | WSS, JWT in handshake `auth.token` | Socket is untrusted until JWT verified | Room joins are client-driven with `userId` argument; server-side verification may be incomplete (`socketService.ts` citation needed) |
| Backend <-> MongoDB | Mongoose on `localhost:27017` (no TLS in dev) | Database is trusted; network is trusted (single host) | No TLS, no auth on MongoDB connection in default config |
| Backend <-> Redis | Redis client on `localhost:6379` | Redis is trusted; used for cache and lockout | No TLS; password optional (`REDIS_PASSWORD`) |
| Backend <-> SHKeeper | HTTPS, `X-Shkeeper-Api-Key` header outbound; HMAC-SHA256 inbound | SHKeeper is semi-trusted (self-hosted, operator controls) | Webhook returns 202 on all errors; no dead-letter queue; signature bypass in dev mode |
| Backend <-> Blockchain RPC | HTTPS JSON-RPC to `bsc-dataseed.binance.org` | RPC is semi-trusted (public endpoint, rate-limited) | Only `receipt.status` checked, not `Transfer` event recipient/amount; public RPC subject to throttling |
| Backend <-> Email (SMTP) | SMTP with credentials | SMTP provider is trusted | Verification codes logged in plaintext to stdout in all environments |
| Backend <-> OpenAI | HTTPS, `OPENAI_API_KEY` | OpenAI is trusted for data handling | No authentication required on AI endpoints; no per-user budget tracking |
| Admin UI <-> Backend | Same as Browser <-> Backend but with `role: admin` JWT | Admin is highly trusted | No step-up auth for payouts; admin role not verified on all admin routes |
| Mini App / Telegram <-> Backend | HTTPS, Telegram WebApp initData | Telegram identity is semi-trusted | Deep-link parameter tampering surface; `initData` validation must be strict |
---
## 5. Threat Catalog
### T01 -- Fake Payment Proof Submission
| Field | Value |
|---|---|
| **STRIDE** | Spoofing |
| **Affected assets** | Payment records, escrow funds |
| **Actors** | Buyer, malicious blockchain actor |
| **Description** | In the Web3 payment path, the backend accepts a `transactionHash` and verifies it via `eth_getTransactionReceipt`. The current `BSCTransactionVerifier` checks only `receipt.status === '0x1'` (transaction succeeded). It does not decode the ERC-20 `Transfer` event to verify that `to` matches `ESCROW_WALLET_ADDRESS` or that `value` matches the expected payment amount. A buyer can submit the hash of any successful USDT transfer (e.g., a 0.01 USDT transfer to their own wallet) and the system marks the payment as `completed` with `escrowState=funded`. |
| **Current state** | Vulnerable |
| **Required mitigation** | Decode `Transfer(address,address,uint256)` event from `receipt.logs`. Assert `to == ESCROW_WALLET_ADDRESS` and `value >= expectedAmount` (with decimal adjustment). Reject if mismatch. Reference: [[Payment Flow - DePay & Web3]], `decentralizedPaymentService.ts`. |
### T02 -- Webhook Replay Attack
| Field | Value |
|---|---|
| **STRIDE** | Spoofing / Tampering |
| **Affected assets** | Payment records, escrow funds |
| **Actors** | Malicious webhook sender, man-in-the-middle |
| **Description** | SHKeeper webhooks are verified using HMAC-SHA256 of the raw body against `SHKEEPER_WEBHOOK_SECRET`. However: (a) in development mode, signature verification is bypassed entirely; (b) the handler deduplicates by comparing `(metadata.shkeeperStatus, balance_fiat, paid)` within a 10-second window, but a replay outside that window with identical payload could be processed again; (c) there is no nonce, timestamp, or delivery-ID check from SHKeeper. An attacker who captures a valid webhook payload can replay it to mark a different payment as paid. |
| **Current state** | Partially mitigated |
| **Required mitigation** | Implement webhook delivery-ID tracking (store `delivery_id` or `(external_id, status)` as idempotency key). Reject webhooks older than a configurable window. Never bypass signature verification in any non-local environment. Add dead-letter storage for failed webhooks. Reference: [[Payment Flow - SHKeeper]], `shkeeperWebhook.ts`. |
### T03 -- Arbitrary Socket.IO Room Join
| Field | Value |
|---|---|
| **STRIDE** | Information disclosure / Elevation of privilege |
| **Affected assets** | Realtime event streams, private notifications, chat messages |
| **Actors** | Compromised user, unauthenticated attacker (with stolen JWT) |
| **Description** | Socket.IO room joins are client-driven. The client emits events like `join-user-room {userId}`, `join-seller-room {sellerId}`, `join-chat-room {chatId}`. The documentation explicitly warns that `userId` arguments "are NOT trusted blindly" but notes the authorization check in `socketService.ts` "needs verification." If the server does not verify `socket.data.user.id === userId` before joining, any authenticated user can subscribe to any other user's private notifications (`user-{otherUserId}`), seller events (`seller-{otherSellerId}`), or chat rooms (`chat-{otherChatId}`). |
| **Current state** | Vulnerable (verification status uncertain) |
| **Required mitigation** | Remove client-driven `join-*-room` events. After JWT handshake, the server should automatically join the socket to `user-{decoded.id}`. For role rooms (`seller-*`, `buyer-*`), derive membership from `decoded.role`. For chat rooms, verify that `decoded.id` is in `chat.participants`. Reference: [[Real-time Layer]], `socketService.ts`. |
### T04 -- Stolen Token Reuse
| Field | Value |
|---|---|
| **STRIDE** | Spoofing / Elevation of privilege |
| **Affected assets** | User sessions, admin sessions, all user-owned data |
| **Actors** | Compromised user, compromised admin |
| **Description** | Access tokens are JWTs stored in `localStorage` with a 7-day expiry. If an XSS vulnerability exists (or a compromised npm dependency injects a script), the attacker exfiltrates both `accessToken` and `refreshToken` from `localStorage`. The refresh token rotation mechanism detects reuse (if the same refresh token is presented twice, all sessions are invalidated), but the attacker can use the access token for up to 7 days without triggering rotation. For admin accounts, a stolen token gives full platform control including manual payout signing. |
| **Current state** | Partially mitigated (rotation exists, but storage and lifetime are weak) |
| **Required mitigation** | (1) Move refresh tokens to `httpOnly` secure cookies. (2) Reduce access token lifetime to 15-60 minutes. (3) Keep only the access token in memory (not localStorage). (4) Implement CSRF protection if cookies are used. Reference: [[Security Architecture]] section 11, [[Authentication Flow]]. |
### T05 -- Double Payout / Double Release
| Field | Value |
|---|---|
| **STRIDE** | Tampering / Repudiation |
| **Affected assets** | Payment records, escrow funds |
| **Actors** | Compromised admin, race condition (concurrent requests) |
| **Description** | The payout flow (`shkeeperPayoutService.createPayoutTask`) has idempotency: it checks for an existing outgoing Payment with the same `(purchaseRequestId, sellerOfferId, sellerId, provider, direction:'out')` in `pending/processing/completed` status. However: (a) the manual admin payout path (`admin-wallet-payout.tsx`) signs on-chain directly and then calls `confirmAdminTx`, with no corresponding idempotency guard; (b) the `PaymentCoordinator` serializes updates for a single payment, but two concurrent payout requests for different payments against the same escrow balance are not guarded; (c) `escrowState` is a mutable field, not derived from an immutable ledger. An admin (or attacker with admin credentials) could trigger two payouts for the same underlying funds. |
| **Current state** | Partially mitigated |
| **Required mitigation** | (1) Introduce an immutable funds ledger (`FundsAccount`, `LedgerEntry`) so that releasable balance is derived, not stored. (2) Add a distributed lock (Redis) around payout creation for a given escrow. (3) Require admin step-up authentication or two-person approval for payouts above a threshold. Reference: [[Escrow Flow]], [[Payout Flow]], [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 2. |
### T06 -- Dispute Bypass (Release During Active Dispute)
| Field | Value |
|---|---|
| **STRIDE** | Tampering |
| **Affected assets** | Escrow funds, dispute records |
| **Actors** | Compromised admin, buyer (if auto-release fires) |
| **Description** | Opening a dispute does not change `Payment.escrowState` away from `funded`. The Dispute Flow explicitly documents this: "Today, opening a dispute does not flip `Payment.escrowState` away from `funded`. An admin could theoretically still release the escrow before resolving the dispute." The Dispute API claims dispute creation "pauses any in-flight payout," but the implementation does not enforce this in the `PaymentCoordinator` or `PayoutService`. A release or refund can proceed while a dispute is active. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Add a `disputed` escrow state or a `disputeHold` flag on Payment that blocks all release/refund operations. (2) Enforce this in `PaymentCoordinator` and `shkeeperPayoutService` as a precondition. (3) Auto-set this flag in `DisputeService.createDispute`. (4) Clear only when dispute is resolved. Reference: [[Dispute Flow]], [[Escrow Flow]], [[Platform Logical Audit - 2026-05-24]] Finding 1. |
### T07 -- Email / Registration Abuse
| Field | Value |
|---|---|
| **STRIDE** | Denial of service / Spoofing |
| **Affected assets** | Email service, user accounts |
| **Actors** | Unauthenticated attacker |
| **Description** | The registration endpoint (`POST /api/auth/register`) triggers an email dispatch for every request. The resend endpoint (`POST /api/auth/resend-verification`) does the same. Rate limiting is disabled globally. An attacker can submit thousands of registration requests with random or victim email addresses, causing: (a) SMTP cost explosion; (b) email service reputation damage (spam complaints); (c) `TempVerification` collection bloat in MongoDB. Additionally, the 6-digit verification code is logged to stdout in all environments, leaking it to anyone with log access. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Enable rate limiting on `/api/auth/register` and `/api/auth/resend-verification` (e.g., 5 requests per 15 minutes per IP, 3 per email). (2) Remove `console.log` of verification codes in production. (3) Add CAPTCHA or proof-of-work for registration. (4) Add TTL index on `TempVerification` collection for auto-cleanup. Reference: [[Registration Flow]], [[Security Architecture]] section 9. |
### T08 -- AI Cost Abuse (Unauthenticated / Over-Budget Usage)
| Field | Value |
|---|---|
| **STRIDE** | Denial of service (financial) |
| **Affected assets** | AI usage budgets, `OPENAI_API_KEY` cost |
| **Actors** | Unauthenticated attacker, compromised user |
| **Description** | All AI endpoints (`/api/ai/generate`, `/api/ai/analyze`, `/api/ai/translate`, `/api/ai/assist`) currently have no authentication or caller identity verification, as documented in the Platform Logical Audit. Any client can call these endpoints and incur OpenAI API costs. Even if authentication is added, there is no per-user budget tracking, daily limit, or cost attribution. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Require Bearer JWT on all AI routes. (2) Implement per-user daily/monthly token budgets stored in MongoDB or Redis. (3) Rate-limit AI endpoints separately (e.g., 20 requests per hour per user). (4) Alert on abnormal cost spikes. Reference: [[Platform Logical Audit - 2026-05-24]] Finding 3. |
### T09 -- Admin Privilege Escalation
| Field | Value |
|---|---|
| **STRIDE** | Elevation of privilege |
| **Affected assets** | All platform assets |
| **Actors** | Compromised user, insider |
| **Description** | Admin role is stored as a string field `role: 'admin'` on the User document. If any route or service fails to enforce `roleGuard('admin')`, an ordinary user can access admin-only endpoints. The audit identifies that "admin-sensitive routes need explicit role enforcement, not just authentication." The admin bootstrap seed creates an admin with known credentials; if these are not rotated before launch, anyone who knows the seed email/password gains admin access. Additionally, there is no step-up authentication for high-risk admin operations (payouts, role changes, user deletion). |
| **Current state** | Partially mitigated (roleGuard exists but may not be applied consistently) |
| **Required mitigation** | (1) Audit every admin route for `roleGuard('admin')` enforcement. (2) Rotate all seed admin credentials before launch. (3) Implement step-up authentication (re-authentication or 2FA) for payouts, role changes, and user suspension. (4) Add audit logging for all admin actions. Reference: [[Security Architecture]] section 3, [[Backend Architecture]] section 4. |
### T10 -- Passkey / WebAuthn Bypass
| Field | Value |
|---|---|
| **STRIDE** | Spoofing |
| **Affected assets** | User credentials, sessions |
| **Actors** | Unauthenticated attacker, compromised user |
| **Description** | The passkey implementation has three critical flaws: (a) The `publicKey` field is stored as the literal string `'simulated-public-key'` rather than the actual COSE public key from the attestation object. A malicious client can register any credential ID under any user account. (b) Challenges are stored in an in-process `Map`, not Redis, breaking in multi-instance deployments. (c) Passkey-issued refresh tokens are not appended to `user.refreshTokens[]`, so the standard refresh endpoint rejects them, and the token reuse detection mechanism does not apply. |
| **Current state** | Vulnerable (passkeys are stubbed and non-functional for security) |
| **Required mitigation** | (1) Replace stub attestation with `@simplewebauthn/server`. Store real COSE public keys. (2) Move challenge storage to Redis with TTL. (3) Persist passkey-issued refresh tokens in `user.refreshTokens[]`. (4) Enforce monotonic counter to detect cloned authenticators. (5) Consider disabling passkey feature entirely until production-ready. Reference: [[Passkey (WebAuthn) Flow]], [[Platform Logical Audit - 2026-05-24]] Finding 2. |
### T11 -- Web3 Payment with Wrong Recipient / Token / Amount
| Field | Value |
|---|---|
| **STRIDE** | Spoofing |
| **Affected assets** | Payment records, escrow funds |
| **Actors** | Buyer, malicious blockchain actor |
| **Description** | Closely related to T01 but distinct in scope. The `POST /api/payment/decentralized/save` and `POST /api/payment/decentralized/update` endpoints are unauthenticated (per the audit findings). This means any client can create or modify decentralized payment records in the database without any on-chain verification. Combined with the incomplete `BSCTransactionVerifier`, an attacker can: (a) save a fake payment with arbitrary status; (b) update confirmations to make it appear verified; (c) trigger the cascade that accepts an offer and advances the purchase request. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Add authentication to all `/api/payment/decentralized/*` endpoints. (2) Add ownership verification (the authenticated user must be the buyer of the referenced purchase request). (3) Harden `BSCTransactionVerifier` per T01. (4) Remove test endpoints from production builds. Reference: [[Platform Logical Audit - 2026-05-24]] Finding 3, [[Payment Flow - DePay & Web3]]. |
### T12 -- Rate Limit Bypass
| Field | Value |
|---|---|
| **STRIDE** | Denial of service |
| **Affected assets** | All endpoints, email service, AI budget, database |
| **Actors** | Unauthenticated attacker |
| **Description** | `express-rate-limit` is explicitly disabled in the Express application (`app.ts:227`). The documentation describes recommended limits but they are not active. Without rate limiting, every endpoint is exposed to abuse: brute-force login attempts, registration email spam, AI cost explosion, payment endpoint flooding, and chat/message spam. Redis-based counters are wired but not enforced. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Enable rate limiting globally with tiered limits: auth paths (5-10 req/5 min/IP), payment paths (20 req/15 min/user), AI paths (20 req/hr/user), chat/messaging (30 req/min/user), file upload (10 req/hr/user), global default (100 req/15 min/IP). (2) Store counters in Redis. (3) Return `429 Too Many Requests` with `Retry-After` header. Reference: [[Security Architecture]] section 9, [[Backend Architecture]] section 3. |
### T13 -- XSS Leading to Token Theft
| Field | Value |
|---|---|
| **STRIDE** | Tampering / Information disclosure |
| **Affected assets** | User sessions, admin sessions |
| **Actors** | Unauthenticated attacker (via stored XSS), compromised user (via reflected XSS) |
| **Description** | Access tokens and refresh tokens are stored in `localStorage`. Any XSS vulnerability -- whether from a stored payload in chat messages, product descriptions, blog posts, dispute evidence, or user profile fields -- allows an attacker to read both tokens via `localStorage.getItem()`. Helmet CSP is in place but configured permissively for Web3 popup compatibility (`COEP: unsafe-none`, `COOP: same-origin-allow-popups`). React auto-escapes output, but `dangerouslySetInnerHTML` may be used in blog or description rendering. File uploads (dispute evidence, chat attachments) are served from `/uploads` without authentication, and a malicious SVG or HTML file could execute scripts in the context of the application. |
| **Current state** | Partially mitigated (CSP, React escaping, MIME validation exist) |
| **Required mitigation** | (1) Move tokens out of `localStorage` per T04. (2) Tighten CSP; evaluate whether `unsafe-none` COEP is necessary. (3) Audit all uses of `dangerouslySetInnerHTML`. (4) Serve uploads with `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`. (5) Add virus scanning (ClamAV) for uploaded files. Reference: [[Security Architecture]] section 11, section 7. |
### T14 -- npm Supply-Chain Compromise
| Field | Value |
|---|---|
| **STRIDE** | Tampering / Elevation of privilege / Information disclosure |
| **Affected assets** | All backend and frontend code, secrets, user data |
| **Actors** | Supply-chain attacker |
| **Description** | Both frontend and backend depend heavily on npm packages. The 2026 ecosystem has documented real compromises: Axios npm supply-chain compromise (March 2026, used in this project), TanStack npm compromise (May 2026, `@tanstack/react-query` used in this project), Express security releases including Multer issues (February 2026, `multer: ^2.0.2` specified). A compromised dependency can exfiltrate `JWT_SECRET`, `SHKEEPER_WEBHOOK_SECRET`, `OPENAI_API_KEY`, or inject malicious code into payment flows. The backend currently has no lockfile pinning verification, no npm provenance checking, and no dependency audit CI step. |
| **Current state** | Partially mitigated (some packages may be updated) |
| **Required mitigation** | (1) Pin all dependencies with lockfile; verify lockfile integrity in CI. (2) Enable npm provenance/signature verification where available. (3) Run `npm audit` / `yarn audit` in CI; fail on high/critical CVEs. (4) Review and update `multer` to >= 2.1.0. (5) Implement a secure build and supply-chain policy (see [[Backend Stack Security and Refactor Assessment - 2026-05-24]] doc 10). (6) Consider extracting security-critical backend code to Go/Kotlin with smaller dependency footprint. Reference: [[Backend Stack Security and Refactor Assessment - 2026-05-24]] supply-chain section. |
### T15 -- CSRF on State-Changing Endpoints
| Field | Value |
|---|---|
| **STRIDE** | Cross-site request forgery |
| **Affected assets** | User account settings, payment actions, admin operations |
| **Actors** | Unauthenticated attacker (via victim browser) |
| **Description** | The current architecture uses JWT in the `Authorization` header (not cookies), which provides inherent CSRF protection because browsers do not automatically attach `Authorization` headers on cross-origin requests. However, if the planned migration to `httpOnly` cookies for refresh tokens (recommended in T04) is implemented without CSRF tokens, the refresh endpoint becomes vulnerable to CSRF. Additionally, if any endpoint accidentally reads tokens from cookies or query parameters, CSRF becomes possible. |
| **Current state** | Mitigated (JWT in Authorization header) |
| **Required mitigation** | When migrating to `httpOnly` cookies: (1) Implement CSRF token (double-submit cookie or synchronizer token). (2) Apply `SameSite=Strict` or `SameSite=Lax` on cookies. (3) Verify `Origin` header on state-changing requests. (4) Ensure CORS is not overly permissive. Reference: [[Security Architecture]] section 4. |
### T16 -- Deep-Link / Parameter Tampering (Telegram Surface)
| Field | Value |
|---|---|
| **STRIDE** | Tampering / Spoofing |
| **Affected assets** | User identity, referral attribution |
| **Actors** | Unauthenticated attacker |
| **Description** | The Telegram Mini App surface accepts `initData` from the Telegram WebApp SDK. If the backend does not cryptographically verify `initData` using the Telegram Bot token, an attacker can forge the user identity, impersonate other Telegram users, or tamper with referral parameters in deep links (`/r/:code` redirect at `app.ts:274-278`). The referral code is passed as a query parameter and is not validated against a signed token. |
| **Current state** | Partially mitigated (verification status depends on implementation) |
| **Required mitigation** | (1) Verify Telegram `initData` signature using HMAC-SHA256 with the Bot token on every Mini App request. (2) Validate referral codes server-side before applying. (3) Do not trust client-supplied userId from Telegram context without signature verification. |
### T17 -- Payment Provider Outage / Chain RPC Outage
| Field | Value |
|---|---|
| **STRIDE** | Denial of service |
| **Affected assets** | Payment processing, escrow operations, user experience |
| **Actors** | Infrastructure failure (non-malicious) |
| **Description** | The platform depends on two external systems for financial operations: (a) SHKeeper gateway at `pay.amn.gg` for pay-in address allocation and payout execution; (b) BSC/Ethereum RPC (`bsc-dataseed.binance.org`) for transaction verification. If SHKeeper is down, new pay-in intents cannot be created (the code falls back to a demo URL in this case). If the RPC is down, Web3 payment verification cannot complete. The circuit breaker (`shkeeperFetch` in `shkeeperHealthCheck.ts`) trips after repeated failures, but the fallback is a demo URL, not a graceful degradation. Public BSC RPC endpoints are rate-limited. There is no dedicated RPC provider. |
| **Current state** | Partially mitigated (circuit breaker and wallet monitor exist as fallbacks) |
| **Required mitigation** | (1) Use a dedicated RPC provider (Ankr, QuickNode, Alchemy) with SLA guarantees. (2) Implement retry with exponential backoff for verification. (3) When SHKeeper is down, return a clear error to the buyer instead of a demo URL. (4) Store pending verifications for reconciliation when service resumes. (5) Document operational runbooks for provider outage. Reference: [[Payment Flow - SHKeeper]], [[Payment Flow - DePay & Web3]]. |
### T18 -- Insider Fund Manipulation
| Field | Value |
|---|---|
| **STRIDE** | Tampering / Repudiation |
| **Affected assets** | Escrow funds, payment records |
| **Actors** | Insider threat (admin, operator, developer) |
| **Description** | The escrow wallet is a single custodial BSC address controlled by the platform operator. The private key has unrestricted access to all in-flight escrowed funds. Admin users can initiate manual payouts via the `admin-wallet-payout.tsx` UI by connecting their wallet and signing transfers directly. There is no multi-signature requirement, no dual-approval for large payouts, no immutable audit ledger, and no automated anomaly detection on payout patterns. A single insider with access to the escrow private key or an admin session can drain all escrowed funds. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Move escrow wallet to a multi-sig solution (e.g., Gnosis Safe) requiring at least 2-of-3 signatures for any transfer. (2) Implement two-person approval for payouts above a configurable threshold. (3) Build an immutable funds ledger so every debit/credit is append-only and auditable. (4) Implement real-time alerting on unusual payout patterns. (5) Store one key in HSM. (6) Separate admin UI access from wallet signing capability. Reference: [[Escrow Flow]] custodial risk warning, [[Payout Flow]], [[Backend Stack Security and Refactor Assessment - 2026-05-24]] Phase 2. |
### T19 -- Seller Price Manipulation After Offer Acceptance
| Field | Value |
|---|---|
| **STRIDE** | Tampering |
| **Affected assets** | Payment amounts, escrow funds |
| **Actors** | Seller |
| **Description** | The Negotiation Flow documents that "`updateOffer` does not enforce status... Current code allows the price change, which is dangerous post-payment." A seller can modify the offer price after the buyer has already paid, potentially inflating the amount or changing terms. The escrow holds the original amount, but the displayed/agreed amount could be tampered with. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Reject `updateOffer` if `status !== 'pending'`. (2) Snapshot offer amount at payment creation time in the Payment document. (3) Use the Payment-embedded amount for all downstream operations, not the live offer. Reference: [[Platform Logical Audit - 2026-05-24]] Finding 18. |
### T20 -- Delivery Confirmation Brute Force
| Field | Value |
|---|---|
| **STRIDE** | Spoofing |
| **Affected assets** | Escrow release trigger |
| **Actors** | Buyer |
| **Description** | The delivery confirmation code is a 6-digit number (`Math.floor(100000 + Math.random()*900000)`) with 900,000 possible combinations. There is no rate limiting on verification attempts. The "manual fast-track" allows the buyer to confirm delivery at any time, even before the seller has shipped. A buyer could brute-force the confirmation code to trigger release without actually receiving the item. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Add Redis-backed rate limiting on delivery code verification (e.g., 5 attempts per 15 minutes per request). (2) Increase code entropy (alphanumeric, longer). (3) Restrict fast-track confirmation to `status: 'delivery'` only. (4) Add time-based invalidation. Reference: [[Platform Logical Audit - 2026-05-24]] Finding 8. |
### T21 -- Unauthenticated Data Exfiltration
| Field | Value |
|---|---|
| **STRIDE** | Information disclosure |
| **Affected assets** | User personal data, payment history, notifications |
| **Actors** | Unauthenticated attacker |
| **Description** | Multiple endpoints accept a `userId` parameter without authentication or ownership verification: `GET /api/payment/decentralized/history/:userId` returns any user's payment history; the legacy notification router accepts `?userId=` query parameter allowing notification read/modify for any user. The payment history endpoint exposes transaction hashes, amounts, counterparties, and wallet addresses. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Add Bearer JWT authentication to all data-access endpoints. (2) Enforce ownership: the authenticated user's ID must match the requested `userId` (or require admin role). (3) Audit all parameterized routes for missing auth middleware. Reference: [[Platform Logical Audit - 2026-05-24]] Finding 3. |
### T22 -- Verification Code Leakage via Logs
| Field | Value |
|---|---|
| **STRIDE** | Information disclosure |
| **Affected assets** | User credentials |
| **Actors** | Insider threat, log viewer |
| **Description** | The registration and password reset controllers log the generated 6-digit verification code to stdout via `console.log` in all environments, including production. Anyone with access to application logs (CloudWatch, Sentry breadcrumbs, docker logs) can read these codes and take over unverified accounts or reset passwords. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Guard all verification code logging behind `if (NODE_ENV !== 'production')`. (2) Implement a log redaction formatter that strips sensitive fields. (3) Audit all `console.log` and `logger` calls for sensitive data. Reference: [[Registration Flow]] warning. |
### T23 -- Payment State Machine Inconsistency
| Field | Value |
|---|---|
| **STRIDE** | Tampering / Repudiation |
| **Affected assets** | Payment records, escrow state |
| **Actors** | Race conditions, software bugs |
| **Description** | The platform has multiple overlapping state machines for the same entities. Payment has both `status` and `escrowState` that must remain consistent. The Dispute entity has three mutually incompatible status/action enum sets across the Data Model, API Reference, and Flow documents. The PurchaseRequest has different status enums across three documents. Ghost states exist (`confirmed`, `partial`, `finalized`, `archived`) that no automated flow sets. This inconsistency creates opportunities for state confusion where a payment appears in one state from one code path and a different state from another. |
| **Current state** | Vulnerable |
| **Required mitigation** | (1) Create a single canonical enum set for each entity, documented in one place. (2) Enforce valid transitions in a centralized state machine service. (3) Reject invalid transitions at the service layer, not just the controller. (4) Remove ghost states or document the transitions that set them. Reference: [[Platform Logical Audit - 2026-05-24]] Findings 5, 9, 20, 21. |
---
## 6. Risk Summary Matrix
| ID | Threat | Risk Level | Current State | Mitigation Owner | Reference |
|---|---|---|---|---|---|
| T01 | Fake payment proof (Web3) | Critical | Vulnerable | Backend / Payment | Task 2 subtask: Web3 verification hardening |
| T02 | Webhook replay attack | High | Partially mitigated | Backend / Payment | Task 2 subtask: Webhook security spec |
| T03 | Arbitrary Socket.IO room join | High | Vulnerable | Backend / Realtime | Task 2 subtask: Realtime authorization spec |
| T04 | Stolen token reuse | Critical | Partially mitigated | Backend / Auth | Task 2 subtask: Session architecture |
| T05 | Double payout / double release | Critical | Partially mitigated | Backend / Payment | Task 2 subtask: Funds ledger specification |
| T06 | Dispute bypass (release during dispute) | Critical | Vulnerable | Backend / Payment | Task 2 subtask: Escrow state machine |
| T07 | Email / registration abuse | Medium | Vulnerable | Backend / Auth | Task 2 subtask: Rate limiting |
| T08 | AI cost abuse | High | Vulnerable | Backend / AI | Task 2 subtask: Rate limiting + auth |
| T09 | Admin privilege escalation | High | Partially mitigated | Backend / Auth | Task 2 subtask: Authorization matrix |
| T10 | Passkey / WebAuthn bypass | High | Vulnerable | Backend / Auth | Task 2 subtask: Disable or fix passkeys |
| T11 | Web3 payment with wrong data (unauthenticated endpoints) | Critical | Vulnerable | Backend / Payment | Task 2 subtask: Auth on all financial endpoints |
| T12 | Rate limit bypass | High | Vulnerable | Backend / Infrastructure | Task 2 subtask: Rate limiting |
| T13 | XSS leading to token theft | High | Partially mitigated | Frontend / Backend | Task 2 subtask: Session architecture |
| T14 | npm supply-chain compromise | High | Partially mitigated | DevOps / Backend | Task 2 subtask: Supply-chain policy |
| T15 | CSRF on state-changing endpoints | Low | Mitigated | Backend / Auth | Monitor when cookie migration occurs |
| T16 | Deep-link / parameter tampering (Telegram) | Medium | Partially mitigated | Backend / Telegram | Task 2 subtask: Telegram initData verification |
| T17 | Payment provider / RPC outage | Medium | Partially mitigated | DevOps / Backend | Task 2 subtask: Operational runbooks |
| T18 | Insider fund manipulation | Critical | Vulnerable | Security / Operations | Task 2 subtask: Multi-sig wallet + ledger |
| T19 | Seller price manipulation post-acceptance | Medium | Vulnerable | Backend / Marketplace | Task 2 subtask: Offer status enforcement |
| T20 | Delivery confirmation brute force | Medium | Vulnerable | Backend / Marketplace | Task 2 subtask: Rate limiting + code entropy |
| T21 | Unauthenticated data exfiltration | High | Vulnerable | Backend / All | Task 2 subtask: Auth on all data endpoints |
| T22 | Verification code leakage via logs | Medium | Vulnerable | Backend / Auth | Immediate: remove log statements |
| T23 | Payment state machine inconsistency | High | Vulnerable | Backend / Payment | Task 2 subtask: Canonical state machine |
### Risk distribution
| Risk Level | Count | Threat IDs |
|---|---|---|
| Critical | 6 | T01, T04, T05, T06, T11, T18 |
| High | 10 | T02, T03, T08, T09, T10, T12, T13, T14, T21, T23 |
| Medium | 6 | T07, T16, T17, T19, T20, T22 |
| Low | 1 | T15 |
| **Total** | **23** | |
---
## 7. Assumptions and Constraints
### 7.1 In scope
- The Express 5 backend API server and all routes documented in [[Backend Architecture]].
- The Socket.IO realtime layer as documented in [[Real-time Layer]].
- The SHKeeper payment integration (pay-in webhook, payout API).
- The Web3/direct-wallet payment path (on-chain verification).
- The escrow state machine and fund custody model.
- The dispute lifecycle and its interaction with escrow.
- Authentication flows (email/password, Google OAuth, passkey/WebAuthn).
- Admin operations surface (dashboard, manual payouts, user management).
- Frontend token storage and session management.
- Infrastructure: single Docker host, CloudFlare/Nginx proxy, Watchtower deployment.
- npm supply chain for both frontend and backend.
- Telegram Mini App integration surface.
### 7.2 Out of scope (for this iteration)
- Physical security of hosting infrastructure.
- Social engineering attacks against platform operators (addressed partially by insider threat mitigations).
- Cryptographic weaknesses in blockchain protocols (BSC, Ethereum assumed secure).
- SHKeeper application internals (assumed to be a trusted component operated by the platform).
- Third-party email provider security (SMTP provider assumed secure).
- Client-side malware on user devices.
- Regulatory compliance (KYC/AML requirements not covered here).
- DNS hijacking (mitigated by CloudFlare proxy assumption).
### 7.3 Assumptions
1. **Hosting is a single Docker host** behind CloudFlare with Nginx. Internal service-to-service communication (MongoDB, Redis) is on localhost without TLS. This is acceptable for a single-host deployment but must be revisited if the architecture scales to multiple nodes.
2. **CloudFlare provides DDoS mitigation and TLS termination.** The threat model assumes HTTPS is enforced externally and the Nginx-to-Express hop is HTTP on localhost.
3. **SHKeeper is self-hosted and under platform control.** The webhook secret is shared only between the backend and the SHKeeper instance. If SHKeeper is compromised, webhook integrity is lost.
4. **MongoDB and Redis are not exposed to the internet.** They are bound to localhost on the Docker host. If this assumption is violated (e.g., misconfigured Docker network), the entire data layer is exposed.
5. **The escrow wallet private key is stored securely by the platform operator.** The threat model assumes the key is not in the codebase or env files. If this assumption is wrong, the exposure is total.
6. **Admin accounts are created via bootstrap seed and manually.** There is no self-service admin creation flow. The seed admin credentials must be rotated before any public launch.
7. **The frontend is a Next.js application served from the same origin or a CORS-allowlisted origin.** Cross-origin access is restricted by the CORS configuration. This assumption must hold for CSRF protection via `Authorization` header to be effective.
### 7.4 Constraints
1. **Single-host deployment limits defense-in-depth.** Network segmentation between services is not possible on a single host.
2. **No background job queue.** All async work is inline with request handlers or uses `setTimeout`/`setInterval`. This limits the ability to implement reliable retry, reconciliation, and scheduled operations.
3. **No external penetration testing has been performed.** All findings are based on code review and documentation analysis.
4. **Documentation and implementation may diverge.** Several findings in the [[Platform Logical Audit - 2026-05-24]] show that documentation describes security controls that may not be implemented in code. This threat model is based on documented behavior and known gaps; the actual attack surface may be larger.
5. **The platform is in pre-launch state.** Many security controls are documented as recommended but not yet implemented. This threat model reflects the current state, not the target state.
---
## Appendix A -- STRIDE Category Reference
| Category | Description |
|---|---|
| **S** -- Spoofing | Pretending to be another user or system |
| **T** -- Tampering | Modifying data or code |
| **R** -- Repudiation | Denying having performed an action |
| **I** -- Information disclosure | Exposing data to unauthorized parties |
| **D** -- Denial of service | Making a system unavailable |
| **E** -- Elevation of privilege | Gaining access beyond authorized level |
## Appendix B -- Threat-to-Mitigation Traceability
The following remediation documents (from the recommended documentation set in [[Backend Stack Security and Refactor Assessment - 2026-05-24]]) address specific threats:
| Remediation Document | Threats Addressed |
|---|---|
| Funds Ledger and Escrow State Machine Specification | T05, T18, T23 |
| Funds Ledger and Escrow State Machine Specification | T06, T19, T23 |
| Authorization Matrix | T09, T21 |
| Webhook Security Spec | T02 |
| Session and Auth Architecture | T04, T10, T13, T22 |
| Realtime Authorization Spec | T03 |
| Payment Provider Adapter Spec | T01, T11, T17 |
| Secure Build and Supply-Chain Policy | T14 |
| Backend Funds Migration and Operational Runbooks | T17 |
---
*This document was created on 2026-05-24 based on cross-referencing the platform audit documents, architecture documentation, and flow specifications. It must be reviewed and updated when: new payment providers are added, the escrow model changes, new authentication methods are introduced, the infrastructure topology changes, or significant new features are shipped.*

View File

@@ -0,0 +1,150 @@
---
title: Webhook Security Spec
tags: [webhooks, security, audit, payments]
created: 2026-05-24
status: advisory
reviewers: [backend, security, operations]
---
# Webhook Security Spec
This document defines signed callback handling for all payment and payout providers.
It closes the gaps in [[Security Architecture]] by turning webhook behavior into an explicit,
auditable contract.
The scope is inbound callbacks only:
- SHKeeper pay-in (`/api/payment/shkeeper/webhook`)
- SHKeeper payout (`/api/payment/shkeeper/payout/webhook`)
- Request Network (`/api/payment/request-network/webhook`)
- Manual/admin reconciliation channels (where applicable)
## 1. Canonical event envelope
All callbacks are normalized by [[Payment Provider Adapter Spec]] into:
```ts
type ProviderCallback = {
provider: "shkeeper" | "request_network" | "manual_wallet" | "admin_wallet" | string;
providerPaymentId: string;
purchaseRequestId?: string;
requestId?: string;
deliveryId?: string;
eventType: string; // e.g., paid, payout_completed, status_update
status: string; // provider-specific raw status
normalizedStatus: "pending" | "completed" | "failed" | "cancelled" | "released" | "refunded";
amount?: string;
currency?: string;
transactionHash?: string;
occurredAt?: string; // ISO 8601 if provided
receivedAt: string; // server-side receive time
rawFingerprint: string; // sha256(raw_body)
};
```
Callbacks are processed only through adapter entry points; provider-specific parsing remains private to the adapter.
## 2. Signature verification
### 2.1 Required mechanics
- Verify signatures against raw request bytes, **before JSON parsing**.
- Use constant-time comparison and short-circuit to 401/403 on mismatch.
- Never disable verification outside local-only test tooling.
- Store raw payload hash (`rawFingerprint`) for forensics and idempotency checks.
### 2.2 Provider headers
| Provider | Header(s) |
|---|---|
| SHKeeper | `x-shkeeper-signature` |
| Request Network | `x-request-network-signature` |
| Test override (local only) | explicitly documented in deployment notes, never in production |
If expected signature header is absent or malformed, treat as a non-retryable client error.
## 3. Replay prevention and idempotency
For each callback store and enforce one of:
- `deliveryId` + `provider` + `eventType`, or
- `(providerPaymentId, normalizedStatus, provider)` when provider has no delivery id.
Replay rules:
- First successful write path = **processed**.
- Same key seen again with no state change = **duplicate** (HTTP 200 response, no side effects).
- Same key seen for different payload hash = **conflict** (HTTP 409, captured to DLQ).
## 4. Unknown and duplicate behavior
| Condition | Response | Side effects |
|---|---|---|
| Signature valid, unknown `providerPaymentId` | `200` (`unknown_payment`) in v1 mode / `404` in strict mode | no state write, record DLQ entry for operator review |
| Known `providerPaymentId`, already terminal | `200` (`duplicate_terminal`) | no state write |
| Known `providerPaymentId`, stale status transition | `200` (`duplicate_or_out_of_order`) | no state write |
| Unknown signature | `401` | no state write |
| Malformed payload | `400` | no state write |
## 5. Retry semantics
- Callback consumers (providers) may retry:
- transient network failures,
- 5xx/provider internal timeouts,
- explicit retryable status from endpoint.
- Retry is triggered only on non-2xx codes for SHKeeper and Request Network.
- Recommended handler mapping:
- `401/400` = do not retry (hard fail),
- `409` = do not retry until manual release,
- `500/503` = retry.
## 6. Dead-letter and replay storage
Persist all failed callbacks for at least 7 days in append-only storage:
- `providerWebhookFailures`
- key fields: `provider`, `deliveryId`, `providerPaymentId`, `requestPath`, `requestHeaders`, `rawFingerprint`, `statusCode`, `errorCode`, `attemptCount`, `nextRetryAt`, `rawBodyRef`, `createdAt`.
- If storage is unavailable, fail closed and raise a high-severity ops alert.
Retention policy:
- 30 days for `success==true`,
- 180 days for `unknown_payment`, `repeated_conflict`, `signature_failure`,
- immediate alert if retry queue exceeds 500 entries for a provider.
## 7. Alerting thresholds
- `failed_webhook_count` over 1 minute:
- warning at `> 20`,
- critical at `> 100`.
- signature failures:
- warning at `> 5` in 5 minutes,
- critical at `> 20` in 5 minutes.
- duplicate ratio:
- warning if `duplicates / total >= 0.15` for 10 minutes.
- dead-letter growth:
- warning at `+200` new entries/hour,
- critical at `+500`/hour.
## 8. Required operator signals
Webhook health checks should expose:
- last-seen timestamp by provider,
- delivery backlog depth,
- per-status counters (`processed`, `duplicate`, `unknown`, `conflict`, `signature_failure`),
- DLQ length and oldest entry age.
## 9. Testing requirements
- Signature bypass tests (must remain false in staging/prod),
- replay/delivery-id duplicate tests,
- malformed payload tests,
- unknown payment tests,
- non-terminal duplicate suppression tests.
## Related
- [[Payment Provider Adapter Spec]]
- [[Error Codes]]
- [[Backend Funds Migration and Operational Runbooks]]

32
AGENTS.md Normal file
View File

@@ -0,0 +1,32 @@
# Agent Instructions
This documentation workspace uses Taskmaster as the source of truth for agent work.
## Taskmaster Workflow
- Before choosing implementation or documentation work, run `task-master next` from the repository root.
- Inspect the selected task before editing with `task-master show <id>`.
- When starting a task or subtask, mark it active:
- `task-master set-status --id=<id> --status=in-progress`
- Keep Taskmaster updated as work progresses:
- `task-master update-subtask --id=<id> --prompt="<what changed, what was learned, blockers, and verification>"`
- When work is complete and verified, mark it done:
- `task-master set-status --id=<id> --status=done`
- If work is paused or incomplete, leave the task in `in-progress` and add a progress note with the remaining work.
## Local Task Files
- Canonical Taskmaster data: `.taskmaster/tasks/tasks.json`
- Per-task markdown files: `.taskmaster/tasks/task-*.md`
- Source PRDs and audits: `.taskmaster/docs/*.md`
- Public share copy: `taskmaster-share/tasks.json`
Do not hand-edit `.taskmaster/tasks/tasks.json` or generated task markdown files unless the user explicitly asks for direct file maintenance. Prefer Taskmaster CLI commands so task state stays consistent.
## Expected Agent Behavior
- Treat pending Taskmaster tasks as the prioritized backlog.
- Respect task dependencies shown by `task-master next` and `task-master show`.
- Update the relevant task whenever edits, findings, verification results, or blockers materially change the state of the work.
- Before the final response, confirm that Taskmaster reflects the current task status.
- If `task-master` is unavailable, mention that in the final response and summarize the Taskmaster update that should be applied manually.

View File

@@ -0,0 +1,67 @@
# PRD: Telegram Phone Number Authentication
> Source spec: `.taskmaster/docs/prd-telegram-phone-auth.md`
> Related task: **Task 5.10** — Implement Telegram as first-class authentication provider
---
## Problem
The current Telegram integration is a *secondary linking layer*: users must already have an Amanat email/password or Google account before they can connect Telegram. This breaks the natural Telegram Mini App user journey — someone who opens the Mini App from Telegram has already proven phone ownership to Telegram and reasonably expects to be authenticated with zero extra signup friction.
## Core idea
Telegram accounts are phone-number-verified. When the backend verifies `initData` from the Mini App (already implemented), it has cryptographic proof that the requester controls a specific Telegram identity. That proof is sufficient to create and sign in to an Amanat account — no email, no password required.
The same principle applies to the **Telegram Login Widget**, the standard web-based flow that lets users authenticate on any browser page by confirming from their Telegram app.
## Two auth paths covered
| Context | Mechanism | Already have? |
|---|---|---|
| Inside Telegram Mini App | `initData` HMAC-SHA-256 (already verified server-side) | Partial — used for linking only |
| Any browser (web login page) | Telegram Login Widget → signed callback | Missing |
Both paths converge at a new `POST /auth/telegram` endpoint that: verifies the signature → looks up or creates the Amanat user → issues a JWT session.
## New user provisioning
When a Telegram user arrives with no existing account:
- Account is auto-created (`authProvider: telegram`, `telegramVerified: true`)
- No email required — email column becomes nullable (`sparse` unique index)
- Name pre-filled from Telegram profile
- Role defaults to `buyer`; user can change
- A non-blocking onboarding screen offers (but does not require) email capture and preference setup
## What does NOT change
- Existing email/password and Google OAuth users are unaffected
- High-risk actions (release, refund, dispute resolution) still require step-up confirmation regardless of auth provider
- Telegram is not made the *only* auth method — it becomes an equal alternative
- Raw phone number is never exposed to Amanat; Telegram user ID is the stable identity anchor
## Key acceptance criteria
1. New Telegram user completes auth inside Mini App without entering email or password.
2. Returning Telegram user gets the same session whether using Mini App or web Login Widget.
3. Replay protection and max-age validation apply to this endpoint.
4. Blocked Telegram accounts receive 403 and cannot circumvent by re-linking.
5. Auto-provisioned users have `authProvider: telegram`; existing users are unaffected.
6. "Continue with Telegram" button visible on the web login page.
7. High-risk action step-up policy is unchanged.
## Collision and merge rules
| Scenario | Behavior |
|---|---|
| New Telegram user | Auto-provision + TelegramLink |
| Returning Telegram user | Auth as linked user |
| Email user opens Mini App (not yet linked) | Prompt to link (existing task 5.2 flow) |
| Same Telegram account on two app users | Rejected — TelegramLink.telegramUserId is unique |
## See also
- `.taskmaster/docs/prd-telegram-phone-auth.md` — full specification with endpoint contract, model changes, and frontend integration details
- `PRD - Platform Audit Remediation Plan (2026-05-24).md` — security baseline this feature must not weaken
- Task 5.2 (Identity linking), Task 5.8 (Security controls)

74
Taskmaster/README.md Normal file
View File

@@ -0,0 +1,74 @@
# Taskmaster Dashboard
Generated from `.taskmaster/tasks/tasks.json` at 2026-05-24T07:15:25.199Z.
Taskmaster remains the canonical source of truth. Re-run:
```sh
node scripts/export-taskmaster-to-obsidian.mjs
```
## Status Summary
- done: 28
- in-progress: 3
- pending: 14
## Task Index
| ID | Title | Status | Priority | Dependencies |
| --- | --- | --- | --- | --- |
| [[Tasks/task-1|1]] | Stabilize Mermaid diagram rendering across documentation vault | done | medium | None |
| [[Tasks/task-1-1|1.1]] | Fix Security Architecture email/password sequence | done | medium | None |
| [[Tasks/task-1-2|1.2]] | Fix authentication login and refresh diagrams | done | medium | None |
| [[Tasks/task-1-3|1.3]] | Fix chat, delivery, dispute, OAuth, purchase request, referral, registration, and seller-offer diagrams | done | medium | None |
| [[Tasks/task-2|2]] | Implement platform audit remediation plan | done | high | None |
| [[Tasks/task-2-1|2.1]] | Secure unauthenticated endpoints and owner enforcement | done | high | None |
| [[Tasks/task-2-2|2.2]] | Re-enable and scope rate limiting | done | high | 1 |
| [[Tasks/task-2-3|2.3]] | Replace stubbed passkey/WebAuthn flow | done | high | 1 |
| [[Tasks/task-2-4|2.4]] | Strengthen DePay/Web3 payment verification | done | high | 1 |
| [[Tasks/task-2-5|2.5]] | Lock Socket.IO room joins to authenticated context | done | medium | 1 |
| [[Tasks/task-2-6|2.6]] | Enforce dispute hold before payout and release operations | done | medium | 1, 4 |
| [[Tasks/task-2-7|2.7]] | Align documentation, API references, and runtime enums | done | medium | 1, 2, 3, 4, 5, 6 |
| [[Tasks/task-3|3]] | Migrate payment architecture toward Request Network and internal funds management | done | high | 2 |
| [[Tasks/task-3-1|3.1]] | Define provider-neutral payment contracts and adapter | done | high | None |
| [[Tasks/task-3-2|3.2]] | Implement provider configuration, feature flags, and safe rollback | done | high | 3.1 |
| [[Tasks/task-3-3|3.3]] | Create internal funds and payment ledger model | done | high | 3.1 |
| [[Tasks/task-3-4|3.4]] | Build migration and indexing plan for existing SHKeeper records | done | high | 3.3 |
| [[Tasks/task-3-5|3.5]] | Implement Request Network pay-in intent and secure payment pages | done | high | 3.2 |
| [[Tasks/task-3-6|3.6]] | Implement signed Request Network webhook intake | done | high | 3.2 |
| [[Tasks/task-3-7|3.7]] | Implement reconciliation and repair jobs | done | high | 3.5, 3.6 |
| [[Tasks/task-3-8|3.8]] | Replace checkout and payment UI with provider-neutral flows | done | high | 3.5 |
| [[Tasks/task-3-9|3.9]] | Add payout/release and refund orchestration using ledger gates | done | high | 3.3, 3.7 |
| [[Tasks/task-3-10|3.10]] | Update release/refund APIs and marketplace release paths | done | high | 3.8, 3.9 |
| [[Tasks/task-3-11|3.11]] | Add comprehensive observability, runbooks, and incident controls | done | high | 3.6, 3.8, 3.9, 3.10 |
| [[Tasks/task-3-12|3.12]] | Add end-to-end integration, migration, and rollback test suites | done | high | 3.6, 3.10, 3.11 |
| [[Tasks/task-4|4]] | Define backend security and refactor strategy from latest audit | in-progress | high | None |
| [[Tasks/task-4-1|4.1]] | Assign security ownership and launch decision criteria | done | high | None |
| [[Tasks/task-4-2|4.2]] | Produce threat model for escrow platform | done | high | 1 |
| [[Tasks/task-4-3|4.3]] | Specify funds ledger and escrow state machine | pending | high | 2 |
| [[Tasks/task-4-4|4.4]] | Create authorization matrix for REST and Socket.IO | pending | high | 2 |
| [[Tasks/task-4-5|4.5]] | Decide session, passkey, and admin step-up architecture | pending | high | 2 |
| [[Tasks/task-4-6|4.6]] | Specify webhook security and provider adapter contracts | pending | high | 3 |
| [[Tasks/task-4-7|4.7]] | Define secure build and supply-chain policy | done | medium | 1 |
| [[Tasks/task-4-8|4.8]] | Make backend-core stack decision | pending | medium | 2, 3, 4, 5, 6, 7 |
| [[Tasks/task-4-9|4.9]] | Create migration and operational runbooks | pending | medium | 8 |
| [[Tasks/task-5|5]] | Deliver Telegram-native app, bot, and wallet experience | in-progress | high | None |
| [[Tasks/task-5-1|5.1]] | Define Telegram product surface and flow map | in-progress | high | None |
| [[Tasks/task-5-2|5.2]] | Build Telegram identity linking and session model | pending | high | 1 |
| [[Tasks/task-5-3|5.3]] | Implement bot command and notification foundation | pending | high | 1, 2 |
| [[Tasks/task-5-4|5.4]] | Build Telegram Mini App shell for marketplace workflows | pending | high | 1, 2 |
| [[Tasks/task-5-5|5.5]] | Add Telegram payment and wallet strategy | pending | high | 2, 4 |
| [[Tasks/task-5-6|5.6]] | Expose escrow, delivery, dispute, and release actions safely | pending | high | 4, 5 |
| [[Tasks/task-5-7|5.7]] | Add admin and support surface for Telegram-originated cases | pending | high | 2, 3, 5 |
| [[Tasks/task-5-8|5.8]] | Add security, compliance, and abuse controls for Telegram | pending | high | 2, 3, 5, 6 |
| [[Tasks/task-5-9|5.9]] | Prepare QA, rollout, analytics, and launch operations | pending | high | 3, 4, 5, 6, 7, 8 |
## Obsidian Tasks Query
```tasks
not done
tag includes #taskmaster
sort by priority
sort by description
```

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "1.1"
status: "done"
priority: "medium"
depends_on: []
parent_id: "1"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 1.1 - Fix Security Architecture email/password sequence
- [x] 1.1 - Fix Security Architecture email/password sequence #taskmaster #priority/medium #status/done 🔼 🆔 tm-1-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 1.1 |
| Status | done |
| Priority | medium |
| Dependencies | None |
| Parent | 1 - Stabilize Mermaid diagram rendering across documentation vault |
## Description
Normalize parser-sensitive sequence text in 01 - Architecture/Security Architecture.md.
## Details
Avoid semicolons and ambiguous inline punctuation in sequence messages.
## Verification
mmdc parse for the specific block.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "1.2"
status: "done"
priority: "medium"
depends_on: []
parent_id: "1"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 1.2 - Fix authentication login and refresh diagrams
- [x] 1.2 - Fix authentication login and refresh diagrams #taskmaster #priority/medium #status/done 🔼 🆔 tm-1-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 1.2 |
| Status | done |
| Priority | medium |
| Dependencies | None |
| Parent | 1 - Stabilize Mermaid diagram rendering across documentation vault |
## Description
Normalize parser-sensitive token and refresh-token sequence text in Authentication Flow.
## Details
Split method-like or expression-like message text into parser-safe plain text lines.
## Verification
mmdc parse for both Authentication Flow blocks.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "1.3"
status: "done"
priority: "medium"
depends_on: []
parent_id: "1"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 1.3 - Fix chat, delivery, dispute, OAuth, purchase request, referral, registration, and seller-offer diagrams
- [x] 1.3 - Fix chat, delivery, dispute, OAuth, purchase request, referral, registration, and seller-offer diagrams #taskmaster #priority/medium #status/done 🔼 🆔 tm-1-3
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 1.3 |
| Status | done |
| Priority | medium |
| Dependencies | None |
| Parent | 1 - Stabilize Mermaid diagram rendering across documentation vault |
## Description
Clean the remaining Mermaid sequence diagrams with invalid or ambiguous syntax.
## Details
Split multi-recipient arrows, remove parser-conflicting semicolon/expression text, and keep intent unchanged.
## Verification
Full vault mmdc parser sweep across all Mermaid blocks.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "1"
status: "done"
priority: "medium"
depends_on: []
parent_id: ""
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 1 - Stabilize Mermaid diagram rendering across documentation vault
- [x] 1 - Stabilize Mermaid diagram rendering across documentation vault #taskmaster #priority/medium #status/done 🔼 🆔 tm-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 1 |
| Status | done |
| Priority | medium |
| Dependencies | None |
| Parent | None |
## Description
Correct Mermaid syntax/rendering issues across the documentation vault and validate all Mermaid blocks.
## Details
Source PRD: .taskmaster/docs/prd-mermaid-diagram-rendering-stabilization.md. Scope covered 57 Mermaid blocks and 11 failing blocks. The source PRD records that all targeted files now pass mmdc parse validation and the full vault sweep passes.
## Verification
Run the same mmdc-based syntax validation across all Markdown Mermaid blocks and confirm zero parser failures in Obsidian/markdown previews.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2.1"
status: "done"
priority: "high"
depends_on: []
parent_id: "2"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2.1 - Secure unauthenticated endpoints and owner enforcement
- [x] 2.1 - Secure unauthenticated endpoints and owner enforcement #taskmaster #priority/high #status/done ⏫ 🆔 tm-2-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2.1 |
| Status | done |
| Priority | high |
| Dependencies | None |
| Parent | 2 - Implement platform audit remediation plan |
## Description
Require authenticateToken and owner/admin checks on exposed payment, AI, and legacy notification routes.
## Details
Derive notification userId from authenticated principal. Protect payment history and mutation endpoints. Restrict AI calls to authenticated users with per-user budgets. Add denied-access audit logs.
## Verification
Unauthorized callers receive 401/403; users cannot access or mutate other users' payments/notifications; admins retain authorized access.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2.2"
status: "done"
priority: "high"
depends_on: ["1"]
parent_id: "2"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2.2 - Re-enable and scope rate limiting
- [x] 2.2 - Re-enable and scope rate limiting #taskmaster #priority/high #status/done ⏫ 🆔 tm-2-2 ⛔ tm-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2.2 |
| Status | done |
| Priority | high |
| Dependencies | 1 |
| Parent | 2 - Implement platform audit remediation plan |
## Description
Restore global and route-tiered rate limits for public-sensitive paths.
## Details
Use stricter limits for auth, financial, AI, file upload, and verification paths. Keep public reads at relaxed limits. Add observability for 429 spikes.
## Verification
Exercise configured limits per tier and confirm expected 429 responses without blocking ordinary reads.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2.3"
status: "done"
priority: "high"
depends_on: ["1"]
parent_id: "2"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2.3 - Replace stubbed passkey/WebAuthn flow
- [x] 2.3 - Replace stubbed passkey/WebAuthn flow #taskmaster #priority/high #status/done ⏫ 🆔 tm-2-3 ⛔ tm-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2.3 |
| Status | done |
| Priority | high |
| Dependencies | 1 |
| Parent | 2 - Implement platform audit remediation plan |
## Description
Implement production-grade WebAuthn registration/authentication and shared challenge storage.
## Details
Use real attestation/assertion verification, Redis-backed TTL challenges, refresh-token persistence/rotation, and deterministic malformed/reused/expired challenge errors.
## Verification
Registration, login, replay, expired challenge, and refresh-token continuity tests pass.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2.4"
status: "done"
priority: "high"
depends_on: ["1"]
parent_id: "2"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2.4 - Strengthen DePay/Web3 payment verification
- [x] 2.4 - Strengthen DePay/Web3 payment verification #taskmaster #priority/high #status/done ⏫ 🆔 tm-2-4 ⛔ tm-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2.4 |
| Status | done |
| Priority | high |
| Dependencies | 1 |
| Parent | 2 - Implement platform audit remediation plan |
## Description
Verify transaction recipient, token contract, and amount, not only receipt success.
## Details
Decode ERC-20 Transfer logs, compare recipient against escrow address, validate token contract and decimals-adjusted minimum amount, store verifier evidence and idempotency fingerprint.
## Verification
Reject successful but wrong-recipient/wrong-token/underpaid tx hashes; accept only matching transfers.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2.5"
status: "done"
priority: "medium"
depends_on: ["1"]
parent_id: "2"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2.5 - Lock Socket.IO room joins to authenticated context
- [x] 2.5 - Lock Socket.IO room joins to authenticated context #taskmaster #priority/medium #status/done 🔼 🆔 tm-2-5 ⛔ tm-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2.5 |
| Status | done |
| Priority | medium |
| Dependencies | 1 |
| Parent | 2 - Implement platform audit remediation plan |
## Description
Remove trust in client-supplied user/buyer/seller room IDs.
## Details
Validate socket handshake token, derive server-side room membership, reject mismatched joins, and monitor suspicious join attempts.
## Verification
A user cannot subscribe to another user's rooms; legitimate realtime notifications still arrive.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2.6"
status: "done"
priority: "medium"
depends_on: ["1", "4"]
parent_id: "2"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2.6 - Enforce dispute hold before payout and release operations
- [x] 2.6 - Enforce dispute hold before payout and release operations #taskmaster #priority/medium #status/done 🔼 🆔 tm-2-6 ⛔ tm-1 ⛔ tm-4
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2.6 |
| Status | done |
| Priority | medium |
| Dependencies | 1, 4 |
| Parent | 2 - Implement platform audit remediation plan |
## Description
Add payment hold state and central release/refund guards that block disputed funds.
## Details
Introduce explicit dispute hold fields or state, enforce in PaymentCoordinator and payout/release services, return clear 409/423 responses, and backfill/report blocked payments.
## Verification
Open dispute blocks release/refund until resolved or explicitly overridden through authorized path.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2.7"
status: "done"
priority: "medium"
depends_on: ["1", "2", "3", "4", "5", "6"]
parent_id: "2"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2.7 - Align documentation, API references, and runtime enums
- [x] 2.7 - Align documentation, API references, and runtime enums #taskmaster #priority/medium #status/done 🔼 🆔 tm-2-7 ⛔ tm-1 ⛔ tm-2 ⛔ tm-3 ⛔ tm-4 ⛔ tm-5 ⛔ tm-6
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2.7 |
| Status | done |
| Priority | medium |
| Dependencies | 1, 2, 3, 4, 5, 6 |
| Parent | 2 - Implement platform audit remediation plan |
## Description
Normalize disputed/payment/request status docs and implementation references after security behavior changes.
## Details
Resolve mismatch around absent dispute module, endpoint names, status enums, and action names across Data Models, API Reference, and Flows.
## Verification
Docs match implemented routes, models, enum values, and state transitions.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "2"
status: "done"
priority: "high"
depends_on: []
parent_id: ""
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 2 - Implement platform audit remediation plan
- [x] 2 - Implement platform audit remediation plan #taskmaster #priority/high #status/done ⏫ 🆔 tm-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 2 |
| Status | done |
| Priority | high |
| Dependencies | None |
| Parent | None |
## Description
Address the code-backed security and consistency issues identified in the 2026-05-24 platform audit remediation PRD.
## Details
Source PRD: .taskmaster/docs/prd-platform-audit-remediation-plan-2026-05-24.md. Target backend hardening first, then documentation/runtime alignment. Delivery order suggested by PRD: security/auth, rate limiting, passkeys, Web3 verification, socket hardening, dispute hold controls, docs/API alignment.
## Verification
Add focused regression tests for route auth/ownership, passkey challenge/verification, Web3 verification semantics, socket authorization, rate limiting tiers, and payout/release dispute holds. Update API docs after behavior is implemented.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.1"
status: "done"
priority: "high"
depends_on: []
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.1 - Define provider-neutral payment contracts and adapter
- [x] 3.1 - Define provider-neutral payment contracts and adapter #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.1 |
| Status | done |
| Priority | high |
| Dependencies | None |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Create provider-agnostic payment interface with pay-in, webhook, payout/refund instruction creation, status lookup, and search methods.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.10"
status: "done"
priority: "high"
depends_on: ["3.8", "3.9"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.10 - Update release/refund APIs and marketplace release paths
- [x] 3.10 - Update release/refund APIs and marketplace release paths #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-10 ⛔ tm-3-8 ⛔ tm-3-9
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.10 |
| Status | done |
| Priority | high |
| Dependencies | 3.8, 3.9 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Refactor release routes to consume ledger state and provider-neutral contracts; deprecate direct simulation where possible.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.11"
status: "done"
priority: "high"
depends_on: ["3.6", "3.8", "3.9", "3.10"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.11 - Add comprehensive observability, runbooks, and incident controls
- [x] 3.11 - Add comprehensive observability, runbooks, and incident controls #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-11 ⛔ tm-3-6 ⛔ tm-3-8 ⛔ tm-3-9 ⛔ tm-3-10
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.11 |
| Status | done |
| Priority | high |
| Dependencies | 3.6, 3.8, 3.9, 3.10 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Track webhook latency, ledger imbalance, release failures, and reconciliation lag with alerts, on-call runbooks, and rollback procedures.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.12"
status: "done"
priority: "high"
depends_on: ["3.6", "3.10", "3.11"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.12 - Add end-to-end integration, migration, and rollback test suites
- [x] 3.12 - Add end-to-end integration, migration, and rollback test suites #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-12 ⛔ tm-3-6 ⛔ tm-3-10 ⛔ tm-3-11
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.12 |
| Status | done |
| Priority | high |
| Dependencies | 3.6, 3.10, 3.11 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Cover backend contract tests, provider fixture tests, UI acceptance, rollout simulation, DRYRUN migration, and release rollback rehearsals.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.2"
status: "done"
priority: "high"
depends_on: ["3.1"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.2 - Implement provider configuration, feature flags, and safe rollback
- [x] 3.2 - Implement provider configuration, feature flags, and safe rollback #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-2 ⛔ tm-3-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.2 |
| Status | done |
| Priority | high |
| Dependencies | 3.1 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Add runtime provider selection, rollout controls, env validation, and one-command kill-switch to revert to SHKeeper.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.3"
status: "done"
priority: "high"
depends_on: ["3.1"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.3 - Create internal funds and payment ledger model
- [x] 3.3 - Create internal funds and payment ledger model #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-3 ⛔ tm-3-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.3 |
| Status | done |
| Priority | high |
| Dependencies | 3.1 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Define FundsAccount, immutable LedgerEntry, and balance/query views for expected/held/releasable/released/refunded/disputed states.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.4"
status: "done"
priority: "high"
depends_on: ["3.3"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.4 - Build migration and indexing plan for existing SHKeeper records
- [x] 3.4 - Build migration and indexing plan for existing SHKeeper records #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-4 ⛔ tm-3-3
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.4 |
| Status | done |
| Priority | high |
| Dependencies | 3.3 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Add DB indexes for payment/provider fields and run backfill to produce a migration report with skipped/failed/ambiguous historical entries.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.5"
status: "done"
priority: "high"
depends_on: ["3.2"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.5 - Implement Request Network pay-in intent and secure payment pages
- [x] 3.5 - Implement Request Network pay-in intent and secure payment pages #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-5 ⛔ tm-3-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.5 |
| Status | done |
| Priority | high |
| Dependencies | 3.2 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Add Request Network intent/service layer, secure payment URLs, and validation of network/currency/reference/amount before setting paid state.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.6"
status: "done"
priority: "high"
depends_on: ["3.2"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.6 - Implement signed Request Network webhook intake
- [x] 3.6 - Implement signed Request Network webhook intake #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-6 ⛔ tm-3-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.6 |
| Status | done |
| Priority | high |
| Dependencies | 3.2 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Build /api/payment/request-network/webhook with raw-body signature verification, idempotent delivery handling, and immutable event audit rows.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.7"
status: "done"
priority: "high"
depends_on: ["3.5", "3.6"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.7 - Implement reconciliation and repair jobs
- [x] 3.7 - Implement reconciliation and repair jobs #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-7 ⛔ tm-3-5 ⛔ tm-3-6
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.7 |
| Status | done |
| Priority | high |
| Dependencies | 3.5, 3.6 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Add periodic Request Network payment search/reconciliation and manual replay support to fix missed or delayed events.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.8"
status: "done"
priority: "high"
depends_on: ["3.5"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.8 - Replace checkout and payment UI with provider-neutral flows
- [x] 3.8 - Replace checkout and payment UI with provider-neutral flows #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-8 ⛔ tm-3-5
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.8 |
| Status | done |
| Priority | high |
| Dependencies | 3.5 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Introduce provider-neutral payment components, remove SHKeeper walletAddress assumptions for RN, and keep legacy path only for existing SHKeeper records.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "3.9"
status: "done"
priority: "high"
depends_on: ["3.3", "3.7"]
parent_id: "3"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3.9 - Add payout/release and refund orchestration using ledger gates
- [x] 3.9 - Add payout/release and refund orchestration using ledger gates #taskmaster #priority/high #status/done ⏫ 🆔 tm-3-9 ⛔ tm-3-3 ⛔ tm-3-7
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3.9 |
| Status | done |
| Priority | high |
| Dependencies | 3.3, 3.7 |
| Parent | 3 - Migrate payment architecture toward Request Network and internal funds management |
## Description
Create release/refund instruction queue with signer, tx payloads, provider tx hash, and strict ledger invariants before action.
## Details
_No details._
## Verification
_No verification strategy._

View File

@@ -0,0 +1,39 @@
---
taskmaster_id: "3"
status: "done"
priority: "high"
depends_on: ["2"]
parent_id: ""
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 3 - Migrate payment architecture toward Request Network and internal funds management
- [x] 3 - Migrate payment architecture toward Request Network and internal funds management #taskmaster #priority/high #status/done ⏫ 🆔 tm-3 ⛔ tm-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 3 |
| Status | done |
| Priority | high |
| Dependencies | 2 |
| Parent | None |
## Description
Plan and implement provider-neutral payment flows, Request Network pay-in support, funds ledger, webhook reconciliation, release/refund orchestration, UI migration, and SHKeeper decommissioning.
## Details
Source PRD: .taskmaster/docs/prd-request-network-migration-and-funds-management.md. The PRD recommends phased migration behind a provider adapter, Secure Payment Pages first, platform-controlled escrow/payee destination, and a first-class internal funds ledger before release/refund enforcement.
Post-completion update: Task 3 now includes a CI-safe focused verification command for the provider-neutral payment migration plus optional Trezor safekeeping. Trezor safekeeping is optional by default via TREZOR_SAFEKEEPING_REQUIRED=false and only gates release/refund confirmation when explicitly enabled. Vault references: 04 - Flows/Trezor Safekeeping Flow.md, 03 - API Reference/Trezor API.md, and 08 - Operations/Payment and Trezor Verification Report.md.
## Verification
Use feature flags, provider fixture tests, webhook signature/idempotency tests, ledger invariant tests, migration dry-run reports, and limited cohort rollout before default provider switch.
Focused verification command: npm test -- __tests__/payment-adapter-registry.test.ts __tests__/request-network-adapter.test.ts __tests__/request-network-payin.test.ts __tests__/request-network-webhook.test.ts __tests__/payment-ledger.model.test.ts __tests__/payment-ledger.service.test.ts __tests__/payment-migration.service.test.ts __tests__/payment-release-refund-orchestration.test.ts __tests__/payment-release-refund-routes.test.ts __tests__/payment-reconciliation.service.test.ts __tests__/payment-observability-redaction.test.ts __tests__/payment-observability-events.test.ts __tests__/trezor-safekeeping.service.test.ts --runInBand. Expected result: 13 suites, 64 tests passing. Also run npm run typecheck.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "4.1"
status: "done"
priority: "high"
depends_on: []
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 4.1 - Assign security ownership and launch decision criteria
- [x] 4.1 - Assign security ownership and launch decision criteria #taskmaster #priority/high #status/done ⏫ 🆔 tm-4-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.1 |
| Status | done |
| Priority | high |
| Dependencies | None |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Define who owns security decisions and what must be true before public launch or migration work proceeds.
## Details
Completed. Produced 09 - Audits/Security Ownership and Launch Decision Criteria.md. Contains: RACI matrix (10 decision areas, 6 roles, fallback rules), 42-item launch safety gate checklist with Required/Strongly Recommended/Deferred classifications cross-referenced to audit findings, launch priority decision (harden first, redesign deferred), external pentest decision (yes, before public launch, with compensating controls), 12-item deferred decisions register with owners and deadlines.
## Verification
Written owner/RACI and launch gate checklist are accepted by leadership and engineering.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "4.2"
status: "done"
priority: "high"
depends_on: ["1"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 4.2 - Produce threat model for escrow platform
- [x] 4.2 - Produce threat model for escrow platform #taskmaster #priority/high #status/done ⏫ 🆔 tm-4-2 ⛔ tm-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.2 |
| Status | done |
| Priority | high |
| Dependencies | 1 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Document protected assets, actors, trust boundaries, and abuse cases for the financial marketplace.
## Details
Completed. Produced 09 - Audits/Threat Model - Amanat Escrow Platform.md. Contains: system description, 17 protected asset classes with sensitivity ratings, 11 actors with access levels and risk profiles, trust boundary diagram (Mermaid) with 10 boundary descriptions and current gaps, 23-threat catalog (T01-T23) with STRIDE categories and specific code-path references, risk summary matrix (6 Critical, 10 High, 6 Medium, 1 Low), threat-to-mitigation traceability matrix mapping 9 remediation docs to specific threats. Living document. Open verification items: Socket.IO room auth in socketService.ts, Telegram initData validation, actual lockfile versions for multer/axios/tanstack.
## Verification
Threat model maps each high-risk finding to at least one mitigation task or accepted risk.

View File

@@ -0,0 +1,37 @@
---
taskmaster_id: "4.3"
status: "done"
priority: "high"
depends_on: ["2"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:26:29.052Z"
---
# 4.3 - Specify funds ledger and escrow state machine
- [x] 4.3 - Specify funds ledger and escrow state machine #taskmaster #priority/high #status/done ⏫ 🆔 tm-4-3 ⛔ tm-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.3 |
| Status | done |
| Priority | high |
| Dependencies | 2 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Define canonical money movement and legal state transitions before refactor or provider migration.
## Details
Completed. Produced `09 - Audits/Funds Ledger and Escrow State Machine Specification.md` (states, transitions, invariants, and migration guidance for canonical funds/escrow transitions).
Create specs for FundsAccount, LedgerEntry, FundsBalance, gross paid, provider fees, platform fees, held, disputed, releasable, released, refunded, idempotency keys, reconciliation behavior, purchase request states, payment states, escrow/funds states, dispute states, valid transitions, forbidden transitions, and release/refund/admin override preconditions.
## Verification
Spec can be used to reject double-release, release-during-dispute, underfunded payout, and ambiguous provider-event scenarios.

View File

@@ -0,0 +1,37 @@
---
taskmaster_id: "4.4"
status: "done"
priority: "high"
depends_on: ["2"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:26:29.052Z"
---
# 4.4 - Create authorization matrix for REST and Socket.IO
- [x] 4.4 - Create authorization matrix for REST and Socket.IO #taskmaster #priority/high #status/done ⏫ 🆔 tm-4-4 ⛔ tm-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.4 |
| Status | done |
| Priority | high |
| Dependencies | 2 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Map every endpoint and realtime event to access level, ownership checks, state preconditions, rate-limit tier, and audit-log requirement.
## Details
Completed. Produced `09 - Audits/Authorization Matrix - REST and Socket.IO.md` and `09 - Audits/Realtime Authorization Spec.md`.
Include public/authenticated/owner/buyer/seller/admin/support/service-role classifications. Socket.IO rooms must be server-derived from authenticated identity, not client-supplied user IDs.
## Verification
No route or socket event remains unmapped; implementation tasks can reference matrix rows directly.

View File

@@ -0,0 +1,37 @@
---
taskmaster_id: "4.5"
status: "done"
priority: "high"
depends_on: ["2"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:26:29.052Z"
---
# 4.5 - Decide session, passkey, and admin step-up architecture
- [x] 4.5 - Decide session, passkey, and admin step-up architecture #taskmaster #priority/high #status/done ⏫ 🆔 tm-4-5 ⛔ tm-2
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.5 |
| Status | done |
| Priority | high |
| Dependencies | 2 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Choose browser session model and high-risk admin authentication requirements.
## Details
Completed. Produced `09 - Audits/Session and Authentication Architecture Decision.md`.
Decide localStorage versus httpOnly cookies, access/refresh token lifetimes, CSRF strategy, refresh rotation, WebAuthn requirements, OAuth requirements, device/session revocation, and whether payouts/role changes require step-up authentication or two-person approval.
## Verification
Decision record lists chosen model, rejected alternatives, migration cost, and required implementation tasks.

View File

@@ -0,0 +1,37 @@
---
taskmaster_id: "4.6"
status: "done"
priority: "high"
depends_on: ["3"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:26:29.052Z"
---
# 4.6 - Specify webhook security and provider adapter contracts
- [x] 4.6 - Specify webhook security and provider adapter contracts #taskmaster #priority/high #status/done ⏫ 🆔 tm-4-6 ⛔ tm-3
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.6 |
| Status | done |
| Priority | high |
| Dependencies | 3 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Define provider-neutral payment interface and signed webhook processing rules.
## Details
Completed. Produced `09 - Audits/Webhook Security Spec.md` and `09 - Audits/Payment Provider Adapter Spec.md`.
Document createPayInIntent, getPayInStatus, handleProviderWebhook, createHostedPaymentLink, createReleaseInstruction, createRefundInstruction, getPayoutStatus, searchProviderPayments, raw-body signature verification, replay prevention, delivery ID idempotency, duplicate/unknown event behavior, retry semantics, dead-letter/replay storage, and alert thresholds.
## Verification
Contracts cover SHKeeper legacy, Request Network, manual/admin wallet, invalid signatures, duplicate deliveries, and missed webhook reconciliation.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "4.7"
status: "done"
priority: "medium"
depends_on: ["1"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:15:25.199Z"
---
# 4.7 - Define secure build and supply-chain policy
- [x] 4.7 - Define secure build and supply-chain policy #taskmaster #priority/medium #status/done 🔼 🆔 tm-4-7 ⛔ tm-1
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.7 |
| Status | done |
| Priority | medium |
| Dependencies | 1 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Reduce npm/dependency compromise risk across frontend and any remaining Node services.
## Details
Completed. Produced 09 - Audits/Secure Build and Supply-Chain Policy.md. 11 sections + 3 appendices: lockfile policy (npm ci mandatory), dependency update cadence (biweekly routine, immediate security-critical), advisory monitoring with SLAs (Critical 24h, High 72h, Medium 1 week), known exposure register with 5 open 2026 CVEs (multer, axios, tanstack, express, node) and SLA deadlines, npm provenance policy, secrets rotation schedule for all 10 secret types, production build reproducibility requirements, frontend vs backend risk separation with interim policy, incident response for 3 scenarios, CI/CD enforcement checklist with Gitea Actions YAML example.
## Verification
Policy is actionable in CI and includes response steps for compromised package, leaked token, and vulnerable dependency alerts.

View File

@@ -0,0 +1,37 @@
---
taskmaster_id: "4.8"
status: "done"
priority: "medium"
depends_on: ["2", "3", "4", "5", "6", "7"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:26:29.052Z"
---
# 4.8 - Make backend-core stack decision
- [x] 4.8 - Make backend-core stack decision #taskmaster #priority/medium #status/done 🔼 🆔 tm-4-8 ⛔ tm-2 ⛔ tm-3 ⛔ tm-4 ⛔ tm-5 ⛔ tm-6 ⛔ tm-7
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.8 |
| Status | done |
| Priority | medium |
| Dependencies | 2, 3, 4, 5, 6, 7 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Choose whether the security-critical backend core remains TypeScript or moves to Go/Kotlin/Rust/Python.
## Details
Completed. Produced `09 - Audits/Backend Core Stack Decision Record - 2026-05-24.md`.
Evaluate team capability, two-year maintainability, operational footprint, rewrite cost, dual-stack complexity, auditability, supply-chain exposure, and which modules belong in a payment/auth/escrow core versus the existing marketplace/chat API.
## Verification
Architecture decision record states chosen stack, scope of extraction, non-goals, migration phases, rollback criteria, and owners.

View File

@@ -0,0 +1,37 @@
---
taskmaster_id: "4.9"
status: "done"
priority: "medium"
depends_on: ["8"]
parent_id: "4"
source: "taskmaster"
generated_at: "2026-05-24T07:26:29.052Z"
---
# 4.9 - Create migration and operational runbooks
- [x] 4.9 - Create migration and operational runbooks #taskmaster #priority/medium #status/done 🔼 🆔 tm-4-9 ⛔ tm-8
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4.9 |
| Status | done |
| Priority | medium |
| Dependencies | 8 |
| Parent | 4 - Define backend security and refactor strategy from latest audit |
## Description
Document rollout, rollback, and incident response for the selected backend/funds architecture.
## Details
Completed. Produced `08 - Operations/Backend Funds Migration and Operational Runbooks.md`.
Include SHKeeper legacy read path, provider feature flag, ledger backfill, validation report before enforcement, rollback criteria, webhook cutoff, manual reconciliation, failed webhook, duplicate/missing payment, stuck release, disputed release attempt, compromised admin, leaked API key, provider outage, chain/RPC outage, suspicious payment proof, and npm/package compromise.
## Verification
Runbooks identify owner, trigger, detection signal, immediate action, recovery action, and post-incident documentation for each scenario.

View File

@@ -0,0 +1,35 @@
---
taskmaster_id: "4"
status: "done"
priority: "high"
depends_on: []
parent_id: ""
source: "taskmaster"
generated_at: "2026-05-24T07:26:29.052Z"
---
# 4 - Define backend security and refactor strategy from latest audit
- [x] 4 - Define backend security and refactor strategy from latest audit #taskmaster #priority/high #status/done ⏫ 🆔 tm-4
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | 4 |
| Status | done |
| Priority | high |
| Dependencies | None |
| Parent | None |
## Description
Convert the backend stack security/refactor assessment into concrete architecture decisions, documentation deliverables, and developer handoff criteria.
## Details
Source audit: .taskmaster/docs/audit-backend-stack-security-and-refactor-assessment-2026-05-24.md. This task is advisory/architecture-focused and should run in parallel with immediate hardening. It should produce the decision artifacts needed before any backend-core rewrite or provider migration is started.
## Verification
Review and sign off each architecture document with backend, payments, frontend, and operations stakeholders. Confirm every open question has an owner or explicit deferred decision before implementation work begins.

Some files were not shown because too many files have changed in this diff Show More