AntFleet

Disagreement · 034f209b-openai-0

Missing OPTIN_HMAC_SECRET causes unhandled 500 in /api/opt-in (no graceful error)

mismatch
repo e24ef98c·PR #9·reviewed 1 week ago

Primary finding

Missing OPTIN_HMAC_SECRET causes unhandled 500 in /api/opt-in (no graceful error)

highbuild-releasehigh
  • apps/web/lib/optin-token.ts:19-25
  • apps/web/lib/optin-token.ts:66-76
  • apps/web/app/api/opt-in/route.ts:45-46
verifyTokenDetailed calls getSecret(), which throws if OPTIN_HMAC_SECRET is unset. handleOptIn calls verifyTokenDetailed without a try/catch, so the route will throw and return a 500 on requests instead of returning a controlled error response. This is a brittle production-footgun if the env var is missing in any environment.

Recommendation

Defensively handle missing secret at the route boundary. Wrap verifyTokenDetailed in a try/catch; if the thrown error message matches the missing-secret case, log a high-severity event (e.g., optin.misconfigured) and return htmlResponse(500, errorPage("Server misconfigured","Opt-in service is temporarily unavailable.")) without leaking stack traces. Optionally validate OPTIN_HMAC_SECRET presence at process start and fail-fast with a clear error.

Counterpart finding

verifyTokenDetailed reads HMAC secret before validating token shape, leaking malformed-token failure mode behind misconfiguration

lowmaintainabilitymedium
  • apps/web/lib/optin-token.ts:71-80
verifyTokenDetailed calls getSecret() before computing/comparing anything. If OPTIN_HMAC_SECRET is unset in production, every opt-in click — even with a clearly malformed token like 'garbage' — throws a 500 rather than returning a clean 400 'Invalid link'. The route's tests assume the secret is always present, but in a misconfigured deploy this turns user-visible 400s into 500s and could mask the real problem (missing env var) behind generic Next error pages. This is intentional per design ('throws if OPTIN_HMAC_SECRET is missing at verify time' is tested), but it's worth noting that handleOptIn does not catch this throw, so misconfiguration becomes a 500 rather than a graceful error page.

Recommendation

Either (a) catch the missing-secret throw at the route layer and render a generic 500 error page consistent with the rest of the design, or (b) document this contract explicitly in route.ts so operators understand that secret rotation/unset means HTTP 500 across all opt-in links.

Why this didn't post

This finding didn't meet AntFleet's unanimous agreement threshold. Both frontier models review every PR independently; only findings they both flag with the same severity and category are posted to the PR. This one fell through.

read the methodology →