Description
While reviewing the Vercel preset I noticed the auto-registered cron handler only authenticates when CRON_SECRET happens to be set:
// src/presets/vercel/runtime/cron-handler.ts (main @ e36e7a6, unchanged as of 2026-08-21)
const cronSecret = process.env.CRON_SECRET;
if (cronSecret) {
// ... timingSafeEqual check, 401 on mismatch
}
// no else — with no CRON_SECRET the request proceeds unauthenticated
const cron = event.req.headers.get("x-vercel-cron-schedule");
if (!cron) throw new HTTPError("Missing x-vercel-cron-schedule header", { status: 400 });
await runCronTasks(cron, { ... });
return { success: true };
Concretely, when experimental.tasks is enabled with non-empty scheduledTasks and CRON_SECRET is unset:
/_vercel/cron answers to any HTTP verb from anyone (the handler registers without a method, so rou3 matches all verbs);
- an attacker chooses the schedule via
x-vercel-cron-schedule, and runCronTasks exact-matches it against the configured keys — so any configured scheduled task can be executed on demand, repeatedly;
- the
{ success: true } response confirms execution (400 if the header is missing; 401 only when CRON_SECRET is set);
- minor extra leak:
runTask is single-flight via __runningTasks__ (src/runtime/internal/task.ts:23-25), so a request timed while the real scheduled run is in-flight resolves with that run's task result value, which is returned to the caller if tasks return data.
There are currently no functional tests for this handler at all (test/presets/vercel.test.ts only snapshots the config output), so neither the 401 path nor the public-by-default path is pinned by tests.
I understand this mirrors Vercel's documented securing model (the code comment cites vercel.com/docs "securing cron jobs", and the nitro docs recommend setting CRON_SECRET), so I'm filing this as a hardening suggestion rather than a vulnerability claim. Still, the current default is fail-open for a code-execution endpoint, and nothing at build time or in the runtime output warns the deployer; a user who skips one env var silently publishes their scheduled tasks.
Suggestions (any one would close the gap)
- Fail closed: return 401/403 when
CRON_SECRET is unset. (Would be a behavior change; a major-version candidate.)
- Build-time warning/error: when the preset registers
/_vercel/cron without CRON_SECRET present, emit a prominent build warning (or refuse if strict).
- Docs: state explicitly in
docs/2.deploy/20.providers/vercel.md that the endpoint is public by default without the secret.
- Tests: there are currently no tests covering the handler's auth behavior; a case for the unset-secret path would pin whatever default you choose.
Happy to send a PR for options 2-4. Context: verified against main @ e36e7a6; no live deployments were tested.
Reported by zz-protocol
Description
While reviewing the Vercel preset I noticed the auto-registered cron handler only authenticates when
CRON_SECREThappens to be set:Concretely, when
experimental.tasksis enabled with non-emptyscheduledTasksandCRON_SECRETis unset:/_vercel/cronanswers to any HTTP verb from anyone (the handler registers without amethod, so rou3 matches all verbs);x-vercel-cron-schedule, andrunCronTasksexact-matches it against the configured keys — so any configured scheduled task can be executed on demand, repeatedly;{ success: true }response confirms execution (400 if the header is missing; 401 only whenCRON_SECRETis set);runTaskis single-flight via__runningTasks__(src/runtime/internal/task.ts:23-25), so a request timed while the real scheduled run is in-flight resolves with that run's task result value, which is returned to the caller if tasks return data.There are currently no functional tests for this handler at all (
test/presets/vercel.test.tsonly snapshots the config output), so neither the 401 path nor the public-by-default path is pinned by tests.I understand this mirrors Vercel's documented securing model (the code comment cites vercel.com/docs "securing cron jobs", and the nitro docs recommend setting
CRON_SECRET), so I'm filing this as a hardening suggestion rather than a vulnerability claim. Still, the current default is fail-open for a code-execution endpoint, and nothing at build time or in the runtime output warns the deployer; a user who skips one env var silently publishes their scheduled tasks.Suggestions (any one would close the gap)
CRON_SECRETis unset. (Would be a behavior change; a major-version candidate.)/_vercel/cronwithoutCRON_SECRETpresent, emit a prominent build warning (or refuse ifstrict).docs/2.deploy/20.providers/vercel.mdthat the endpoint is public by default without the secret.Happy to send a PR for options 2-4. Context: verified against main @
e36e7a6; no live deployments were tested.Reported by zz-protocol