When challengeRequired is set on ARCPRuntime, the handshake at Sources/ARCP/Runtime/ARCPRuntime.swift:149 sends a fresh session.challenge nonce and then calls auth.validate(auth: authPayload.auth, challenge: nonce). JWTAuthValidator.validate(auth:challenge:) at Sources/ARCP/Auth/JWTAuth.swift:24 receives that nonce as the challenge nonce: String? parameter but never reads it: it only verifies the JWT signature, exp, and aud. The minimal ARCPClaims struct at Sources/ARCP/Auth/JWTAuth.swift:48 has no nonce field, and verify(using:) only calls exp.verifyNotExpired(). The result is that the challenge mechanism is ceremonial when the configured auth scheme is signed_jwt — any valid, unexpired JWT for this runtime is accepted regardless of which nonce was issued, so a captured token can be replayed against any future challenge until it expires. That defeats the protection that challengeRequired is meant to provide and silently downgrades the security posture documented for the challenge flow.
Fix prompt: Have JWTAuthValidator.validate(auth:challenge:) enforce challenge binding when a non-nil challenge is supplied. Extend ARCPClaims with a nonce (and ideally jti) claim, require the JWT to carry the same nonce that the runtime issued, and reject the token with ARCPError.unauthenticated when it is missing or does not match. Document the new claim requirement in the JWT auth guide and in the inline doc comment on JWTAuthValidator, and add unit tests covering a happy path with a matching nonce, a rejected token with a missing nonce, a rejected token with the wrong nonce, and a token replayed against a second challenge.
When
challengeRequiredis set onARCPRuntime, the handshake atSources/ARCP/Runtime/ARCPRuntime.swift:149sends a freshsession.challengenonce and then callsauth.validate(auth: authPayload.auth, challenge: nonce).JWTAuthValidator.validate(auth:challenge:)atSources/ARCP/Auth/JWTAuth.swift:24receives that nonce as thechallenge nonce: String?parameter but never reads it: it only verifies the JWT signature,exp, andaud. The minimalARCPClaimsstruct atSources/ARCP/Auth/JWTAuth.swift:48has nononcefield, andverify(using:)only callsexp.verifyNotExpired(). The result is that the challenge mechanism is ceremonial when the configured auth scheme issigned_jwt— any valid, unexpired JWT for this runtime is accepted regardless of which nonce was issued, so a captured token can be replayed against any future challenge until it expires. That defeats the protection thatchallengeRequiredis meant to provide and silently downgrades the security posture documented for the challenge flow.Fix prompt: Have
JWTAuthValidator.validate(auth:challenge:)enforce challenge binding when a non-nilchallengeis supplied. ExtendARCPClaimswith anonce(and ideallyjti) claim, require the JWT to carry the same nonce that the runtime issued, and reject the token withARCPError.unauthenticatedwhen it is missing or does not match. Document the new claim requirement in the JWT auth guide and in the inline doc comment onJWTAuthValidator, and add unit tests covering a happy path with a matching nonce, a rejected token with a missing nonce, a rejected token with the wrong nonce, and a token replayed against a second challenge.