Boot guards
Think of it like…the engine check that won't let your car start if the parking brake is on, the doors aren't shut, or a seatbelt isn't buckled. It is much better to fail loudly in the driveway than to discover the problem at the first intersection under load. Boot guards turn the most common misconfigurations (wildcard CORS with credentials, weak session secrets, unconfigured proxy headers) into refuse-to-start errors.
Daloy ships the boot-guards slice of the secure-by-default initiative: refuse-to-boot / first-request guards that turn the most common production misconfigurations into loud failures during startup instead of silent vulnerabilities under load. They cover weak session secrets, wildcard CORS, missing CSRF, auth: declarations with nothing enforcing them, unauthenticated mcpRoutes() endpoints, and spoofable forwarded / vendor client-IP headers.
Every guard is gated on the resolved environment being production (sources: app({ env: "production" }), then app({ production: true }), then NODE_ENV === "production") so dev and CI workflows keep working with sample secrets and ad-hoc headers. The single master escape hatch app({ secureDefaults: false }) disables every boot guard at once.
- 01startupApp boots in productionenv: "production"
- 02guardsRefuse-to-boot checkssecret, cors '*', csrf, auth:, mcp, forwarded IP
- 03misconfigRefuses to start / first request 500loud error during startup
- 04cleanServes trafficall guards satisfied
1. Weak session secret refuse-to-boot
app.use(session({ secret })) now refuses to register in production when the secret is shorter than 32 UTF-8 bytes, matches a well-known placeholder ("changeme", "your-jwt-secret", "it-is-very-secret", …), or is a single repeated character ("a".repeat(64), "0".repeat(64)). The check runs synchronously inside app.use(...) so the process exits during startup, not on first request.
Third-party session implementations can opt into the same check by stamping SESSION_HOOK_MARKER and SESSION_SECRETS_MARKER on the returned Hooks object. The standalone helper assertStrongSecret(secret, scope) is also exported for use in your own boot code.
2. cors({ origin: "*" }) refuse-to-boot
A wildcard CORS origin exposes every state-changing route cross-origin and is almost never what production wants. Daloy now refuses to register a cors() hook whose origin is "*" or an array containing "*" in production.
3. session() + state-changing route without csrf()
When any route accepts POST, PUT, PATCH, or DELETE AND a session() hook is installed, a csrf() hook must also be installed. The check runs on first request (because route registration order is unknown until then) and the boot error is cached so every subsequent request rethrows the same failure until you fix the wiring.
Non-browser apps (machine-to-machine APIs, webhook receivers behind bearer auth) can acknowledge that CSRF does not apply with app({ csrf: "off" }):
4. Spoofable client-IP headers with trustProxy unset return 500
When app({ trustProxy }) is not set and a request arrives carrying X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-Port, or X-Real-IP, Daloy refuses to dispatch the request and returns a structured 500 problem+json. The rate limiter, audit log, and request-id propagation would otherwise honour the attacker-supplied IP.
The same refusal now covers the platform-specific client-IP headers cf-connecting-ip (Cloudflare), fly-client-ip (Fly.io), and true-client-ip. They are exactly as spoofable as X-Forwarded-*when the app is not actually running behind that platform's proxy, so an unconfigured app refuses them too rather than letting a client forge its source IP through a vendor header the operator never opted into.
The warning is logged at warn exactly once per process via a latch, so a flood of forged requests does not flood your logs.
5. Route auth: declared but not enforced (shadow security)
A route can declare auth: { scheme, ... }so the generated OpenAPI document advertises it as protected. Previously that declaration was documentation only: if no authentication hook actually ran, the route accepted unauthenticated requests while claiming to be protected, a “shadow security” footgun. Now, in production with secureDefaults on, the App refuses to boot when a route declares auth: but no authentication hook is present in its effective hook chain.
The built-in auth middlewares (bearerAuth, basicAuth, jwk, httpSignatureAuth, clientCertAuth) satisfy the guard automatically. For a custom auth hook, or when authentication is actually enforced by an upstream gateway, wrap the hook with the exported markAuthHook() so the guard can see it.
The exported AUTH_HOOK_MARKER symbol is the marker markAuthHook() stamps, in case you need to check for it yourself. Disable this guard along with the rest via app({ secureDefaults: false }).
6. Unauthenticated mcpRoutes() endpoint
MCP tools are model-controlled and side-effecting, so an unauthenticated MCP endpoint is a high-impact default. In production with secureDefaults on, the App refuses to boot when an mcpRoutes() POST endpoint has no authentication hook in its effective chain. Cover it with an auth middleware, or opt in to a genuinely public server with the new mcpRoutes(path, handler, { public: true }) option (typed as McpRoutesOptions).
Only the POST transport route is checked and stamped; GET (a 405 hint) and OPTIONS (CORS preflight) are left unmarked so preflight stays credential-free. See the MCP docs for the full server setup.
Migration checklist
- Audit every
session({ secret })call, regenerate any secret shorter than 32 bytes withopenssl rand -base64 48. - Replace
cors({ origin: "*" })with an explicit allowlist or predicate. - Add
app.use(csrf(...))next toapp.use(session(...)), or passapp({ csrf: "off" })for non-browser-facing apps. - Pick a
trustProxyposture explicitly for every production app. If you relied oncf-connecting-ip,fly-client-ip, ortrue-client-ip, settrustProxy: truenow that those headers are refused too. - For every route that declares
auth:, confirm a built-in auth middleware covers it, or wrap your custom hook withmarkAuthHook(...). - Add an auth middleware in front of every
mcpRoutes()endpoint, or passmcpRoutes(path, handler, { public: true })for a deliberately public MCP server.