Skip to content

[flags] accept Vercel-signed override tokens - #587

Open
luismeyer wants to merge 10 commits into
luismeyer/flag-values-platform-encryptionfrom
luismeyer/kms-signed-overrides
Open

luismeyer wants to merge 10 commits into
luismeyer/flag-values-platform-encryptionfrom
luismeyer/kms-signed-overrides

Conversation

@luismeyer

@luismeyer luismeyer commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Stacked on #586. Merge #586 first. This PR's diff is against luismeyer/flag-values-platform-encryption.

Summary

Lets v4 apps accept flag overrides that the Vercel Toolbar signs with Vercel KMS, so overrides work on Vercel without a FLAGS_SECRET. This is the SDK half of vercel/next-live-mode#3898.

  • Same cookie, same API. The SDK still reads only vercel-flag-overrides, and no exports change.
  • Dispatch by token shape. On Vercel (VERCEL), or locally after vercel env pull (VERCEL_OIDC_TOKEN), a 3-part JWS is verified as a Vercel-signed token. Everything else goes to the existing decryptOverrides, unchanged. Signed and FLAGS_SECRET tokens are dual-read, so the Toolbar can roll signing out to projects that still have a FLAGS_SECRET.
  • SvelteKit without a secret (enabled by [flags] Encrypt FlagValues with the public key of Vercel without FLAGS_SECRET #586) now accepts signed overrides. Before, it had no way to read overrides at all.
  • Verification (src/lib/flag-overrides-platform.ts, shaped like flag-values-platform.ts: exported issuer, JWKS URL and frozen FLAG_OVERRIDES_POLICY; isFlagOverridesFromPlatform / verifyFlagOverridesFromPlatform):
    • Checks: ES256 only; issuer https://kms.vercel.com/64af96a2-ba60-42ef-91af-459fcef6f864; aud vercel:flag-overrides; pur flag-overrides; ver 1; required iat/exp; at most 7-day lifetime.
    • The token header must have a kid and no jku/jwk/x5u/crit. The token is limited to 16 KB.
    • The host's project must be in scope.projectIds (all projects of a microfrontends group share the cookie), its environment in environments, and the team must match when known. Unknown scope keys are rejected. projectIds differs from scope.projectId in the v5 standard, which needs the same change.
    • An invalid token or unavailable keys means no overrides; it never throws.
  • Shared verifier (src/lib/vercel-platform.ts, which [flags] Encrypt FlagValues with the public key of Vercel without FLAGS_SECRET #586 adds with findVercelProject() and createKeySetCache()). This PR adds:
    • isVercelSignedToken() / verifyVercelSignedToken(): the checks above for a given issuer and policy, so [flags] accept Vercel-signed tokens for flags discovery #589 can use them for the discovery tokens.
    • One key set cache for each issuer, so the keys of one issuer never verify a token of another. The keys stay in memory for 5 minutes, so there is no per-request network I/O.
  • The host must know its project. On Vercel, the project must expose the system environment variables (VERCEL, VERCEL_PROJECT_ID). Locally, vercel env pull supplies the VERCEL_OIDC_TOKEN. SvelteKit reads it from $env too, because vite dev does not put .env.local in process.env.
  • A signed token which is ignored logs a warning (at most one per 30s for each cause): the host can not verify signed tokens, the keys do not load, or the token is for a different project or environment. An expired token or a bad signature logs nothing.
  • Next.js without a secret ignores an overrides cookie that the host can not read, instead of throwing flags: Missing FLAGS_SECRET. SvelteKit already did this.
  • Signed tokens aren't memoized, so they stop working when they expire.

Notes for reconciling with standard (v5)

  • standard rejects signed tokens when FLAGS_SECRET is set. This PR intentionally accepts them, because the Toolbar signs for all projects behind its rollout flag. v5 needs the same rule before it ships.
  • standard reads flag-overrides first, then vercel-flag-overrides, so v5 already works with the Toolbar writing vercel-flag-overrides.
  • The token contract matches standard's vercel.ts. This module is a reduced copy: no options object, and ES256 only.

Test plan

🤖 Generated with Claude Code

@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
flags-playground Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-dev Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-flagsmith Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-growthbook Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-launchdarkly Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-openfeature Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-posthog Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-reflag Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-snippets Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-statsig Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
flags-sdk-sveltekit-snippets Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
shirt-shop Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC
shirt-shop-api Ready Ready Preview, v0 Nov 11, 2026 11:25am UTC

@vercel
vercel Bot temporarily deployed to Preview – flags-sdk-openfeature November 3, 2026 12:11 Inactive
@vercel
vercel Bot temporarily deployed to Preview – flags-sdk-posthog November 3, 2026 12:11 Inactive
@vercel
vercel Bot temporarily deployed to Preview – flags-sdk-growthbook November 3, 2026 12:11 Inactive
@vercel
vercel Bot temporarily deployed to Preview – flags-sdk-statsig November 3, 2026 12:11 Inactive
@vercel
vercel Bot temporarily deployed to Preview – flags-sdk-vercel November 3, 2026 12:11 Inactive
@luismeyer luismeyer changed the title feat(flags): accept Vercel-signed override tokens [flags] accept Vercel-signed override tokens Nov 3, 2026
@luismeyer
luismeyer force-pushed the luismeyer/kms-signed-overrides branch from 104bfaf to ed43299 Compare November 3, 2026 18:58
@luismeyer
luismeyer changed the base branch from main to luismeyer/flag-values-platform-encryption November 3, 2026 18:58
luismeyer and others added 6 commits November 10, 2026 16:33
On Vercel, or locally after `vercel env pull`, verify override tokens which
the Vercel Toolbar signs with Vercel KMS, alongside FLAGS_SECRET tokens. Share
the host project lookup and the JWKS cache with the platform encryption of the
flag values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds group

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lues

Rename vercel-overrides to flag-overrides-platform, and use the same shape as
flag-values-platform: an exported issuer, JWKS URL and frozen policy, a
publicKeys cache, and the FromPlatform/ForPlatform names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apps which read the vercel-flag-overrides cookie themselves call
decryptOverrides, so it verifies overrides signed by Vercel KMS too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chriswdmr chriswdmr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! Two things to fix before approval:

  • SvelteKit: signed overrides do not work in local development, because vite dev does not put .env.local values in process.env
  • Replace the nested ternary in SvelteKit flag() and add braces to the if in isInScope (AGENTS.md)

The other comments are non-blocking. The "Shared with #586" part and the test count in the description are out of date.

Comment thread packages/flags/src/sveltekit/index.ts Resolved
Comment thread packages/flags/src/next/overrides.ts Resolved
publicKeys.set(issuer, keys);
return async (header, token) => {
try {
return await (await keys.get())(header, token);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion (non-blocking): The key fetch has no timeout

createKeySetCache (from #586) calls fetch with no signal. With this PR, a flag call for a request with a signed cookie waits on that fetch. If the key set endpoint does not answer, the flag call does not settle. This also occurs when keys are in memory but older than 5 minutes, because get() waits for the reload.

An AbortSignal.timeout(...) on the fetch, and the keys in memory as the result during a reload, would fix it. This can go in #586.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The timeout is in #586 (4196d37): 5 s with AbortSignal.timeout.

I did not make get() return the keys in memory during a reload. With the timeout, the wait is at most 5 s, and when the reload fails or times out, the keys in memory are used. Keys in memory as the result during each reload would let a revoked key verify tokens after the 5 minutes. Tell me if you prefer it anyway.

luismeyer and others added 4 commits November 11, 2026 11:14
…er/kms-signed-overrides

Co-Authored-By: Claude Code <noreply@anthropic.com>
- SvelteKit: verify signed tokens in vite dev, where the variables of
  vercel env pull are only in $env
- Replace the nested ternary in SvelteKit flag() with one guard
- Next.js: ignore an overrides cookie which the host can not read
  without a secret, instead of throwing
- Warn with the cause when a signed token is ignored
- Do not need a process global to check for a signed token
- Set the key cache entry only when it is missing
- More rejection tests for the verifier

Co-Authored-By: Claude Code <noreply@anthropic.com>
…he request

Co-Authored-By: Claude Code <noreply@anthropic.com>

@chriswdmr chriswdmr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thank you! Merge #586 first.

This branch was successfully deployed

14 active deployments
Preview – flags-sdk-dev — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-sveltekit-snippets — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-snippets — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – shirt-shop — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-launchdarkly — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-posthog — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-statsig — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-playground — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-openfeature — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-growthbook — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-reflag — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-flagsmith — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – flags-sdk-vercel — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
Preview – shirt-shop-api — eda6ee69 Deployed Nov 11, 2026 by vercel[bot]
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.

2 participants