Skip to content

CVE-2026-33322: OIDC JWT Algorithm Confusion

The OIDC verifier mixed the relying-party client_secret with JWKS public keys; after first implementing an opt-in HMAC compatibility path, we removed it and enforced JWKS-only asymmetric verification in d24f449.

Status: Released
First containing release: RELEASE.2026-04-17T00-00-00Z
Affected entry points: AssumeRoleWithWebIdentity, AssumeRoleWithClientGrants
Fix commit: d24f449
GitHub issue: pgsty/silo#22

The previous OIDC implementation in internal/config/identity/openid placed the relying-party client_secret into the JWT verification keyring while also accepting HMAC signing algorithms (HS256, HS384, HS512). An attacker who obtained that client_secret could therefore sign a forged ID token with HMAC and exchange it through STS (AssumeRoleWithWebIdentity or AssumeRoleWithClientGrants) for temporary S3 credentials.

The final fix (d24f449) enforces asymmetric, JWKS-only verification and deliberately drops HS256/HS384/HS512 token support rather than keeping a configuration toggle that would reintroduce ambiguous trust semantics.

Why the vulnerability was key-role confusion, not missing signature verification

The vulnerable code did verify JWT signatures before issuing STS credentials. The failure lay in which keys the verifier accepted and whether the token’s alg header could select a signing algorithm whose trust semantics did not match the key’s intended role.

In OIDC, the client_secret authenticates the relying party (SILO) to the identity provider’s Token Endpoint. It is a shared credential between the relying party and the IdP—not an asymmetric signing key used by the IdP to assert user identities. By loading client_secret into the local token.Parse keyring alongside JWKS public keys, the verifier allowed a single credential leak to escalate into full identity forgery:

  1. The attacker obtained the configured OIDC client_secret.
  2. The attacker constructed a JWT claiming arbitrary user or role attributes and signed it with HS256, HS384, or HS512 using the client_secret.
  3. The verifier matched the HMAC algorithm against the client_secret in its keyring and accepted the signature.
  4. The forged token passed AssumeRoleWithWebIdentity or AssumeRoleWithClientGrants and returned valid temporary STS credentials.

A compatibility path was implemented, then deliberately removed

During development, we first implemented an explicit algorithm-allowlist and allow_hmac-style compatibility prototype (fc47fa0). On the surface, an opt-in flag appeared reasonable: enforce asymmetric verification by default while letting deployments with HMAC-based tokens opt in explicitly.

Design review overturned that approach. Keeping client_secret anywhere inside the local JWT verification keyring meant that enabling the option would silently widen the entire STS trust boundary: anyone holding the relying-party secret could again mint arbitrary user tokens. Furthermore, any future drift in the parser’s algorithm allowlist or JWKS retry path could reopen the vulnerability by default.

Option Benefit Risk Decision
Keep client_secret in keyring and filter algorithms Minimal code diff; preserves HMAC IdPs Still conflates Token Endpoint client auth with local JWT signature verification Rejected
Add an allow_hmac configuration option (fc47fa0) Makes HMAC compatibility explicit Difficult for operators to reason about safely; expands parser and retry test surface Implemented, then removed
Enforce JWKS-only asymmetric verification (d24f449) Clean separation of key roles; primary parse and JWKS refresh share one parser policy Identity providers issuing HS256/384/512 tokens must migrate Adopted

Removing the completed compatibility implementation produced a much simpler and more auditable security boundary.

Final invariants

Commit d24f449 establishes four invariants in internal/config/identity/openid:

  • Verifier keys are loaded exclusively from the identity provider’s JWKS endpoint (jwks_uri).
  • The OIDC client_secret never enters the local JWT verification keyring.
  • HS256, HS384, and HS512 tokens are rejected unconditionally.
  • Both the initial token verification pass and the JWKS cache-refresh/retry path use the same asymmetric algorithm allowlist (RS256, RS384, RS512, ES256, ES384, ES512).

We also kept the algorithm scope tight rather than expanding JOSE support during a security fix: PS256 and EdDSA remain unsupported.

Verification and release

The test suite in internal/config/identity/openid covers HS256 token rejection, RS256 token acceptance, and JWKS refresh/retry behavior when a key rotation occurs. All temporary compatibility helpers, configuration flags, and HMAC opt-in tests from the initial prototype were stripped from the final commit (d24f449), which shipped in RELEASE.2026-04-17T00-00-00Z.

go test ./internal/config/identity/openid -count=1

Compatibility trade-offs

This fix introduces an intentional breaking change. Identity providers configured to issue HS256, HS384, or HS512 ID tokens must migrate to JWKS-backed RSA or ECDSA signing keys before upgrading SILO. If you suspect the OIDC client_secret was exposed prior to upgrading, rotate the secret in both the identity provider and SILO configuration.