Skip to content

fix(automations): refuse the automations cron on Vercel preview deployments - #64

Open
polylane[bot] wants to merge 2 commits into
mainfrom
polylane/autofix/92bsky8orscp
Open

polylane[bot] wants to merge 2 commits into
mainfrom
polylane/autofix/92bsky8orscp

Conversation

@polylane

@polylane polylane Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Fixes: cache-app preview deployments run with production secrets, database and cron credential

A Vercel preview deployment of cache-app builds the same way as production and inherits every production secret, including the cron credential. The automations endpoint decided it was production from the runtime mode that a preview build also reports, so an unreviewed branch build could claim due runs and start production automation workflows. Requests that arrive on a preview deployment are now refused.

flowchart LR
  P["Vercel preview deployment (unreviewed branch)"] --> R["GET /api/cron/automations"]
  R --> G{"deployment target"}
  G -- "preview" --> X["404, before any work"]
  G -- "production or self-hosted" --> A["bearer CRON_SECRET check"]
  A --> W["claim due runs and start production workflows"]
Loading

What caused this

Affected: acc_0b563dc42001x121b0ek7bgc

Why this fix

This endpoint is reachable by anyone who holds the cron credential, and it starts durable production workflows. Its authorization keyed on the runtime mode, which every Vercel preview build also reports as production, so the shared credential alone was enough for a branch deployment to claim due runs.

A preview deployment is the only untrusted deploy target Vercel gives this project, and Vercel reports it directly: serverEnv.VERCEL_ENV is "preview" there, and that is the value the handler now compares. Off Vercel the variable is unset, so self-hosted installs keep their current behavior and still require the bearer secret. Production is untouched.

I did not put the comparison on the runtime mode, because it cannot distinguish a preview. A throwaway test that imported this route with the runtime mode set to production and the deployment target set to preview returned 401: the request passed the deployment check and reached the token check. The same request against this guard returns 404. That reproduces the mechanism the finding rested on.

The guard is the first statement in the handler, so a refused preview request performs no database read and starts no workflow. Nothing legitimate is lost: no in-repository or Vercel scheduler drives this route (crons.definitions is empty), so its only caller is the operator's own scheduler against production.

Remaining work, tracked on the issue: preview deployments still receive the production database, Redis, Stripe and cron credentials, so a preview route or server action can still read or write production rows. Re-scoping those project variables, or pointing preview at a separate database, and issuing a per-environment cron credential is owner action on a read-only account. See the follow-up entry on the issue timeline.

Out of scope
  • Re-scoping the project's environment variables so preview no longer receives DATABASE_URL, REDIS_URL, STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET: owner action on the read-only Vercel account, tracked on the issue timeline.
  • A per-environment CRON_SECRET: same account, same owner.
  • Deployment-failure and runtime alerting: the account has no drain, monitor or trigger configured; tracked on the issue timeline.

This change is a declared stopgap: it contains the symptom while the producer's fix is tracked as a follow-up on the issue timeline.

1 file changed (+7/-0)
  • app/api/cron/automations/route.ts: modified, +7/-0

View thread View autofix


Generated by Polylane. You can ask follow-ups by mentioning @polylane in a comment.

…yments

Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
@polylane polylane Bot added polylane severity:medium Polylane autofix severity: medium labels Sep 19, 2026
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 3523e5d0-1673-43cd-92ae-a8f39d0defba

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Sep 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cache-app Ready Ready Preview Sep 19, 2026 6:56pm UTC

@polylane

polylane Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Tip

Expected to resolve the linked issue.

Linked issue: iss_0ba833886001zmax1m730uno — "cache-app preview deployments run with production secrets, database and cron credential": a preview build could claim due runs and start production automation workflows

The guard fires only when VERCEL_ENV is "preview"; on the production deployment it stays "production", so production's cron path (6 recent runs, last 2026-09-18T21:00Z, 0 failed) is unchanged and the route is the automations service's only caller. Vercel request/error metrics return HTTP 400 on this account, so no traffic series exists to chart.

View the full analysis →

Also considered · 2 refuted
  • Refuted · A preview build still starts production automation runs via the shared CRON_SECRET · The new guard closes the only in-repo ingress: rg over the tree shows the automations service is imported by exactly app/api/cron/automations/route.ts and app/workflows/automation.ts, and the route returns 404 before claimDueAutomationRuns/recoverStaleAutomationRuns run.
  • Refuted · Narrowing the guard to "preview" re-opens the cron on a second deploy target · VERCEL_ENV is set to development only for local vercel dev runs, not for any deployed target: the pinned preset types it as the enum development|preview|production and Vercel deployments report production or preview.

cache-app · deployments per hour

Analysed against 1 Project and 1 repository

View in Polylane Disable reviews

Polylane analysed e339aed for production impact. You can ask follow-ups by mentioning @polylane in a comment.

Rate this review: 👍 helpful · 👎 not helpful · 😕 confusing

Previous verdicts (1)
Head Verdict Analysis
abb38d2 Expected to resolve the linked issue analysis

@polylane polylane Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested observability improvements for code this pull request changes. They are advisory and never affect the production-impact verdict.

Comment thread app/api/cron/automations/route.ts Outdated
Comment on lines +19 to +24
if (!isProductionDeployment(serverEnv.VERCEL_ENV)) {
return Response.json(
{ error: "Not found" },
{ headers: NO_STORE_HEADERS, status: 404 }
);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refusal path is silent today, so a preview deployment attempting to claim production automation runs leaves no queryable trace and the containment cannot be confirmed in production.

Suggested change
if (!isProductionDeployment(serverEnv.VERCEL_ENV)) {
return Response.json(
{ error: "Not found" },
{ headers: NO_STORE_HEADERS, status: 404 }
);
}
if (!isProductionDeployment(serverEnv.VERCEL_ENV)) {
log.warn("Refused automations cron outside production", {
deploymentTarget: serverEnv.VERCEL_ENV,
});
return Response.json(
{ error: "Not found" },
{ headers: NO_STORE_HEADERS, status: 404 }
);
}

…yments

Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
@polylane polylane Bot changed the title fix(automations): refuse the automations cron on non-production deployments fix(automations): refuse the automations cron on Vercel preview deployments Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

polylane severity:medium Polylane autofix severity: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant