A secret comparison that authenticates "Bearer undefined"
What it costs you
This handler checks its authorization header against a secret interpolated into a string, and nothing in the file refuses to run when that variable is missing. With the variable unset the check compares against the literal text "Bearer undefined", so anyone who sends exactly that header is authorized — in a new environment, a preview deployment, or after the variable is renamed.
The defect, and the fix
Both samples are scanned as app/api/cron/cleanup/route.ts.
Our test suite runs both through the scanner on every build: the first must be reported, the second must not.
export async function GET(req) {
const auth = req.headers.get("authorization");
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response("Unauthorized", { status: 401 });
}
await purgeExpiredRows();
return new Response("ok");
}export async function GET(req) {
const secret = process.env.CRON_SECRET;
if (!secret) throw new Error("CRON_SECRET is not set");
const auth = req.headers.get("authorization");
if (auth !== `Bearer ${secret}`) {
return new Response("Unauthorized", { status: 401 });
}
await purgeExpiredRows();
return new Response("ok");
}What changed: The secret is read once and the handler refuses to run without it, so an unset variable can no longer make the comparison succeed against "Bearer undefined".
How to fix it
Read the secret into a variable and refuse the request when it is missing — `const secret = process.env.CRON_SECRET; if (!secret) return new Response("Unauthorized", { status: 401 });` — before comparing. An environment with no secret configured can authorize nothing.
Why this rule doesn't cry wolf
Each clause below exists because it was attacked: someone was asked to find correct code that the rule would flag, and the clause is what stopped it. This is published because a check you cannot audit is a check you have to take on faith.
Fires only on an equality comparison between an inbound request header and an env secret INTERPOLATED into a string (template literal or `+` concatenation) whose name is *_SECRET / *_TOKEN / *_KEY, where no fail-closed check on that same variable — `if (!x) return/throw`, an assertEnv-style call, a zod/envalid schema over process.env, or a throwing `??` fallback — appears anywhere in the file, and where the variable has no literal fallback (that is a hardcoded-credential finding, not this one). Webhook paths are left to VC062/VC209. Each clause removes a class of correct code: bare non-interpolated compares, non-secret env vars, guarded handlers, validated-env projects.
This is not a security finding
QA004 is reported in its own section, separately from security findings. It does not change your security grade, and it does not fail your build unless you pass --fail-on-quality. The security catalogue lives at /rules.
Check your own code
npx xploitscan scan .Runs on every plan, including free. All 12 quality checks.