ci: platform-prod-secrets mints a fresh link keyset and drops the JWT copy

Merged
TE
4d ago
+114-18

What. .github/workflows/platform-prod-secrets.yml (dispatch-only, environment cmux-next-production) now puts two secrets on the cmux-platform-production Worker instead of three:

  • CLOUD_LINK_SIGNING_KEYS: when the Worker lacks it, the job mints a fresh value with the new scripts/cmux-next/release/mint-link-keyset.mjs and pipes it into wrangler secret put. The environment's secrets.CLOUD_LINK_SIGNING_KEYS is no longer read.
  • CLOUD_FREESTYLE_API_KEY: unchanged (from the environment's FREESTYLE_API_KEY, only when missing).
  • JWT_PRIVATE_JWK: dropped from env and the loop.

Why. The platform takes over NEXT Cloud's CloudDO with a fresh namespace. Production has never issued a Cloud link token, and link tokens are verified only by Cloud VMs, against the keyset the platform itself serves (the bind answer and GET /v1/cloud/keyset, both forwarded to the platform). Nothing verifies a link across Workers, so a fresh keyset minted for the platform is correct and cmux-api's copy (a Worker secret only) is not needed. The platform verifies cmux-api's install tokens against cmux-api's public JWKS (a separate cmux-platform change), so it needs no JWT_PRIVATE_JWK copy.

Format. {"active":"<kid>","keys":{"<kid>":{"kty":"OKP","crv":"Ed25519","x":...,"d":...,"kid":"<kid>"}}}, kid production-<YYYY-MM-DD> (UTC), matching /^[A-Za-z0-9._-]{1,64}$/. This is what parseSigningKeys accepts in backend/apps/api/src/link-token.ts (and the identical cmux-platform parser). One kid with no published_at signs at once (signingKid), which is what we want since no production VM holds an older keyset.

Guarantees.

  • Never prints or writes the value: the script writes only to stdout; the job holds it in a shell variable and pipes it to wrangler's stdin. No echo, no file, no set -x.
  • Never overwrites: each name is put only when wrangler secret list does not show it.
  • Fails closed: set -euo pipefail, the mint is a command substitution (a failed mint stops the job), and an empty value is refused before anything is put. The script itself exits non-zero, printing nothing, on a bad kid or an unexpected JWK shape.
  • runs-on is unchanged. The checkout is sparse (just the mint script) with persist-credentials: false.

Tests.

  • New backend/apps/api/test-node/link-keyset-mint.test.ts (Node vitest, runs in the backend workflow's test:node step; the mint script was added to that workflow's paths): parseSigningKeys accepts the output, exactly one kid, it is the active kid, signingKid(keys, Date.now()) returns it, publicKeyset has no d, two runs differ, and a bad prefix exits 2 with empty stdout. Passed locally (3/3).
  • python3 tests/test_ci_linux_single_size.py, tests/test_ci_fork_runner_routing.py, tests/test_ci_no_single_size_pins.py, tests/test_ci_checkout_network_diagnostics.py, tests/test_ci_actionlint_covers_every_workflow.py: all pass.

Agent session: agent session

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Reworks platform-prod-secrets.yml so cmux-platform-production gets a freshly minted Cloud link signing keyset instead of a copy from cmux-next-production, and drops JWT_PRIVATE_JWK from the workflow.

Production never issued a link token, and link tokens are verified only by Cloud VMs against the keyset the platform itself serves. The platform verifies cmux-api's install tokens against cmux-api's public JWKS, so it needs no private JWK copy.

  • scripts/cmux-next/release/mint-link-keyset.mjs mints a single Ed25519 kid (production-<UTC date>) that signs immediately; the workflow pipes its stdout into wrangler secret put only when the Worker lacks the secret.
  • The minted value is never printed or written to disk, and a failed or empty mint exits the job before anything is put. Existing secrets are never overwritten. CLOUD_FREESTYLE_API_KEY is unchanged.
  • The secret list is re-read right before each put, and a failed or unreadable list stops the job instead of reading as "absent", narrowing the race window to the seconds of one put.
  • New link-keyset-mint.test.ts runs in the backend test:node step and validates the keyset against parseSigningKeys, signingKid, and publicKeyset; the mint script was added to that workflow's trigger paths.
  • The workflow is dispatch-only, so the change applies on the next dispatch from feat-cmux-next.