Repository navigation
Conversation
Challenges created from the same realm, method, intent, request, metadata and explicit expiry had identical IDs, so a credential for one issuance also satisfied any other indistinguishable issuance. Verify.createChallenge now adds 32 random bytes to the HMAC-bound opaque metadata under the reserved `_mppx_nonce` key, as mppx does. The route check strips that key before comparing the echoed opaque with the configured metadata; every other field stays pinned, and challenges issued without a nonce still verify.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes tempoxyz/mpp-tools#260 (AGR-2026-112).
Verify.createChallengebuilds the challenge from the realm, method, intent, request, caller metadata and expiry only. With an explicitexpires, two issuances for the same route get the same HMAC ID, so a credential paid for one issuance also satisfies any other indistinguishable one. mppx avoids this by adding per-issuance randomness to the HMAC-bound opaque metadata (ChallengeMeta.withNonceinsrc/server/Mppx.ts) and ignoring that key when it compares route metadata (opaqueValuesMatch).This does the same:
ChallengeMeta.withNoncecopies the caller metadata and adds 32 random bytes fromSecureRandomunder the reserved_mppx_noncekey.Verify.createChallengeuses it, soMppHandler.charge,MppHandler.challengeandComposedHandlerall issue fresh IDs.Verify.opaqueMatchesstrips that key from both sides before comparing the echoed opaque with the configured metadata. Every other metadata field is still pinned, and the nonce itself is still covered by the challenge HMAC.Tests in
IntegrationTest:equivalentConcurrentIssuancesGetDistinctChallengeIds: ten concurrent issuances with the same realm, method, intent, request, metadata and explicit expiry get ten distinct IDs. Before the fix:Expected size: 10 but was: 1.issuanceNonceKeepsApplicationMetadata: the issued opaque keepsrouteand adds the nonce. Before the fix the nonce key is missing.challengesIssuedWithoutANonceStillVerify: a credential for a challenge created without a nonce is still accepted.specOpaqueCredentialPassesRouteBindingAndSplitLifecycle,differentOpaqueMetadataForcesAChallengeBeforeLifecycleHooks) pass unchanged../gradlew testpasses on JDK 11, 17 and 21,compileExampleJavabuilds, and./gradlew integrationTestagainst the docker-compose Tempo node succeeds.