Skip to content

Commit 11f2574

Browse files
committed
Add post-decode issuer mismatch check and cross-issuer test
After jwt.decode verifies the signature against the detected issuer's JWKS, explicitly assert that the verified iss claim matches the issuer we routed to. This makes the invariant enforced rather than emergent, closing a theoretical hole if two issuers ever shared a JWKS endpoint.
1 parent e21072e commit 11f2574

2 files changed

Lines changed: 26 additions & 6 deletions

File tree

aws/lambda/cross_repo_ci_relay/tests/test_jwt_helper.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ def test_github_token_missing_repository_claim_raises_401(self):
112112
verify_oidc_token("token.no.repo")
113113
self.assertEqual(ctx.exception.status_code, 401)
114114

115+
def test_cross_issuer_token_rejected(self):
116+
"""A GitHub-signed token asserting iss=buildkite must be rejected."""
117+
self.mock_detect.return_value = GITHUB_ISSUER
118+
self.mock_decode.return_value = {
119+
"iss": BUILDKITE_ISSUER,
120+
"repository": "org/repo",
121+
}
122+
with self.assertRaises(HTTPException) as ctx:
123+
verify_oidc_token("cross.issuer.token")
124+
self.assertEqual(ctx.exception.status_code, 401)
125+
self.assertIn("Issuer mismatch", ctx.exception.detail)
126+
115127

116128
class TestVerifyBuildkiteOIDC(unittest.TestCase):
117129
"""Tests for Buildkite OIDC tokens."""

aws/lambda/cross_repo_ci_relay/utils/jwt_helper.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,21 +211,29 @@ def verify_oidc_token(token: str) -> dict:
211211
if token.lower().startswith("bearer "):
212212
token = token[7:].strip()
213213

214-
issuer = _detect_issuer(token)
215-
if issuer not in _jwks_clients:
216-
raise HTTPException(401, f"Unsupported OIDC issuer: {issuer}")
214+
detected_issuer = _detect_issuer(token)
215+
if detected_issuer not in _jwks_clients:
216+
raise HTTPException(401, f"Unsupported OIDC issuer: {detected_issuer}")
217217

218-
client = _jwks_clients[issuer]
218+
client = _jwks_clients[detected_issuer]
219219
signing_key = client.get_signing_key_from_jwt(token)
220220
claims = jwt.decode(
221221
token,
222222
signing_key.key,
223223
algorithms=["RS256"],
224-
issuer=issuer,
224+
issuer=detected_issuer,
225225
audience=AUDIENCE,
226226
)
227227

228-
extractor = _REPO_EXTRACTORS[issuer]
228+
verified_issuer = claims.get("iss", "")
229+
if verified_issuer != detected_issuer:
230+
raise HTTPException(
231+
401,
232+
f"Issuer mismatch: token claims '{verified_issuer}' "
233+
f"but was routed as '{detected_issuer}'",
234+
)
235+
236+
extractor = _REPO_EXTRACTORS[detected_issuer]
229237
repo = extractor(claims)
230238
claims["repository"] = repo
231239

0 commit comments

Comments
 (0)