Skip to content

Rotate project proxy tokens when access changes - #379

Merged
Brooooooklyn merged 29 commits into
security/1b-proxy-token-v2from
security/1c-proxy-token-rotation
Oct 8, 2026
Merged

Brooooooklyn merged 29 commits into
security/1b-proxy-token-v2from
security/1c-proxy-token-rotation

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Attack surface before this PR

Every tenant Worker holds a project token, __VOID_PROXY_TOKEN. With it, the Worker uses AI, email, D1, KV and R2 through the platform proxy.

1. A removed teammate keeps access forever

collaborator deploys code that reads env.__VOID_PROXY_TOKEN ──► keeps a copy
        │
        ▼
owner removes the collaborator ──► token still works ✗
                                   D1 SQL, KV, R2, AI, email — forever

The previous PR added numbered tokens (<generation>.<signature>) and a per-project floor. Nothing raised the floor yet.

2. A deleted project's token still works

owner deletes project ──► floor and ai-block removed ──► old token on /ai/run ──► accepted ✗
                                                         (Workers AI on the platform account)

3. Internal routes still take a project token

internet ──► public API host /internal/resolve-worker, resolve-resource, verify-prerender-build
             x-void-internal: <project token>  ──► accepted ✗  (proxy-only routes)

What this PR changes

access change ──► rotation ──► every Worker gets a new token ──► floor rises ──► old token refused ✓
new deploy    ──► Worker gets a numbered token
/internal/*   ──► only the proxy's own key (x-void-internal no longer accepted) ✓

When a project is rotated

Event Rotates?
A collaborator or admin is removed, leaves, or their account is deleted Yes
A collaborator or admin is changed to reader Yes
A platform admin suspends a collaborator or admin Yes
A user is suspended for a usage limit No (they can still deploy)
A project is deleted Its floor is closed: no token of that project works again
A reader is removed No (readers can't deploy code, so they can't read the token)
A deploy or rollback replaces code written by someone who can no longer deploy Yes, again
An operator runs void platform system rotate-proxy-tokens --project <id> --yes (or --all) Yes
Platform upgrade No. Old tokens stay valid until the project has one of the events above.

We assume no current user has copied a token, so there is no fleet-wide rotation on upgrade.

How one rotation works (about 6 minutes)

1. new number   2. each Worker          3. record          4. lock out     5. wait
   gen + 1  ──►    gets new token  ──►    which token  ──►   old token  ──►  for caches
   (database)      (Cloudflare setting,   it holds           refused         to catch up
                   no rebuild)            (+ cron/queue copies)
  • Senders always send the token that a Worker holds (step 3 records it). They never retry with another token.
  • The floor rises only after every Worker holds the new token.
  • If a step fails, the job retries after 15 minutes. If it failed after a PATCH may have reached a Worker, it retries on the next 2-minute run, once per run, until it commits.
  • Workers kept for rollback get the new token too, except code from people who lost access.
  • Live WebSocket connections reconnect once (step 2 starts a new Worker version).

A project's life after this PR

today (old token) ──deploy──► new Worker gets a numbered token; older Workers keep the old token (still accepted)
                  ──member removed──► rotation ──► every Worker gets a new token ──► old token refused

What people see

Who What changes
App users Nothing.
Project owners and admins If a rotation fails 4 times in a row (about 1 hour), void project status and void project team list show a warning.
Operators New command: void platform system rotate-proxy-tokens. --plan shows progress. --project <id> --yes or --all --yes starts a rotation.

There is no outage on upgrade.

Before merging

  • Cloudflare check. Done live on a test account (2026-10-07), with the real rotation client, 6 rotations:
    bindings: 13 others (plain_text, secret_text, d1, kv, r2, queue, service, durable_object, assets) byte-identical
    secrets:  upload-metadata and PUT /secrets ones both kept
    code:     no upload, same etag; compat, tags, tail_consumers, observability, placement unchanged
    switch:   new token at 0.4–11 s, last old token at ≤ 18 s  (drain wait is 120 s)
    
  • Add rotatable proxy tokens and a proxy-only key for internal API routes #378 is live on prod. The proxy must understand token generations first.

After it reaches staging, rotate one test project with --project <id> --yes. Then check AI, email, queues, crons, remote D1/KV/R2, prerendering and WebSocket reconnects on that app.

After deploy, alert on log lines [proxy-token-rotation] FAILED ... attempts=4 or higher.

Known limits (on purpose)

  • Very old projects. A project whose live Worker is from before early March 2026 (named user-<slug>, token under the old name __AI_TOKEN) cannot rotate. The rotation stops at its first step and retries every 15 minutes, the floor stays, and the app keeps working. Owners and admins see the stuck-rotation warning after about an hour. These projects already cannot deploy (project_requires_normalization), so an operator must repair them before the rotation can finish. Only AI used that old token. platform/meta/operations.md has a query that lists these projects. Hosted only.

  • Code from a removed teammate keeps running while it is the live deployment, and gets each new token. The project rotates again as soon as a deploy or rollback replaces it. Kept copies of that code get no new tokens, so a rollback to one fails its AI, email and ISR revalidation calls until a redeploy. Remote D1/KV/R2 keep working.

  • Short switch window. While Cloudflare moves a Worker to its new version, a call can reach the old version and get a clean 403. Cron runs in that window are dropped ([cron-fires] AUTH_DRIFT); queue messages are retried after 120 s.

  • Failed step. If a step fails right after a token change, that Worker's proxy calls get 403, its queue messages use up retries and its cron fires are dropped until the next 2-minute run retries the step. A failure that repeats keeps this going, one retry per run.

  • Cache delay. The floor is in Cloudflare KV. In rare cases, the old token can work for a short time after a rotation completes.

  • Platform rollback. Do not roll back below this release. Each Worker deploy moves its project to token generation 1, which 0.26.0 and earlier cannot read, so every project deployed since the upgrade loses AI, email, ISR revalidation, remote D1/KV/R2 and prerendering. A release with only the token-generation PR is not a full fallback either: its proxy sends those Workers the old token, so remote D1/KV/R2 and prerendering fail. void platform rollback does not block it; the operator docs say not to. After upgrading again from any rollback, run rotate-proxy-tokens --all --yes and check with --plan.

    upgrade ──► deploy ──► project at generation 1
    rollback to 0.26 ──► proxy reads only the legacy token ──► AI, email, ISR, remote bindings, prerender ✗
    

Testing

Command Result
vp run check pass
vp run test --base security/1b-proxy-token-v2 pass: 13401 tests passed, 30 skipped
vp test run (whole stack, on main cf45d55db) pass: 13691 tests passed, 30 skipped
vp lint pass
vp fmt --check pass

🤖 Generated with Claude Code

@Brooooooklyn
Brooooooklyn added this pull request to stack #381 October 6, 2026 21:33
@Brooooooklyn Brooooooklyn changed the title security/1c proxy token rotation Rotate project proxy tokens across all Workers Oct 6, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://security-1c-proxy-token-rotation-void-cloud.voidzero-docs.workers.dev (commit 836bb81)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://8d706452-void-cloud.voidzero-docs.workers.dev 836bb81 2026-10-08T12:30:25.495Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://475d9ccf-void-cloud.voidzero-docs.workers.dev b4a0522 2026-10-08T09:46:55.532Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://50cab0f3-void-cloud.voidzero-docs.workers.dev 3e4d53f 2026-10-08T07:20:53.936Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://3fb1c504-void-cloud.voidzero-docs.workers.dev d1f21cf 2026-10-08T07:10:14.174Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://fe77fbf2-void-cloud.voidzero-docs.workers.dev 40b9b20 2026-10-08T04:35:34.066Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://c14df8a2-void-cloud.voidzero-docs.workers.dev 3f40d89 2026-10-08T03:43:10.338Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://c0b8505f-void-cloud.voidzero-docs.workers.dev 9a164e1 2026-10-08T03:29:13.304Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://65df1422-void-cloud.voidzero-docs.workers.dev 671d0a0 2026-10-08T03:00:45.999Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://42fdc302-void-cloud.voidzero-docs.workers.dev a777bcd 2026-10-07T12:37:07.448Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a39d26d8-void-cloud.voidzero-docs.workers.dev 51c20df 2026-10-07T12:28:51.337Z Visit the dashboard ↗

View all previews: View all previews ↗

@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from 767e231 to ed2d039 Compare October 7, 2026 02:40
@Brooooooklyn Brooooooklyn mentioned this pull request Oct 7, 2026
1 task
@Brooooooklyn Brooooooklyn changed the title Rotate project proxy tokens across all Workers Rotate project proxy tokens and remove the legacy token Oct 7, 2026
@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from a7f2543 to 5a74f1e Compare October 7, 2026 07:55
@Brooooooklyn Brooooooklyn changed the title Rotate project proxy tokens and remove the legacy token Rotate project proxy tokens when access changes Oct 7, 2026
@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from 5a74f1e to 6562595 Compare October 7, 2026 08:22
@Brooooooklyn
Brooooooklyn marked this pull request as ready for review October 7, 2026 09:24
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T12:33:56.019503Z 836bb81 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65625950e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/proxy-token-rotation.ts
Comment thread platform/packages/api/src/proxy-token-rotation.ts
@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from 6562595 to 8d4a14c Compare October 7, 2026 09:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d4a14c30c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/db/admin.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82852f37ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/db/admin.ts
Comment thread platform/packages/api/src/proxy-token-rotation.ts Outdated
@cpojer

This comment has been minimized.

@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from 82852f3 to 51c20df Compare October 7, 2026 12:27
@Brooooooklyn

Copy link
Copy Markdown
Member Author

All five handled:

  • Deleting a project now writes a closed floor (retried as a cleanup job) instead of removing the key, so no token of a deleted project works again, including the original one.
  • Retained code of former members gets no new tokens; a rollback to it is allowed and its platform calls fail until a redeploy.
  • Rollback to an original-token Worker: each deployment row now records the generation its Worker holds, resolve-worker returns it, and senders send exactly that token with no retries.
  • Deployments from before teams are attributed to their billing user (the owner at the time, since only owners could deploy then), so the same trust check applies without rotating every old project.
  • Admin suspension: rotation requests are now queued in the transaction that first writes the suspension.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51c20df3db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/proxy-token-rotation-cloudflare.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a777bcd03b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/provisioning/cleanup.ts
@Brooooooklyn
Brooooooklyn removed this pull request from stack #381 October 7, 2026 19:58
@Brooooooklyn
Brooooooklyn added this pull request to stack #385 October 7, 2026 19:58
@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from a777bcd to 671d0a0 Compare October 8, 2026 02:59

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 671d0a021a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/db/admin.ts Outdated
@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from 671d0a0 to 9a164e1 Compare October 8, 2026 03:28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a164e10b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/routes/internal-resolve-worker.ts
Brooooooklyn and others added 25 commits October 8, 2026 17:45
…s replaced

Removing a collaborator or admin, changing one to reader, or deleting a
user who was one requests a proxy token rotation in the same transaction
as the member change. Readers never trigger it.

While code from a user who can no longer deploy is active, it receives
every new token. The deploy cutover, the recovered cutover and rollback
now request one more rotation right before they replace such code, so
protection starts once the code stops running. Owner suspension does not
count as lost access, and rows without a recorded author never trigger it.

The request writer moves to its own module so the db/ modules can call it
without importing db.ts back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
void platform system rotate-proxy-tokens [--project <id>] [--all]
requests proxy token rotations and advances them within a bounded budget
(150 Cloudflare calls, 60 seconds); the scheduled run finishes the rest.
Without --project it covers every project still on its original token;
--all covers every project. On a job already past START, --project and
--all add one more generation, so the run always ends on a token minted
after the command. The response is 207 until no rotation job is left in
scope and lists the jobs that keep failing.

--plan reads the database only and never returns a token: for one
project its generation, job state and Workers; otherwise counts of
legacy projects, projects a run would queue, and pending and failed
jobs. The audit target is the project ID or "all". Platforms without a
Cloudflare API token, or under the development auth bypass, get 409.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a project's proxy token rotation has failed four steps in a row,
GET /projects/:id/deployments and GET /projects/:id/members add a fixed
warning for owners and admins (never for readers, collaborators or deploy
tokens). `void project status` and `void project team list` print it
before their output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Describe the rotate-proxy-tokens operator command, the one-time rotation
after upgrading and the rollback limits for rotated projects. Tell owners
that removing a collaborator or admin, or changing one to reader, replaces
the project's platform credentials, that deploying trusted code replaces
them again, and that a stuck replacement shows a warning in project status
and team list. Note that deployments kept for rollback also receive the
new credentials and their Durable Object alarms may keep running.

For maintainers, describe the release order, the staging canary, the
failure log to alert on, and running a rotation for every project if the
routing namespace is recreated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Releases older than the one that accepts rotated credentials drop rotation
jobs that have not finished, and Workers deployed while they run carry the
legacy token. Upgrading again does not restore either, so tell operators to
rerun the rotation for every project and check its progress.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A deploy of a project still on generation 0 now moves it to generation 1
and bakes tok(1) into the new Worker, so new code never receives the
legacy token. The accept floor stays at 0 and no rotation is queued: older
Workers of the project keep the legacy token, which stays accepted, and
the proxy tries it after generation 1. The project leaves the legacy token
when a rotation raises its floor.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The operator command no longer has a default scope for projects still on
the legacy token. Applying needs --project <id> or --all; --plan without
either shows the progress of every rotation, and --plan --all also counts
the projects --all would queue. The bulk request now always covers every
project, and the unused legacy-init reason is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The resolve-worker, resolve-resource and verify-prerender-build routes now
require x-void-proxy-internal and ignore x-void-internal, so a tenant project
token no longer authenticates them and they never read the token floor. The
proxy stops sending the project token on those calls. /internal/revalidate
still checks the forwarded project token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The runbook states the release order (the release that accepts token
generations first, then rotation), that a deploy issues generation 1 while
older Workers keep the accepted legacy token, and that Workers deployed
before the binding rename must be redeployed before their project rotates.
The rollback notes cover projects deployed since the upgrade and keeping
the API and proxy on the same release.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An administrator suspension ends a collaborator's or admin's deploy access,
the same as deleting the account, so request a rotation for each project
where the user holds one of those roles, in the suspension transaction.
Usage-limit suspensions keep deploy access and request nothing; owned
projects are left alone, as with account deletion.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If Cloudflare applied a settings PATCH but the step then failed (lost
response or rolled-back transaction), the retry found the Worker already on
the new token, recorded no lastPatchAt, and REVOKE could raise the floor at
once while the Worker's old version still served. When the step's previous
attempt failed and the Worker already holds the new token, record
lastPatchAt as if it was patched now. A deploy that baked the new token
still skips the wait.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deleting the project removed its accept floor, which brought back floor 0:
the legacy token and every revoked generation worked again on routes that
check only the token, such as /ai/run. Deletion now writes a floor above any
issued generation instead, through a new routing-put cleanup job that the
retry runner repeats when the KV write fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A rotation patched every retained Worker, including code from a user who can
no longer deploy, so a rotation that followed the replacement of that code
still handed it the new token. The walk now skips retained Workers whose
author has lost deploy access; they keep the old token, which the raised
floor rejects. The active Worker is always patched. A rollback to a skipped
Worker is still allowed, and its platform calls fail until a redeploy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deployments made before project teams have no recorded author, so the
replacement rotation skipped them and the rotation walk patched them as
trusted code. That includes a transferred project's history when the former
owner is later removed. Only the owner could deploy back then, and the billing
user backfilled for those rows is that owner, so the author check now falls
back to it. A project that was never transferred keeps the current owner as
author and does not rotate on its next deploy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Suspending a platform administrator writes the suspension before the routing
update, but the rotation requests were queued only by the final suspend step.
When the routing update failed, the account stayed suspended and no rotation
was queued until an operator retried. The requests are now queued in the
same transaction that first writes the suspension; the final step still
queues them, and a pending request absorbs the repeat.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A tenant Worker runs its own middleware before the generated handlers check
the proxy token, so a sender must present the one token the Worker holds and
never replay a rejected call with another.

Each deployment now records the generation its Worker holds
(deployments.proxy_token_gen, default 0: every Worker deployed before this
release holds the legacy token). A deploy records the generation it bakes,
and resolve-worker reports the active deployment's generation instead of the
project's issued one, so the proxy sends a rolled-back gen-0 Worker the
legacy token. Rollback and cutover replay give the queue entries and the
cron scheduler only the token the target Worker holds.

Rotation no longer stages a fallback: START goes straight to the PATCH walk.
After Cloudflare confirms a PATCH, the same step records the new generation
on the deployment row and, for the active Worker, points its queue entries
and cron scheduler at the new token. A step that finds a Worker already
holding the new token, but a row that does not say so, records it and drains
it as if just patched. REVOKE walks the Workers again while any row it would
cut off records an older generation.

A queue relay rejected with 401/403 is retried after 120 seconds, once the
queue entry has caught up, instead of at once. Cron fires stay acked with
AUTH_DRIFT. The only remaining gap is the drain right after a PATCH.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A rotation recorded the new generation on the row of a Worker that has no
__VOID_PROXY_TOKEN binding, although nothing was patched. Senders then
presented a token that Worker does not hold. Such a Worker is now skipped
like one whose author cannot deploy: its row, queue entries and cron
scheduler keep the generation it holds, the step logs that it needs a
redeploy, and REVOKE raises the floor without waiting for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A rollback to a deployment that kept its old token still serves remote
D1, KV and R2, because the proxy sends the token its deployment row
records; only the Worker's own AI, email and ISR revalidation calls fail.

Workers that hold __AI_TOKEN are never skipped by a rotation. Their
projects have no active deployment, so the rotation stops at its first
step and the floor never rises; deploys and normalization are refused
for the same projects, so they need an operator repair. The new query
lists them.

Every Worker deploy now moves a project to token generation 1, so a
platform rollback to 0.26.0 or earlier breaks every project deployed or
rotated since the upgrade, not only rotated ones. The rollback command
still allows it, so the docs tell operators not to do it and how to
recover.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A 429 reschedules the rotation without counting a failed attempt, because
owners cannot fix a platform-wide rate limit. Until now it also logged
nothing, so a rotation that stayed limited was invisible to operators.
Log a RATE-LIMITED line on each 429 and document the alert.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn
Brooooooklyn force-pushed the security/1c-proxy-token-rotation branch from 3e4d53f to b4a0522 Compare October 8, 2026 09:45
When Cloudflare applies a step's PATCH but the step then fails (a copy
write, the row update, the commit, or a lost PATCH response), the Worker
holds the new token while its row still names the old one, and so do
the queue and cron copies of the active Worker. The proxy, the queue
relay and the cron consumer then send a token the Worker rejects: remote
D1/KV/R2 get 403, queue messages use up their retries, and cron fires
are dropped. A retained Worker in this state breaks the same way once a
rollback makes it active. The job used to retry only after 15 minutes.

Retry such a step on the next cron run. A scheduled run now tries each
job at most once, so a step that is due again at once waits for the
next run, and a lasting failure costs one attempt per run. Steps that
fail before any PATCH keep the 15-minute delay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn
Brooooooklyn merged commit 737fccd into main Oct 8, 2026
19 of 20 checks passed
@Brooooooklyn
Brooooooklyn deleted the security/1c-proxy-token-rotation branch October 8, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants