feat(16-01): add boot-time refuse-to-boot guard for dev-bypass in production
- Create apps/api/src/lib/bootGuards.ts with assertNotDevBypassInProduction() - Guard exits non-zero when NODE_ENV=production AND DEV_AUTH_BYPASS=true (D-08) - Wire import + call as first statement in isMainModule() block in index.ts - 3/3 unit tests pass, typecheck green
This commit is contained in:
@@ -15,6 +15,7 @@ import { persistSessionCookie } from './auth/persistSessionCookie.js';
|
|||||||
import { startBrokerPoller } from './broker/poller.js';
|
import { startBrokerPoller } from './broker/poller.js';
|
||||||
import { startOutboxWorker, initOutboxTrigger } from './broker/outboxWorker.js';
|
import { startOutboxWorker, initOutboxTrigger } from './broker/outboxWorker.js';
|
||||||
import { startReminderScheduler } from './broker/reminderScheduler.js';
|
import { startReminderScheduler } from './broker/reminderScheduler.js';
|
||||||
|
import { assertNotDevBypassInProduction } from './lib/bootGuards.js';
|
||||||
import webpush from 'web-push';
|
import webpush from 'web-push';
|
||||||
|
|
||||||
export const app = new Hono();
|
export const app = new Hono();
|
||||||
@@ -110,6 +111,9 @@ function isMainModule(): boolean {
|
|||||||
// (not imported in tests). WR-04: gating the cron schedules here keeps them out of the
|
// (not imported in tests). WR-04: gating the cron schedules here keeps them out of the
|
||||||
// test process.
|
// test process.
|
||||||
if (isMainModule()) {
|
if (isMainModule()) {
|
||||||
|
// D-08: Production safety guard — must be FIRST, before VAPID config, workers, or serve().
|
||||||
|
assertNotDevBypassInProduction();
|
||||||
|
|
||||||
// Configure VAPID credentials for web-push before starting background workers.
|
// Configure VAPID credentials for web-push before starting background workers.
|
||||||
// VAPID_SUBJECT must be a mailto: or https: URL identifying the operator.
|
// VAPID_SUBJECT must be a mailto: or https: URL identifying the operator.
|
||||||
// The private key is NEVER served to clients; it signs push requests server-side only.
|
// The private key is NEVER served to clients; it signs push requests server-side only.
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Boot-time production safety guards (D-08).
|
||||||
|
*
|
||||||
|
* Exported as a standalone function so it can be unit-tested without
|
||||||
|
* forking a process or importing the full app module graph.
|
||||||
|
*
|
||||||
|
* Call assertNotDevBypassInProduction() as the FIRST statement inside
|
||||||
|
* the isMainModule() block in index.ts, before VAPID config, workers,
|
||||||
|
* or serve(). Placement after any network/DB calls would allow a misconfigured
|
||||||
|
* production container to partially start before the guard fires.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refuses to start the process when NODE_ENV==='production' AND
|
||||||
|
* DEV_AUTH_BYPASS==='true'.
|
||||||
|
*
|
||||||
|
* Rationale (D-07 + D-08): The production Dockerfile bakes NODE_ENV=production,
|
||||||
|
* engaging the devBypass.ts hard guard. This boot guard is defense-in-depth — it
|
||||||
|
* converts a silent misconfiguration (operator accidentally sets DEV_AUTH_BYPASS=true
|
||||||
|
* in the production compose) into an immediate, loud, non-zero-exit failure instead
|
||||||
|
* of a silently bypassed auth layer.
|
||||||
|
*
|
||||||
|
* The function evaluates env vars at call time (when the app starts), not at import
|
||||||
|
* time, so the test suite can set env vars before calling it without module-cache tricks.
|
||||||
|
*/
|
||||||
|
export function assertNotDevBypassInProduction(): void {
|
||||||
|
if (process.env.NODE_ENV === 'production' && process.env.DEV_AUTH_BYPASS === 'true') {
|
||||||
|
console.error(
|
||||||
|
'[FATAL] DEV_AUTH_BYPASS=true is set in a production environment. ' +
|
||||||
|
'This configuration is forbidden. Refusing to start.',
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user