Skip to content

Auth server can mint an act claim that violates RFC 8693 §4.1 #6093

Description

@jhrozek

Found while reviewing #6046.

Problem

HandleTokenEndpointRequest copies the subject token's prior act claim into the newly minted one without checking it is a JSON object:

// pkg/authserver/server/tokenexchange/handler.go:156-162
if priorAct, ok := validatedClaims.Extra["act"]; ok && priorAct != nil {
    if actChainDepth(priorAct) >= maxDelegationDepth {
        return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint(
            "The subject token's delegation chain is too deep."))
    }
    act["act"] = priorAct   // <-- no type check
}

The depth gate does not catch it either, because actChainDepth returns 0 for anything that is not a map:

// pkg/authserver/server/tokenexchange/handler.go:475-489
func actChainDepth(act any) int {
    depth := 0
    for {
        m, ok := act.(map[string]any)
        if !ok {
            return depth      // <-- non-object => depth 0, gate passes
        }
        ...

RFC 8693 §4.1 states: "The act claim value is a JSON object, and members in the JSON object are claims that identify the actor." So a subject token carrying e.g. "act": "some-agent" or an array produces a minted token with act: {"sub": "<actor>", "act": "some-agent"} — non-conformant, emitted by us.

Trigger

Requires a subject token whose own act claim is not an object. Our own AS never mints one, so this needs a token from another issuer — plausible as multi-issuer support lands (see the TODO referencing #5989 in multi_issuer_validator.go). The subject token is signature-validated first, so this is an issuer-conformance problem, not an unauthenticated attack.

Impact

Low severity, but it means ToolHive both emits and consumes a spec violation. As of #6046 the consuming side now flags it — auth.ParseDelegationChain sets malformed: true on the audit event — so the symptom is observable in audit logs while the cause sits on the issuance side.

Suggested fix

Either reject the exchange with invalid_grant when the prior act is not a JSON object, or drop the non-conformant prior act (and log at WARN) so the minted chain stays conformant. Rejecting is the more consistent choice, since the adjacent depth violation already returns invalid_grant.

Worth fixing actChainDepth to distinguish "absent" from "malformed" at the same time, rather than returning 0 for both.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions