Env Validation
Fail fast on missing environment variables — no silent fallbacks
Status (reconciled 2026-07-02)
The web app validates env via lib/env.ts (zod) and the middleware throws without JWT_SECRET in production. The backend still has the dangerous defaults — removing them is plan phase 1, which owns the exact Joi spec.
The cal.diy pattern
apps/api/v2/src/env.ts defines a typed getEnv<K>() helper that throws at startup if a required variable is missing — no silent fallbacks:
type Environment = {
DATABASE_URL: string;
JWT_SECRET: string;
// ...
};
export const getEnv = <K extends keyof Environment>(
key: K,
fallback?: Environment[K]
): Environment[K] => {
const value = process.env[key];
if (value === undefined) {
if (fallback !== undefined) return fallback;
throw new Error(`Missing environment variable: ${key}.`); // crash fast, loud
}
return value as Environment[K];
};Every key is typed via Environment — a typo on a key name is a compile error.
The KwadMarket problem
JWT_SECRET: Joi.string().default('marketplace-ts-dev-secret'),A misconfigured production deploy silently uses a known-public JWT secret: all JWTs from that deploy can be forged by anyone who reads the source code. Same class of problem for AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / MAILER_* defaulting to ''.
What to implement
- Backend fail-fast — the
Joi.when('NODE_ENV', { is: 'production', then: required() })pattern, specified with grep gates in Security §1. Dev keeps a default; production crashes at boot. - Extend frontend validation — add every
NEXT_PUBLIC_*var the app uses toapps/web/lib/env.ts(currently onlyNEXT_PUBLIC_API_URL); replace directprocess.env.NEXT_PUBLIC_*reads withenv.*. - Optional, medium term — a typed
getEnv<K extends keyof Env>()helper for the backend, eliminatingprocess.env.TYPO_KEYat runtime.
NEXT_PUBLIC_* is build-time inlined; server-only secrets like JWT_SECRET must be validated in a server-only module so they never ship to the client bundle.