Skip to content

fix(oidc): key credential cache on (cache_key, subject, extra_claims)#104

Draft
alukach wants to merge 1 commit into
mainfrom
fix/l1-cred-cache-key-subject
Draft

fix(oidc): key credential cache on (cache_key, subject, extra_claims)#104
alukach wants to merge 1 commit into
mainfrom
fix/l1-cred-cache-key-subject

Conversation

@alukach

@alukach alukach commented Jul 2, 2026

Copy link
Copy Markdown
Member

Problem

OidcCredentialProvider::get_credentials caches minted backend credentials keyed on cache_key alone (the role ARN). But the backend's authorization gate — an AWS role trust policy, or the Azure/GCP equivalent — conditions on the minted assertion (its subject, plus any extra_claims) and is evaluated at mint time, inside the exchange.

A cache hit skips the mint, so it skips that gate. Two subjects that share a role therefore share a cached credential: the second subject rides on credentials the trust policy might have denied it. The subject here is the per-connection identity (scv1:conn:{id}), which is exactly what an account owner conditions their trust policy on to restrict which connections/account-products may assume a shared role.

Fix

get_credentials already receives subject and extra_claims, so fold them into the effective cache key: entries are now keyed per (cache_key, subject, extra_claims).

  • Done inside get_credentials (not at the call site) so no caller can forget to scope by identity — it's closed for every backend.
  • Length-prefixed framing (<len>:<value>) keeps the key unambiguous by construction: no crafted subject/ARN can forge another tuple's key. The key is a security boundary, so it can't rely on a delimiter that a value might contain.
  • Granularity becomes per-(backend, identity), which is the correct scope — credentials already are per-subject — so hit rate is unaffected in practice.

Context

This is the L1 (in-isolate) half of a two-tier fix. The L2 (cross-isolate, Cloudflare Cache API) tier in data.source.coop had the same role-ARN-only keying and is fixed in that repo's PR #175 by keying on (RoleArn, RoleSessionName). This PR fixes the root cause at the layer that owns the cache and has the raw subject (lossless, vs. the sanitized/truncated RoleSessionName the L2 form exposes).

Tests

  • same_backend_different_subject_makes_separate_calls — same role, two subjects → two mints (the security property).
  • credential_cache_key_is_unambiguous — length-prefix framing can't collide across tuples; subject and claims are part of the key; identical inputs still hit.
  • Existing cache/hit/miss tests unchanged and green.

🤖 Generated with Claude Code

The credential cache keyed on `cache_key` alone (the role ARN). But the
backend's authorization gate — an AWS role trust policy, or the Azure/GCP
equivalent — conditions on the *minted assertion* (its `subject` and any
`extra_claims`) and is evaluated at mint time, inside the exchange. A cache hit
skips the mint, so it skips that gate: two subjects sharing a role would share a
cached credential, letting the second subject ride on credentials the trust
policy might have denied it.

`get_credentials` already receives `subject` and `extra_claims`, so fold them
into the effective key. Doing it here (not at the call site) closes the footgun
for every caller — none can forget to scope by identity. Length-prefixed framing
keeps the key unambiguous so no crafted subject/ARN can forge another tuple's
key.

Cache granularity becomes per-(backend, identity), which is the correct scope —
credentials already are per-subject — so hit rate is unaffected in practice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alukach alukach deployed to preview July 2, 2026 18:12 — with GitHub Actions Active
@github-actions github-actions Bot added the fix label Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🚀 Latest commit deployed to https://multistore-proxy-pr-104.development-seed.workers.dev

  • Date: 2026-07-02T18:12:24Z
  • Commit: 5f0cd25

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant