Skip to content

feat(auth): bind a JWKS key to the tenants it may sign for - #18

Merged
Optale-ops merged 2 commits into
mainfrom
security/jwt-key-tenant-binding
Oct 4, 2026
Merged

Optale-ops merged 2 commits into
mainfrom
security/jwt-key-tenant-binding

Conversation

@Optale-ops

@Optale-ops Optale-ops commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

What

A JWKS entry in CODEAPI_JWT_JWKS_JSON may carry "tenants": ["<tenant_id>", ...]. A token signed by that key is accepted only when its tenant_id is in the list; otherwise 401 with reason tenant_not_allowed.

Why

Today any trusted key can mint code runs for any tenant: the engine takes the tenant from the token. AX41 trusts the staging key, so a holder of the staging private key can run code as any production tenant. Thor chose option A (bind keys to tenants in the engine). Finding: "Production code engine trusts lab and staging signing keys".

Behaviour

  • Keys without tenants behave exactly as before.
  • A bound key decides on the signed tenant_id only: a token without one is refused even if the default namespace (CODEAPI_JWT_SINGLE_TENANT_ID or legacy) is listed.
  • A bound kid configured again in any key source (a second JWKS entry, the key directory, CODEAPI_JWT_PUBLIC_KEY, the HS256 secret) fails startup instead of being replaced by an unbound copy; a tenants list on an entry without a kid fails startup.
  • The check runs after signature and claim validation.
  • A malformed list (empty, not an array, empty/padded strings, non-strings) is a config error, so startup.ts fails at boot instead of trusting the key for every tenant.
  • Only JWKS entries can be bound; CODEAPI_JWT_PUBLIC_KEY, CODEAPI_JWT_PUBLIC_KEYS_DIR and HS256 keys are unchanged.

Proof

bun test src/auth src/middleware: 54/54. The review-repair tests (signed tenant_id with a listed default, kidless binding, second key source) fail on cb089f3 (3 fail, 20 pass) and pass here.

Deploy

api only (the only verifier). Config change on AX41: add tenants to the codeapi-staging-20260916 entry. The plan is in the Security PRIMARY artifact AX41-KEY-TENANT-BINDING-DEPLOY.md. Ships with the fixed #16 redeploy.

A JWKS entry may carry a tenants list; tokens signed by that key are accepted
only for those tenant_id values (reason tenant_not_allowed, 401). A bound key
never falls back to the single-tenant namespace. A malformed list is a
configuration error at startup, never an unbound key. Keys without the member
behave as before.
Review repairs for the tenant binding: a bound key is checked against the
signed tenant_id, so a token without one is refused even when the default
namespace is listed; a bound kid configured again in any key source fails
startup instead of being silently replaced by an unbound copy; a tenants
list on an entry without a kid fails startup. Drops two prose pins
(synthetic weak-token message, rate-limit message).
@Optale-ops
Optale-ops merged commit 88d2dfc into main Oct 4, 2026
5 checks passed
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.

1 participant