# Security design updates (Phases 0 & 3) Summary of application-security design changes from the production-readiness program. Full finding table lives in `docs/ops/RentalDriveGo_Production_Readiness_Plan.md` §3. ## Closed in code (S1–S15) | ID | Design rule now enforced | |----|---------------------------| | S1 | Team list/invite responses use safe presenters — never return password hashes or raw reset tokens | | S2 | Per-tenant Docker/Compose orchestration is **out of production scope** (fail-closed + ADR-001) | | S3 | Invite tokens hashed at rest (SHA-256) | | S4 | Employee email unique per company; login fails closed on ambiguity | | S5 | Post-login redirects limited to safe relative paths | | S6 | Authenticated payment/subscription return URLs allowlisted | | S7 | Admin company slugs validated / slugified | | S8 | Admin presenters strip reset/verification secrets | | S9 | Forwarded headers scrubbed by default; `TRUSTED_FORWARD_HEADERS=true` only behind a scrubbing edge (`docs/ops/proxy-trust.md`) | | S10 | Password reset token lookup is hash-only | | S11 | `reviewToken` is never returned in reservation/review API JSON (still used server-side for email links) | | S12 | Public booking access: **read** does not burn the token; **payment init** atomically consumes an unused token | | S13 | Admin money / privileged mutations require 2FA proof newer than `ADMIN_FRESH_2FA_MAX_AGE_MS` | | S14 | `.gitignore` present for secrets/build artifacts | | S15 | `npm run security:static` in CI | ## Public booking token (S12) flow ``` createBooking → mint publicAccessToken (hash stored) │ ├─ GET booking?token=… → validate (used or unused OK until expiry) — do not consume │ └─ initPayment(token) → require usedAt IS NULL → updateMany set usedAt → proceed (second payment attempt with same token → 404) ``` ## Fresh admin 2FA (S13) `requireFreshAdmin2FA` rejects when: - TOTP not enrolled, or - JWT lacks `last2faAt`, or - `now - last2faAt > ADMIN_FRESH_2FA_MAX_AGE_MS` (default 30 minutes) Wired on finance/support money and high-privilege admin mutations. ## Tenant isolation design - App-level: every company resource query includes `companyId` from the authenticated session - Regression suite: `apps/api/src/tests/integration/cross-tenant-isolation.test.ts` - Postgres RLS: **deferred** (ADR-002) until the app-level suite and worker/migration roles are ready ## Pen-test Scope pack: `docs/security/pen-test-scope.md`. Reports belong under `security-reports/` (evidence, not design).