Bug description
pkg/auth/awssts maps JWT claims to IAM roles. A claim-based mapping is documented and tested as an exact claim-value match (config.go:74-77, role_mapper_test.go list cases), but each mapping is evaluated with the fixed CEL expression claim_value in claims[role_claim_key] (role_mapper.go:23). claims is declared map<string, dyn> (role_mapper.go:40), so the right operand's runtime type decides in's semantics, and no normalization exists in the path.
In the pinned cel-go v0.30.0, in has only list/map overloads — no string overload. When an IdP emits the role claim as a string (e.g. groups: "admins", or any string-valued claim used as RoleClaim like azp/aud), evaluation raises a runtime error. SelectRole swallows the error with a Debug-only log and treats the mapping as "no match" (role_mapper.go:184-189), then returns FallbackRoleArn (role_mapper.go:196-202).
Steps to reproduce
Verified on db86c06b (Go 1.26.5). groups: "admins" (string) with Claim: "admins" should select AdminRole:
cfg := &awssts.Config{
Region: "us-east-1",
RoleClaim: "groups",
FallbackRoleArn: "arn:aws:iam::123456789012:role/FallbackRole",
RoleMappings: []awssts.RoleMapping{
{Claim: "admins", RoleArn: "arn:aws:iam::123456789012:role/AdminRole", Priority: intPtr(1)},
},
}
rm, _ := awssts.NewRoleMapper(cfg)
role, _ := rm.SelectRole(map[string]any{"sub": "u1", "groups": "admins"}) // returns FallbackRole, want AdminRole
go test -v ./pkg/auth/awssts -run TestRoleMapper_SelectRole -count=1
Observed: groups="admins" (string) → FallbackRole, with the underlying CEL evaluation failing (no such overload); control groups=["admins"] (list) → AdminRole.
Expected behavior
A string-typed role claim equal to the configured Claim (e.g. groups: "admins" for Claim: "admins") must select the mapped role; superadmins / admins-readonly must not. Evaluation failures for unsupported claim shapes must not be silently treated as "no match".
Actual behavior
"admins" in "admins" raises a CEL runtime error; the mapping is skipped with a Debug-only log and the user is granted FallbackRoleArn — silently.
Environment
- Go 1.26.5; toolhive
db86c06b (v0.42.1-23-gdb86c06b)
github.com/google/cel-go v0.30.0 (go.mod:29) — in registers only list/map overloads
- Reachable on validated JWT claims via the vMCP
aws_sts strategy (pkg/vmcp/auth/strategies/aws_sts.go:129,170) and the standalone middleware (pkg/auth/awssts/middleware.go:172)
Additional context
- Impact: a user whose role claim is a single string never gets their mapped role. If
FallbackRoleArn is at least as privileged as a mapped role, this is a silent authorization bypass; otherwise it is a silent wrong-role grant. No operator-visible signal is emitted.
- Suggested fix: normalize a string-typed role claim to a single-element list before evaluation (restoring exact membership), and fail closed (Warn/Error log + error return) on evaluation failure instead of falling back. Verified against
go test ./pkg/auth/awssts/... ./pkg/vmcp/auth/strategies/... — no existing test regresses (all list-claim tests are unaffected).
Bug description
pkg/auth/awsstsmaps JWT claims to IAM roles. A claim-based mapping is documented and tested as an exact claim-value match (config.go:74-77,role_mapper_test.golist cases), but each mapping is evaluated with the fixed CEL expressionclaim_value in claims[role_claim_key](role_mapper.go:23).claimsis declaredmap<string, dyn>(role_mapper.go:40), so the right operand's runtime type decidesin's semantics, and no normalization exists in the path.In the pinned
cel-go v0.30.0,inhas only list/map overloads — no string overload. When an IdP emits the role claim as a string (e.g.groups: "admins", or any string-valued claim used asRoleClaimlikeazp/aud), evaluation raises a runtime error.SelectRoleswallows the error with a Debug-only log and treats the mapping as "no match" (role_mapper.go:184-189), then returnsFallbackRoleArn(role_mapper.go:196-202).Steps to reproduce
Verified on
db86c06b(Go 1.26.5).groups: "admins"(string) withClaim: "admins"should selectAdminRole:go test -v ./pkg/auth/awssts -run TestRoleMapper_SelectRole -count=1Observed:
groups="admins"(string) →FallbackRole, with the underlying CEL evaluation failing (no such overload); controlgroups=["admins"](list) →AdminRole.Expected behavior
A string-typed role claim equal to the configured
Claim(e.g.groups: "admins"forClaim: "admins") must select the mapped role;superadmins/admins-readonlymust not. Evaluation failures for unsupported claim shapes must not be silently treated as "no match".Actual behavior
"admins" in "admins"raises a CEL runtime error; the mapping is skipped with a Debug-only log and the user is grantedFallbackRoleArn— silently.Environment
db86c06b(v0.42.1-23-gdb86c06b)github.com/google/cel-go v0.30.0(go.mod:29) —inregisters only list/map overloadsaws_stsstrategy (pkg/vmcp/auth/strategies/aws_sts.go:129,170) and the standalone middleware (pkg/auth/awssts/middleware.go:172)Additional context
FallbackRoleArnis at least as privileged as a mapped role, this is a silent authorization bypass; otherwise it is a silent wrong-role grant. No operator-visible signal is emitted.go test ./pkg/auth/awssts/... ./pkg/vmcp/auth/strategies/...— no existing test regresses (all list-claim tests are unaffected).