From 39ec298bb9f893e0872e84f7aa61a2624cd07c78 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 9 Mar 2026 12:10:08 -0700 Subject: [PATCH 1/2] fix(cli): prefer user/api audience for mixed-aud JWTs Signed-off-by: Sergio --- app/cli/internal/token/token.go | 34 +++++++++++++++++++++++----- app/cli/internal/token/token_test.go | 8 +++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/cli/internal/token/token.go b/app/cli/internal/token/token.go index baccfaec1..44c7ee7d2 100644 --- a/app/cli/internal/token/token.go +++ b/app/cli/internal/token/token.go @@ -38,6 +38,20 @@ const ( federatedTokenAudience = "chainloop" ) +func chooseAudience(audiences []string) string { + // Priority matters for mixed-audience tokens: user/api token identity + // should win over generic federated audience. + for _, candidate := range []string{apiTokenAudience, userAudience, federatedTokenAudience} { + for _, aud := range audiences { + if aud == candidate { + return candidate + } + } + } + + return "" +} + // Parse the token and return the type of token. At the moment in Chainloop we have 3 types of tokens: // 1. User account token // 2. API token @@ -65,20 +79,28 @@ func Parse(token string) (*ParsedToken, error) { return nil, nil } - // Supports both string and array formats per JWT RFC 7519 - // Takes first array element when multiple audiences exist - var audience string + // Supports both string and array formats per JWT RFC 7519. + // For multi-audience tokens, prefer the most specific Chainloop auth audience + // instead of taking the first element (order can vary by issuer/runtime). + var audiences []string switch aud := claims["aud"].(type) { case string: - audience = aud + audiences = []string{aud} case []interface{}: - if len(aud) > 0 { - audience, _ = aud[0].(string) + for _, a := range aud { + if s, ok := a.(string); ok && s != "" { + audiences = append(audiences, s) + } } default: return nil, nil } + if len(audiences) == 0 { + return nil, nil + } + + audience := chooseAudience(audiences) if audience == "" { return nil, nil } diff --git a/app/cli/internal/token/token_test.go b/app/cli/internal/token/token_test.go index 218cbe526..049b1be93 100644 --- a/app/cli/internal/token/token_test.go +++ b/app/cli/internal/token/token_test.go @@ -53,6 +53,14 @@ func TestParse(t *testing.T) { TokenType: v1.Attestation_Auth_AUTH_TYPE_FEDERATED, }, }, + { + name: "user token with mixed audiences prefers user audience", + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmMwYjIxOTktY2E4NS00MmFiLWE4NTctMDQyZTljMTA5ZDQzIiwiaXNzIjoiY3AuY2hhaW5sb29wIiwiYXVkIjpbImNoYWlubG9vcCIsInVzZXItYXV0aC5jaGFpbmxvb3AiXSwiZXhwIjoxNzE1OTM1MjUwfQ.signature", + want: &ParsedToken{ + ID: "bc0b2199-ca85-42ab-a857-042e9c109d43", + TokenType: v1.Attestation_Auth_AUTH_TYPE_USER, + }, + }, { name: "federated github token", token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiY2hhaW5sb29wIiwicmVwb3NpdG9yeSI6Im1hdGlhc2luc2F1cnJhbGRlL3Byb2plY3QiLCJzdWIiOiJyZXBvOm1hdGlhc2luc2F1cnJhbGRlL3Byb2plY3Q6cmVmOnJlZnMvaGVhZHMvbWFpbiJ9.signature", From 2eef29279fb37d26ec644b75a9a9f0103486dc7e Mon Sep 17 00:00:00 2001 From: sergiochan Date: Mon, 23 Mar 2026 16:11:07 -0700 Subject: [PATCH 2/2] test(cli): add unknown mixed-audience token regression case Signed-off-by: sergiochan --- app/cli/internal/token/token_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/cli/internal/token/token_test.go b/app/cli/internal/token/token_test.go index 049b1be93..fb28444f9 100644 --- a/app/cli/internal/token/token_test.go +++ b/app/cli/internal/token/token_test.go @@ -61,6 +61,10 @@ func TestParse(t *testing.T) { TokenType: v1.Attestation_Auth_AUTH_TYPE_USER, }, }, + { + name: "unknown audiences return nil", + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjcC5jaGFpbmxvb3AiLCJhdWQiOlsidW5rbm93bjEiLCJ1bmtub3duMiJdLCJleHAiOjE3MTU5MzUyNTB9.signature", + }, { name: "federated github token", token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiY2hhaW5sb29wIiwicmVwb3NpdG9yeSI6Im1hdGlhc2luc2F1cnJhbGRlL3Byb2plY3QiLCJzdWIiOiJyZXBvOm1hdGlhc2luc2F1cnJhbGRlL3Byb2plY3Q6cmVmOnJlZnMvaGVhZHMvbWFpbiJ9.signature",