# 2026-07-16 — P0 security (WT-A) Closes the deployment-readiness P0s for auth, CORS, body size, hygiene, and client-side secret handling. ## Files changed **New:** - `server/src/config/index.js` — single source of truth for env-derived config. Crashes on missing required vars in production. - `server/scripts/lint-no-backups.js` — `npm run lint:hygiene` fails if any `*.bak` / `*.orig` / `*~` is staged. - `server/scripts/refactor-jwt-secret.js` — one-shot script that rewrote 56 files. - `server/scripts/smoke-test.js` — post-change smoke test (health, login, offline secret, me/credentials, 2MB body rejection). - `server/.env.example` — documents all env vars with required-in-prod set. - `.harness/changelogs/2026-07-16-p0-security.md` — this file. **Modified (56 server files):** - All controllers (54) + `server/src/index.js` + `server/src/services/wsHub.js` no longer hardcode `'africa-alert-secret-key-2024'`. They import `const { jwtSecret: JWT_SECRET } = require('../config')` (or `'./config'` for `index.js`). - `server/src/index.js`: CORS restricted to `ALLOWED_ORIGINS` in prod (permissive in dev). JSON body limit dropped to 1mb. Added `/api/health` endpoint + 413 error handler for `entity.too.large`. - `client/src/lib/offlineAuth.ts`: `OFFLINE_JWT_SECRET` now sourced from `localStorage` (populated at login from the new `offline_jwt_secret` response field). Dev fallback only in `import.meta.env.DEV`; null in prod unless `ALLOW_CLIENT_OFFLINE_MINT=true`. - `client/src/store/auth.ts`: captures `offline_jwt_secret` from `/api/auth/login` response and caches it via `cacheOfflineJwtSecret`. - `server/package.json`: new scripts `lint:hygiene` and `refactor:jwt-secret`. - `README.md`: production security checklist section. **Deleted:** - `server/src/controllers/sync.controller.js.bak` (6045 bytes). ## Verification Smoke test (`node server/scripts/smoke-test.js`): - ✓ `GET /api/health` → 200 - ✓ `POST /api/auth/login` returns `token` + `offline_jwt_secret` - ✓ `GET /api/auth/me/credentials` (with token) → 200 - ✓ 2MB JSON body → 413 (was 500 before the error handler) - 5/5 checks pass Regression net: 14/14 Playwright E2E specs green (`cd client && npx playwright test`). Hygiene: `npm run lint:hygiene` → "OK — no .bak / .orig / ~ files in server/src or client/src". `Select-String -Path 'server\src' -Pattern "'africa-alert-secret-key-2024'"` → 0 matches. `Select-String -Path 'client\src' -Pattern "OFFLINE_JWT_SECRET\s*=\s*'africa-alert"` → 0 matches. ## Known follow-ups (deferred to WT-B, WT-C) - `WT-B` (offline cache) is independent and should land before the next deploy. - `WT-C` (docker) will add `HEALTHCHECK` that hits `/api/health` (already present now). - The dev fallback string `dev-only-insecure-secret-do-not-use-in-production` is intentional; the prod crash on missing `JWT_SECRET` is in `server/src/config/index.js`.