audit: Tier 0 quick wins — EMAIL_REDIRECT_TO prod guard + storage routing + metadata masking
Tier 0.2: src/lib/env.ts now refuses boot when NODE_ENV=production AND EMAIL_REDIRECT_TO is set. Sendmail logs the rewrite at warn (was debug) so dev/staging windows where someone forgets to unset are immediately visible. Tier 0.6: backup_jobs.storage_path added to TABLES_WITH_STORAGE_KEYS in src/lib/storage/migrate.ts. Flipping the storage backend used to silently orphan every pg_dump artefact — last-resort recovery path is now actually portable. Tier 1.7: createAuditLog now runs metadata through maskSensitiveFields (was only applied to old/new value diffs). Portal-auth, crm-invite, hard-delete and email-accounts services were writing raw emails into this column unbounded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,21 @@ const envSchema = z.object({
|
||||
.enum(['true', 'false'])
|
||||
.default('false')
|
||||
.transform((v) => v === 'true'),
|
||||
}).superRefine((env, ctx) => {
|
||||
// CRITICAL safety net: EMAIL_REDIRECT_TO is a dev/test feature that
|
||||
// silently rewrites every outbound recipient. Leaving it set in prod
|
||||
// funnels every customer email (invites, EOIs, portal magic links,
|
||||
// contracts) to a single inbox. The audit caught this had only a
|
||||
// `logger.debug` line as forensic trail. Refuse boot when both are
|
||||
// simultaneously set in production.
|
||||
if (env.NODE_ENV === 'production' && env.EMAIL_REDIRECT_TO) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['EMAIL_REDIRECT_TO'],
|
||||
message:
|
||||
'EMAIL_REDIRECT_TO must NOT be set in production — it silently rewrites every outbound email recipient. Unset it before deploying.',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
|
||||
Reference in New Issue
Block a user