Skip to content

Close small security gaps in dev restart, deploy upload, project tokens, and CI ownership - #377

Merged
Brooooooklyn merged 8 commits into
mainfrom
security/1a-quick-fixes
Oct 8, 2026
Merged

Brooooooklyn merged 8 commits into
mainfrom
security/1a-quick-fixes

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Attack surface before this PR

Four small, separate holes. Each one could be used by a web page, a tenant, any signed-up user, or a repo writer.

1. Dev server: script injection from any website

evil site ──link──► http://localhost:5173/__void/restarting?return=javascript:…
                        │
                        ▼
                    script runs on the dev origin ✗
                    reads source, .dev.vars and .void/dev-trigger-token,
                    reaches production bindings in remote mode

evil site ──POST──► /__void/restart   ──► dev server restarts ✗   (any site, any time)

2. Deploy upload: tenant module named metadata

tenant deploy ──► workerFiles: { "metadata": "…", "index.js": "…" }
                        │
                        ▼
API ──► Cloudflare multipart upload (platform token)
          part "metadata"  ← platform's: bindings for this tenant
          part "metadata"  ← tenant's:   bindings it chooses ✗

If Cloudflare read the tenant's part, a tenant could bind another tenant's D1, KV or R2.

3. Project token: any signed-up user could make one

token = HMAC(JWT_SECRET, projectId)       login JWT = header.body . HMAC(JWT_SECRET, "header.body")

proxy call:  projectId = "header.body",  token = JWT signature   ──► accepted ✗
                                                                     free Workers AI on the platform account

4. CI and release files had no owners

writer ──► PR edits .github/workflows/publish.yml or package.json ──► any one approval ──► merge ✗

What this PR changes

1. Dev server

/__void/restarting?return=…   return must be a same-origin path, else "/"
                              page opens only from the dev origin or the address bar (Sec-Fetch-Site)
POST /__void/restart          same-origin only (Sec-Fetch-Site, or Origin when it is missing)
                              and a per-server token that only the dev page hands to the restarting page

2. Deploy upload

module name ──► relative path? no "." or ".." segments? not "metadata"? known extension?
                ──► yes: upload        no: 400, before the deploy starts
main module must be one of the uploaded modules

3. Project token

projectId ──► matches proj_ + 12 [0-9a-z]? ──► no: 403 (before any HMAC)
                                            └► yes: check the token as before

"header.body" always has a dot, so a JWT signature can never pass. Every real project id has this form, so no live project is affected.

4. Owners

.github/  **/scripts/  pnpm-workspace.yaml  pnpm-lock.yaml  **/package.json
        └────────────────────── owned by @voidzero-dev/void ─────────────────────┘

Decisions

Choice Why
Per-server restart token on top of the origin check With void dev --host on a plain-HTTP LAN address, browsers send no Sec-Fetch-Site, and the restarting page's own POST carries the dev origin. The token closes that. The in-browser restart works as before; a hand-made POST /__void/restart (curl) now gets 403. Restarting from the terminal is unchanged.
Id format check, not a new token key No live token changes and no redeploys. The next PR adds a separate key for new tokens.
Module rules match what the CLI uploads Valid CLI deploys pass unchanged.
CODEOWNERS covers only build and release files Normal PR review for app and package code stays the same.

Left as is on purpose: an empty Worker bundle is still accepted. It breaks only the tenant's own app.

Rollout

merge ──► deploy API and proxy as usual ──► admin turns on require_code_owner_review in the main ruleset

No user or app change. Existing tokens and apps keep working.

Testing

Command Result
vp run check pass
vp run test --base origin/main pass: 13139 tests passed, 29 skipped
vp lint, vp fmt --check pass
CI 18 pass, 1 skipped

🤖 Generated with Claude Code

@Brooooooklyn
Brooooooklyn added this pull request to stack #381 October 6, 2026 21:33
@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-1a-quick-fixes-void-cloud.voidzero-docs.workers.dev (commit db153ea)

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://bce5a172-void-cloud.voidzero-docs.workers.dev db153ea 2026-10-08T09:46:53.656Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://5acd7c47-void-cloud.voidzero-docs.workers.dev ca53624 2026-10-08T04:35:40.631Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://f500d32a-void-cloud.voidzero-docs.workers.dev 5ae3b3b 2026-10-08T03:29:07.874Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://7ad75c0c-void-cloud.voidzero-docs.workers.dev f97a22b 2026-10-08T03:01:08.527Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://6fe4294e-void-cloud.voidzero-docs.workers.dev 9213a94 2026-10-07T11:54:42.208Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://dd23e842-void-cloud.voidzero-docs.workers.dev ba9ae09 2026-10-07T09:34:29.840Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://51675eff-void-cloud.voidzero-docs.workers.dev 2dbb77b 2026-10-07T02:42:04.050Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://c71776f1-void-cloud.voidzero-docs.workers.dev e067e85 2026-10-06T21:34:18.681Z Visit the dashboard ↗

@Brooooooklyn Brooooooklyn changed the title security/1a quick fixes Close small security gaps in dev restart, deploy upload, project tokens, and CI Oct 6, 2026
@Brooooooklyn
Brooooooklyn force-pushed the security/1a-quick-fixes branch from e067e85 to 2dbb77b Compare October 7, 2026 02:40
@Brooooooklyn Brooooooklyn changed the title Close small security gaps in dev restart, deploy upload, project tokens, and CI Close small security gaps in dev restart, deploy upload, project tokens, and CI ownership Oct 7, 2026
@Brooooooklyn
Brooooooklyn marked this pull request as ready for review October 7, 2026 08:09
@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-08T09:49:27.723714Z db153ea 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: 2dbb77bbb6

ℹ️ 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/id.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: ba9ae09b08

ℹ️ 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/scripts/seed.sql
@cpojer

This comment has been minimized.

@Brooooooklyn

Copy link
Copy Markdown
Member Author

Both fixed:

  • Restart over LAN HTTP (570e8d9): the toast now carries a random per-server token and POST /__void/restart refuses a request without it, so a foreign navigation can no longer restart the server.
  • Seed ids (9213a94): the seed now removes the old proj_seed1..6 projects and their non-cascading deployments, builds, domains and resources first, so setup --force reseeds cleanly.

@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/1a-quick-fixes branch from 9213a94 to f97a22b 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: f97a22b8e8

ℹ️ 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 .github/CODEOWNERS Outdated
@Brooooooklyn
Brooooooklyn force-pushed the security/1a-quick-fixes branch from 5ae3b3b to ca53624 Compare October 8, 2026 04:34
Brooooooklyn and others added 8 commits October 8, 2026 17:45
The /__void/restarting page navigated to the raw `return` query value, so
any site could send the developer to a `javascript:` URL on the dev origin.
The server now keeps `return` only when it is a same-origin path and embeds
it into the page script with safeJsonStringify; anything else falls back
to /. A request url that does not parse also falls back to / instead of
failing with a 500.

The page POSTs to /__void/restart as soon as it loads, so a link or hidden
iframe from another site could restart the dev server. The page is now
served only when Sec-Fetch-Site is same-origin, none, or absent, answers
403 otherwise, and is marked no-store. POST /__void/restart answers 403 to
cross-site browser requests, judged by Sec-Fetch-Site, or by Origin when
the browser omits it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Module names from workerFiles become multipart part names next to the
platform's own "metadata" part. The deploy API now rejects names that are
empty, absolute, contain backslashes, control characters, or ".", ".." or
empty segments, equal "metadata", or lack a module extension the CLI
uploads. It also requires the main module to be one of the uploaded
modules. The check runs before the deploy is admitted, on both direct and
staged deployment inputs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A project token is an HMAC-SHA256 over the project id under the key that
also signs login JWTs and OAuth state. A login JWT split into
projectId = "header.body" and token = signature therefore verified as a
project token at the proxy and at the API's internal project routes.

Add isProjectId() next to generateId() (proj_ plus 12 characters of
[0-9a-z]) and require it before every project-token HMAC check: the proxy's
service-binding auth and receipt verification, and the internal revalidate,
resolve-resource, resolve-worker and verify-prerender-build routes. Each
rejects a malformed id with its existing invalid-token response. The proxy
README documents the check.

Test fixtures now use ids in the generated shape.

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>
Browsers send no Fetch Metadata to plain-HTTP LAN origins, so with
`void dev --host` any page could open /__void/restarting and its
auto-POST carried the dev server's own Origin. The binding-hint toast
now navigates with a random per-server token that the restarting page
sends back, and POST /__void/restart refuses a missing or wrong token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Local databases seeded before the generated-shape ids still hold
proj_seed1..6 with the same slugs, so setup --force failed on the
unique slug and kept the old rows. The seed now deletes those projects
and their deployments, builds, domains and resources first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn
Brooooooklyn force-pushed the security/1a-quick-fixes branch from ca53624 to db153ea Compare October 8, 2026 09:45
@Brooooooklyn
Brooooooklyn merged commit ea2dfae into main Oct 8, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the security/1a-quick-fixes 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