AntFleet

Disagreement · f916efe6-anthropic-0

Health endpoint can be abused as a low-cost DoS amplifier against Postgres

mismatch
repo ab4e09bc·PR #3·reviewed 2 weeks ago

Primary finding

Health endpoint can be abused as a low-cost DoS amplifier against Postgres

lowsecuritymedium
  • apps/web/app/api/health/route.ts:33-65
The GET handler is unauthenticated and performs a database round-trip on every call. Because `dynamic = "force-dynamic"` disables caching and there is no rate limiting, an attacker (or a misconfigured external monitor) can drive arbitrary load to Postgres by spamming /api/health. For an application-level health probe this is the conventional trade-off, but it is worth gating with a short in-memory cache (e.g. cache the last result for ~5s) or with the same CRON_SECRET style bearer the cron sweep uses, especially since one of the required env vars is `CRON_SECRET`.

Recommendation

Either (a) memoize the readiness result for a few seconds in-process to bound DB QPS, or (b) require a shared-secret header for the readiness variant and keep an unauthenticated liveness path that does no DB work.

Counterpart finding

Health check treats whitespace-only env vars as present

lowapi-contractmedium
  • apps/web/app/api/health/route.ts:31-35
The intent (per comments) is to pre-emptively catch missing secrets so the next real request doesn’t fail. The check only flags undefined or empty-string values, but a whitespace-only value (e.g., " ") would be considered present and pass the health check, while still being effectively invalid for keys/secrets. This slightly undermines the stated goal of catching misconfiguration early.

Recommendation

Treat whitespace-only values as missing by trimming before checking, e.g., const v = process.env[name]; const isMissing = v == null || (typeof v === 'string' && v.trim() === ''); and use that in the filter.

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 →