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: '*'.
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.
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.
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 main2026-08-04 13:18:42 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
messages.module.ts,configuration.ts).'fallbacksecret'/'super-secret-key'were publicly visible in source, makingtokens forgeable if
JWT_SECRETwas ever unset.'default-secret'fallback for the 2FA TOTP encryption key, and add anoptional
TWO_FACTOR_ENCRYPTION_KEY. The key was derived fromJWT_SECRET, sorotating
JWT_SECRET(which the handover requires) would have made every storedTOTP secret undecryptable and locked out every 2FA user. See
docs/2fa-key-rotation.mdbefore rotating.EMAIL_API_URL; drop the hardcoded vendor email endpoint.CORS_ORIGINSinstead oforigin: '*'.rejectUnauthorized: falseremoved).15m/7d(were7d/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().MessagesGatewayreadsprocess.env.CORS_ORIGINSinside its
@WebSocketGatewaydecorator, so it was freezing to the localhostfallback even when
CORS_ORIGINSwas set — which would have rejected everybrowser websocket connection from the live domain.
Fixed by
src/load-env.ts, imported first inmain.ts. It must stay the firstimport, 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; missingCORS_ORIGINS/FRONTEND_URLin production;JWT_SECRETunder 32 chars inproduction; and a refresh lifetime not longer than the access lifetime.
Warns on: access tokens over 1h,
REDIS_TLSoff in production,sslmode=no-verify,localhostin a productionCORS_ORIGINS.Run against the current production env, this reports the missing
EMAIL_API_URLand the inverted
JWT_ACCESS_EXPIRATION=7d/JWT_REFRESH_EXPIRATION=1m.Admin bootstrap
prisma/seed.tsnow requiresADMIN_EMAIL/ADMIN_PASSWORD(min 12 chars),validated before it writes anything. Removes the published
admin@re-quest.com/Admin@123456defaults 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 thefirst
migrate deploy, or it fails with "relation already exists":npx prisma migrate resolve --applied 20260721102313_init
npx prisma migrate deploy
Config contract + docs
.env.examplemade accurate: admin bootstrap,REDIS_TLS,S3_ENDPOINT,S3_PUBLIC_ENDPOINT, 2FA key, Firebase path; dead SMTP block removed with a notethat 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 buildandtsc --noEmitclean. Websocket CORS fix and env validation bothverified by execution, not inspection.