fix(security): resolve client audit findings #1

Merged
sathish merged 2 commits from fix/security-audit into main 2026-08-04 13:18:42 +00:00
Member

Addresses the RE:Quest code-audit findings for the backend. 20 files, no behaviour
change to any feature — the only runtime difference is that a misconfigured deploy
now fails at startup instead of silently falling back to a development default.

Security fixes

  • Remove hardcoded JWT fallback secrets (messages.module.ts, configuration.ts).
    'fallbacksecret' / 'super-secret-key' were publicly visible in source, making
    tokens forgeable if JWT_SECRET was ever unset.
  • Remove the 'default-secret' fallback for the 2FA TOTP encryption key, and add an
    optional TWO_FACTOR_ENCRYPTION_KEY. The key was derived from JWT_SECRET, so
    rotating JWT_SECRET (which the handover requires) would have made every stored
    TOTP secret undecryptable and locked out every 2FA user. See
    docs/2fa-key-rotation.md before rotating.
  • Require EMAIL_API_URL; drop the hardcoded vendor email endpoint.
  • Drive WebSocket CORS from CORS_ORIGINS instead of origin: '*'.
  • Enable Redis TLS certificate verification (rejectUnauthorized: false removed).
  • Token lifetime defaults corrected to 15m / 7d (were 7d / 365d).

Env-load ordering fix (please read — this one is subtle)

Decorator arguments are evaluated at module-import time, which is strictly earlier
than ConfigModule.forRoot(). MessagesGateway reads process.env.CORS_ORIGINS
inside its @WebSocketGateway decorator, so it was freezing to the localhost
fallback even when CORS_ORIGINS was set — which would have rejected every
browser websocket connection from the live domain.

Fixed by src/load-env.ts, imported first in main.ts. It must stay the first
import, and it has to be a side-effect import because tsc emits all require()
calls ahead of any statement. Verified empirically: the gateway now picks up the
configured origins where it previously did not.

Fail-fast env validation (src/config/env.validation.ts)

Aborts startup on: missing DATABASE_URL / JWT_SECRET / EMAIL_API_URL; missing
CORS_ORIGINS / FRONTEND_URL in production; JWT_SECRET under 32 chars in
production; and a refresh lifetime not longer than the access lifetime.
Warns on: access tokens over 1h, REDIS_TLS off in production, sslmode=no-verify,
localhost in a production CORS_ORIGINS.

Run against the current production env, this reports the missing EMAIL_API_URL
and the inverted JWT_ACCESS_EXPIRATION=7d / JWT_REFRESH_EXPIRATION=1m.

Admin bootstrap

prisma/seed.ts now requires ADMIN_EMAIL / ADMIN_PASSWORD (min 12 chars),
validated before it writes anything. Removes the published
admin@re-quest.com / Admin@123456 defaults and stops printing credentials.

Migrations

Adds the initial migration and stops gitignoring prisma/migrations/.

⚠️ Production was built with db push, so it must be baselined once before the
first migrate deploy, or it fails with "relation already exists":
npx prisma migrate resolve --applied 20260721102313_init
npx prisma migrate deploy

Config contract + docs

  • .env.example made accurate: admin bootstrap, REDIS_TLS, S3_ENDPOINT,
    S3_PUBLIC_ENDPOINT, 2FA key, Firebase path; dead SMTP block removed with a note
    that no SMTP path exists in the code.
  • docs/ — architecture, ER model (24 entities, generated from the schema),
    sequence diagrams for all 8 core flows, data-flow diagram, 2FA runbook.
    Mermaid-in-Markdown; covers the client's §8 documentation requirement.

Verification

nest build and tsc --noEmit clean. Websocket CORS fix and env validation both
verified by execution, not inspection.

Addresses the RE:Quest code-audit findings for the backend. 20 files, no behaviour change to any feature — the only runtime difference is that a misconfigured deploy now fails at startup instead of silently falling back to a development default. ## Security fixes - Remove hardcoded JWT fallback secrets (`messages.module.ts`, `configuration.ts`). `'fallbacksecret'` / `'super-secret-key'` were publicly visible in source, making tokens forgeable if `JWT_SECRET` was ever unset. - Remove the `'default-secret'` fallback for the 2FA TOTP encryption key, and add an optional `TWO_FACTOR_ENCRYPTION_KEY`. The key was derived from `JWT_SECRET`, so rotating `JWT_SECRET` (which the handover requires) would have made every stored TOTP secret undecryptable and locked out every 2FA user. See `docs/2fa-key-rotation.md` before rotating. - Require `EMAIL_API_URL`; drop the hardcoded vendor email endpoint. - Drive WebSocket CORS from `CORS_ORIGINS` instead of `origin: '*'`. - Enable Redis TLS certificate verification (`rejectUnauthorized: false` removed). - Token lifetime defaults corrected to `15m` / `7d` (were `7d` / `365d`). ## Env-load ordering fix (please read — this one is subtle) Decorator arguments are evaluated at module-import time, which is strictly earlier than `ConfigModule.forRoot()`. `MessagesGateway` reads `process.env.CORS_ORIGINS` inside its `@WebSocketGateway` decorator, so it was freezing to the localhost fallback **even when `CORS_ORIGINS` was set** — which would have rejected every browser websocket connection from the live domain. Fixed by `src/load-env.ts`, imported first in `main.ts`. It must stay the first import, and it has to be a side-effect import because tsc emits all `require()` calls ahead of any statement. Verified empirically: the gateway now picks up the configured origins where it previously did not. ## Fail-fast env validation (`src/config/env.validation.ts`) Aborts startup on: missing `DATABASE_URL` / `JWT_SECRET` / `EMAIL_API_URL`; missing `CORS_ORIGINS` / `FRONTEND_URL` in production; `JWT_SECRET` under 32 chars in production; and a refresh lifetime not longer than the access lifetime. Warns on: access tokens over 1h, `REDIS_TLS` off in production, `sslmode=no-verify`, `localhost` in a production `CORS_ORIGINS`. Run against the current production env, this reports the missing `EMAIL_API_URL` and the inverted `JWT_ACCESS_EXPIRATION=7d` / `JWT_REFRESH_EXPIRATION=1m`. ## Admin bootstrap `prisma/seed.ts` now requires `ADMIN_EMAIL` / `ADMIN_PASSWORD` (min 12 chars), validated before it writes anything. Removes the published `admin@re-quest.com` / `Admin@123456` defaults and stops printing credentials. ## Migrations Adds the initial migration and stops gitignoring `prisma/migrations/`. ⚠️ Production was built with `db push`, so it must be baselined once before the first `migrate deploy`, or it fails with "relation already exists": npx prisma migrate resolve --applied 20260721102313_init npx prisma migrate deploy ## Config contract + docs - `.env.example` made accurate: admin bootstrap, `REDIS_TLS`, `S3_ENDPOINT`, `S3_PUBLIC_ENDPOINT`, 2FA key, Firebase path; dead SMTP block removed with a note that no SMTP path exists in the code. - `docs/` — architecture, ER model (24 entities, generated from the schema), sequence diagrams for all 8 core flows, data-flow diagram, 2FA runbook. Mermaid-in-Markdown; covers the client's §8 documentation requirement. ## Verification `nest build` and `tsc --noEmit` clean. Websocket CORS fix and env validation both verified by execution, not inspection.
sathish added 2 commits 2026-08-04 13:00:30 +00:00
Removes hardcoded fallback secrets and makes a misconfigured deploy fail
loudly instead of silently falling back to development defaults.

- Remove insecure JWT fallback secrets (messages.module, configuration)
- Remove the 'default-secret' fallback for the 2FA TOTP encryption key and
  allow a dedicated TWO_FACTOR_ENCRYPTION_KEY so rotating JWT_SECRET no
  longer locks out every 2FA user (see docs/2fa-key-rotation.md)
- Require EMAIL_API_URL; drop the hardcoded vendor email endpoint
- Drive WebSocket CORS from CORS_ORIGINS instead of origin:'*'
- Load .env before any Nest module is imported (src/load-env.ts). Decorator
  arguments evaluate at import time, so the gateway previously froze its CORS
  config to the localhost fallback even when CORS_ORIGINS was set
- Add boot-time env validation: missing required vars, weak JWT_SECRET, and
  inverted access/refresh token lifetimes now abort startup
- Enable Redis TLS certificate verification
- Require ADMIN_EMAIL/ADMIN_PASSWORD for the seed; remove the published
  default super-admin credentials and stop printing them
- Add the initial Prisma migration and stop gitignoring prisma/migrations
- Make .env.example an accurate configuration contract (admin bootstrap,
  REDIS_TLS, S3_ENDPOINT, 2FA key, Firebase path; drop the dead SMTP block)
- Add handover documentation: architecture, ER model, sequence and data-flow
  diagrams, 2FA key rotation runbook

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to 4df4d8c, which added the variable. Keeps .env.example an
accurate configuration contract.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sathish merged commit 8b677251cc into main 2026-08-04 13:18:42 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Re-Quest/backend#1