Summary
The OIDC backend credential cache keys entries on the role ARN alone, while the credential that gets minted is a function of the full identity (role_arn, subject, extra_claims). Today those always co-vary, so there is no live vulnerability — but the key does not enforce that invariant, leaving a latent cross-identity credential-reuse hazard for future callers.
Surfaced during the review of #61 (shared single-flight credential cache).
Current behavior (not exploitable today)
OidcCredentialProvider::get_credentials(cache_key, exchange, subject, extra_claims) caches on cache_key but federates using subject + extra_claims (crates/oidc-provider/src/lib.rs).
- The only production caller,
AwsBackendAuth::resolve_aws (crates/oidc-provider/src/backend_auth.rs), derives both the key (role_arn from oidc_role_arn) and subject (from oidc_subject, default "s3-proxy") from the same BucketConfig, and always passes extra_claims = &[].
resolve_aws never reads the inbound caller identity — the proxy federates as itself into the backend, and inbound authorization is enforced upstream. So two callers hitting the same OIDC bucket correctly share one backend STS session.
Because (role_arn, subject, extra_claims) move in lockstep per bucket config, the cache key is sufficient in the current code.
The latent risk
Nothing in the type system or the cache enforces that the key captures everything the minted JWT encodes. If a future caller ever passes a caller-derived subject or a non-empty extra_claims (e.g. per-caller source_identity for STS session tagging) while keeping the role_arn-based key, the cache would serve identity A's backend session to identity B — a real cross-identity credential leak.
The pre-#61 cache docstring even hinted at the ambiguity ("an opaque key the caller chooses — e.g. a role ARN, or the rendered OIDC subject"), so this predates the merge.
Recommended fix
Make the cache key derive from the full minted identity rather than trusting the caller to pass a sufficient key. E.g. have get_credentials compute the key internally from (role_arn, subject, extra_claims) (concatenated or hashed) instead of accepting a caller-supplied cache_key, so the key can never drift from what the JWT encodes.
Severity
Minor / latent — defense-in-depth. Not reachable on main today; worth closing before any caller-specific subject/extra_claims is introduced.
Pointers
crates/oidc-provider/src/lib.rs — get_credentials
crates/oidc-provider/src/backend_auth.rs — resolve_aws
crates/oidc-provider/src/cache.rs — CredentialCache::get_or_fetch
Summary
The OIDC backend credential cache keys entries on the role ARN alone, while the credential that gets minted is a function of the full identity
(role_arn, subject, extra_claims). Today those always co-vary, so there is no live vulnerability — but the key does not enforce that invariant, leaving a latent cross-identity credential-reuse hazard for future callers.Surfaced during the review of #61 (shared single-flight credential cache).
Current behavior (not exploitable today)
OidcCredentialProvider::get_credentials(cache_key, exchange, subject, extra_claims)caches oncache_keybut federates usingsubject+extra_claims(crates/oidc-provider/src/lib.rs).AwsBackendAuth::resolve_aws(crates/oidc-provider/src/backend_auth.rs), derives both the key (role_arnfromoidc_role_arn) andsubject(fromoidc_subject, default"s3-proxy") from the sameBucketConfig, and always passesextra_claims = &[].resolve_awsnever reads the inbound caller identity — the proxy federates as itself into the backend, and inbound authorization is enforced upstream. So two callers hitting the same OIDC bucket correctly share one backend STS session.Because
(role_arn, subject, extra_claims)move in lockstep per bucket config, the cache key is sufficient in the current code.The latent risk
Nothing in the type system or the cache enforces that the key captures everything the minted JWT encodes. If a future caller ever passes a caller-derived
subjector a non-emptyextra_claims(e.g. per-callersource_identityfor STS session tagging) while keeping therole_arn-based key, the cache would serve identity A's backend session to identity B — a real cross-identity credential leak.The pre-#61 cache docstring even hinted at the ambiguity ("an opaque key the caller chooses — e.g. a role ARN, or the rendered OIDC subject"), so this predates the merge.
Recommended fix
Make the cache key derive from the full minted identity rather than trusting the caller to pass a sufficient key. E.g. have
get_credentialscompute the key internally from(role_arn, subject, extra_claims)(concatenated or hashed) instead of accepting a caller-suppliedcache_key, so the key can never drift from what the JWT encodes.Severity
Minor / latent — defense-in-depth. Not reachable on
maintoday; worth closing before any caller-specificsubject/extra_claimsis introduced.Pointers
crates/oidc-provider/src/lib.rs—get_credentialscrates/oidc-provider/src/backend_auth.rs—resolve_awscrates/oidc-provider/src/cache.rs—CredentialCache::get_or_fetch