Visitar URL original
fix: auto-select url-signing JWK from JWT_JWKS (closes #629) by 61465 · Pull Request #1488 · supabase/storage · GitHub
Skip to content

fix: auto-select url-signing JWK from JWT_JWKS (closes #629) - #1488

Open
61465 wants to merge 4 commits into
supabase:masterfrom
61465:fix/jwk-signing-key-629
Open

61465 wants to merge 4 commits into
supabase:masterfrom
61465:fix/jwk-signing-key-629

Conversation

@61465

@61465 61465 commented Oct 7, 2026

Copy link
Copy Markdown

What this does

Closes #629.

Self-hosted deployments that supply only the JWT_JWKS env config could not sign storage URLs with an asymmetric key: urlSigningKey was auto-populated only in the multi-tenant JWKS loader, so single-tenant installs with an EC signing key silently fell back to the HMAC jwtSecret. The reporter spent debugging time tracing a "storage outage" to this.

This PR makes the single-tenant env parser mirror the per-tenant selection logic that already lives in jwksManager, and adds a one-shot startup warning for the mis-configuration shape the reporter actually hit.

Changes

  • src/config.ts — new exported helpers isUrlSigningCapableJwk + pickUrlSigningKey + describeJwtJwksMisconfiguration. JWT_JWKS parsing now auto-populates urlSigningKey with the first signing-capable JWK (oct with k or EC with d, skipping use: "enc" per RFC 7517 §4.2), preserving an explicit value when supplied.
  • src/internal/auth/jwks/manager.ts — tenant JWKS loader now uses the shared capability check (DRY, behavior unchanged).
  • src/internal/database/tenant.ts — mergeTenantJwksWithLegacyKeys now preserves the tenant-provisioned urlSigningKey with a fallback picked from the merged key list. getSingleTenantJwtConfig emits a one-shot logSchema.warning when JWT_JWKS has keys but none can sign URLs, logging only key count + kty list + configured urlSigningJwkType (no key material, no HMAC secret).
  • .env.sample — documented the URL-signing contract.
  • src/config.test.ts — 25 unit tests across four groups: direct capability-helper tests, config auto-selection, mis-configuration detection, and a jose round-trip proving the auto-selected EC key produces an ES256 token header instead of HS256.

RSA and OKP keys stay verification-only — this matches the existing UrlSigningJwksConfigKey = oct | EC and URL_SIGNING_JWK_TYPES = ['HS512', 'ES256'] whitelist. HS256 default deployments (no JWT_JWKS) are unaffected.

Backward compatibility

  • Deployments without JWT_JWKS → unchanged
  • Deployments with JWT_JWKS containing an explicit urlSigningKey → unchanged (explicit value is preserved)
  • Deployments with JWT_JWKS containing only verification-shaped keys → unchanged behavior (HMAC fallback) + one new startup warning
  • Deployments with JWT_JWKS containing a signing-capable oct or EC key → URLs are now actually signed with that key instead of the HMAC jwtSecret (the behavior the issue reporter expected)

Test plan

  • npm run test:unit -- src/config.test.ts — 25 new tests pass
  • JWK-adjacent suites green: src/config.test.ts src/internal/auth src/http/plugins/signature-v4.test.ts → 174/175 pass (one pre-existing env-leakage unrelated to this change)
  • Round-trip test proves the auto-selected EC key produces alg: "ES256" in the signed token header
  • Red-team guard proves the warning payload never carries the JWK's n, e, k, d, or kid
  • Biome + Prettier clean on all touched files
  • npx tsc --noEmit clean on the changed files (one unrelated pre-existing error in src/internal/cluster/eks.ts)

61465 added 4 commits October 8, 2026 00:31
Self-hosted deployments that supply only the JWT_JWKS env config could not
sign storage URLs with an asymmetric key: urlSigningKey was auto-populated
only in the multi-tenant JWKS loader, so single-tenant installs with an EC
signing key silently fell back to the HMAC jwtSecret.

- add isUrlSigningCapableJwk / pickUrlSigningKey helpers in config.ts
  (oct with k or EC with d; reject use="enc" per RFC 7517 §4.2)
- parse JWT_JWKS with auto-populated urlSigningKey when the field is
  absent, preserving an explicit value when provided
- mergeTenantJwksWithLegacyKeys now preserves the tenant urlSigningKey
  with a fallback picked from the merged key list
- refactor jwksManager tenant loader to use the shared capability check
- document the URL signing contract in .env.sample
- 9 unit tests covering selection, exclusion, and backward compatibility

HS256 default deployments (no JWT_JWKS) are unaffected.
…supabase#629)

Adds a unit-level round-trip test that provisions JWT_JWKS with only an
ES256 EC key, lets the config parser auto-populate urlSigningKey, calls
the real signJWT, and verifies the token header reports alg=ES256 with
the expected kid — proving asymmetric signing actually happens rather
than silently falling back to the HS256 jwtSecret.

Also adds a negative control that an RSA-only JWT_JWKS still leaves
urlSigningKey unset (RSA cannot sign storage URLs per the maintainer
whitelist) and that the keys are still parsed for verification.
Operators who provisioned JWT_JWKS without a URL-signing-capable key
previously had storage URL signing silently fall back to the HMAC
jwtSecret, with the only observable symptom being the signed-URL
header bytes. The github issue reporter spent debugging time tracing
this to the config. Now the first call that reads the single-tenant
JWT config emits a one-shot startup warning describing the mismatch.

- describeJwtJwksMisconfiguration helper in config.ts (pure, no logger
  dependency so it stays importable from the module graph that logger
  itself depends on)
- getSingleTenantJwtConfig emits the warning via logSchema.warning
  once per process, keeping the fast path allocation-free afterwards
- warning metadata exposes only key count, kty list, and configured
  urlSigningJwkType — no key material, no HMAC secret
- 5 unit tests pinning the dangerous shape and the four excluded
  silent-safe scenarios, with an explicit red-team guard that the
  serialized description never carries key material

HS256 default deployments and well-configured JWT_JWKS tenants stay
silent.
…pabase#629)

The two capability helpers are consumed by three independent code
paths (JWT_JWKS env parser, tenant JWKS merge, per-tenant jwksManager
loader). Previously they were only reached indirectly through the
config-parsing and misconfiguration-detection tests. Add dedicated
table-driven tests that pin every branch of the capability predicate
(oct/EC with and without private material, RSA, OKP, use="enc" for
both key types) and the ordering/empty-list semantics of the picker,
so a future refactor that touches these helpers surfaces a direct
failure instead of a confusing cross-module regression.

This branch has not been deployed

No deployments
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.

JWK not used in signObjectURL

1 participant