Security Checklist
Security is non-negotiable and enforced from day one. This page consolidates the rules and the pre-deploy checklist.
On every new endpoint
- Zod validation on all
req.bodyandreq.query. authenticateon all protected routes;requireActiveUseron all writes.- Ownership check in the service layer — never trust an ID from the client.
- Sanitize free-text fields with
sanitize-htmlbefore persisting.
Critical rules
| Rule | Why |
|---|---|
| Never expose phone numbers in list/browse APIs | Only in the ACCEPTED-deal notification |
Never return passwordHash | Explicitly exclude it in Prisma selects |
Mask phones in logs (XXXXXX${phone.slice(-4)}) | PII in logs |
| Never log request bodies with phone/tokens | PII / secret leakage |
CORS: only FRONTEND_URL + ADMIN_URL (+ ecropto.com) | Never * |
JWT only from Authorization: Bearer | Never from query params |
| Verify upload magic bytes; reject SVG; never reuse the original filename | SVGs can carry JS; generate keys with nanoid |
| Raw SQL only via Prisma tagged templates | Never $queryRawUnsafe with string concat |
Never dangerouslySetInnerHTML | Sanitize before persisting instead |
| Owner-only financials off counterparty serializers | Cost/margin/supplier/P&L privacy |
Owner-only vs public data
- Owner-only (private, never on a counterparty payload): buy cost, production cost, cost basis, blended cost, realised P&L, inventory valuation, supplier details, source-deal backlink. Guard tests assert no leak in
serializeBatchForLead,getProductBatchSummary,available-batches, and lead/board rows. - Public (statutory / to any buyer): sale price, GI status/region, quality metrics, photos, GST bracket (
gstRate,hsnCode).
Auth hardening
- Access tokens 15m; refresh tokens 30d, device-bound, single-use rotation with a 60s grace window.
- Login limiters count failures only; a success clears the phone + device counters.
DEACTIVATEDusers are rejected at theauthenticatemiddleware.- The admin panel gates
ProtectedRouteon role ∈ {admin, sub-admin} and classifies refresh failures terminal-vs-transient so a 5xx never logs an admin out.
Observability & privacy
Sentry.setUser({ id })— id only (DPDP-compliant), never phone or PII.- Sentry DSNs are per-environment and empty in dev/CI so events are dropped silently.
Pre-deploy checklist
text
[ ] npm audit --audit-level=high passes (fix high/critical first)
[ ] No secrets in git history
[ ] All new routes have Zod schemas
[ ] All new routes accessing user resources have ownership checks
[ ] No console.log with phone numbers or tokens
[ ] Env vars in Railway (not in code)
[ ] File upload tested with a non-image file (must reject with 400)
[ ] ENABLE_TEST_ENDPOINTS unset, TEST_ENDPOINT_SECRET unset, TEST_OTP_PHONES unset
[ ] Coverage thresholds met (80/80/70/80)
[ ] Health check green on staging: GET /api/v1/healthTest-endpoint locks
The QA fast-path (/api/v1/test/*) has two independent locks — ENABLE_TEST_ENDPOINTS and TEST_ENDPOINT_SECRET. TEST_OTP_PHONES is separately gated and, where it deletes fixtures, hard-refuses any number outside the whitelist. All three must be unset on real production; the API logs a loud warning at boot if any are set.
