CVE-2026-88255 Overview
CVE-2026-88255 is an improper input validation vulnerability [CWE-1289] in ZenHive mpp, an Elixir library implementing the Micropayment Protocol. The flaw resides in MPP.Methods.Tempo, where the pre-broadcast duplicate-submission gate keys its reserve on the caller-supplied raw hex rather than a canonical form of the signed transaction. An unauthenticated remote client can submit the same signed transaction twice using two different recovery-id encodings (v=27 and v=0) to bypass the deduplication check. On nodes that answer with a canonical hash for an already-known transaction, the attacker receives two valid Payment-Receipts for a single on-chain payment.
Critical Impact
A single signed transaction can be replayed through the Tempo gate to obtain duplicate Payment-Receipts, enabling payment double-crediting against affected mpp versions from 0.2.0 before 0.16.2.
Affected Products
- ZenHive mpp versions 0.2.0 through 0.16.1
- Elixir applications embedding MPP.Methods.Tempo for Micropayment Protocol handling
- Deployments relying solely on the Tempo reserve for replay protection (Tempo is explicitly carved out of the plug-level credential replay store in lib/mpp/replay.ex)
Discovery Timeline
- 2026-09-16 - CVE-2026-88255 published to NVD
- 2026-09-16 - Last updated in NVD database
Technical Details for CVE-2026-88255
Vulnerability Analysis
The vulnerability is an Improper Validation of Unsafe Equivalence in Input weakness. MPP.Methods.Tempo reserves a pre-broadcast dedup slot via reserve_hash_atomic/2, keyed through store_key/1 on tx.raw rather than on a canonical transaction identifier. The deserializer stores the caller-supplied hex verbatim and accepts both recovery-id encodings for the signature v field. The same signed transaction can therefore be encoded two different ways, one with v=27 and one with v=0, and produce two distinct reserve keys that both pass the gate and reach the broadcast path.
The post-broadcast mark writes the canonical hash key, but the raw-keyed reserve never reads it, so the canonical mark cannot block the second submission. Outcome depends on the downstream node: a strict node rejects the second broadcast on nonce reuse and fails closed, while a node that returns the canonical hash for an already-known transaction issues a second valid Payment-Receipt for the same on-chain payment.
Root Cause
The root cause is non-canonical keying of a replay-prevention store. The dedup key is derived from a mutable serialization of the transaction rather than from a canonical hash. Because the plug-level replay store in lib/mpp/replay.ex deliberately excludes Tempo, the Tempo reserve is the only gate, and its equivalence check does not treat semantically identical transactions as equal.
Attack Vector
An unauthenticated remote client crafts one signed transaction and submits it twice over the network. The first submission uses v=27; the second re-encodes the identical signature with v=0. Both requests reserve distinct keys, both broadcast, and on permissive downstream nodes both return valid Payment-Receipts for one on-chain payment.
# Patch excerpt: lib/mpp/tempo/store.ex introduces a proper dedup store
defmodule MPP.Tempo.Store do
@moduledoc """
Behaviour for transaction dedup stores used by `MPP.Methods.Tempo`.
Prevents within-challenge replay attacks by tracking which transaction hashes
have already been used. HMAC-bound challenges prevent cross-request replay;
this store prevents a client from resubmitting the same signed transaction
within a single challenge window.
Keys are formatted as `"mpp:charge:<lowercase_hex_value>"` where the value is
the transaction hash (for type="hash") or the full serialized transaction
hex (for type="transaction").
"""
def get(key) do
case :ets.lookup(:payment_dedup, key) do
[{^key, value}] -> {:ok, value}
[] -> :not_found
end
end
end
Source: GitHub commit f8904666
Detection Methods for CVE-2026-88255
Indicators of Compromise
- Two MPP.Methods.Tempo submissions within the same challenge window whose payloads decode to the same canonical transaction hash but differ only in the signature v byte (27/28 vs 0/1).
- Two Payment-Receipts issued for a single on-chain transaction hash observed on the settlement network.
- Broadcast-path log entries showing distinct reserve keys mapping to the same canonical hash on post-broadcast mark.
Detection Strategies
- Correlate Tempo request logs by canonical transaction hash rather than by raw hex, and alert when more than one accepted request maps to the same canonical hash.
- Instrument reserve_hash_atomic/2 callsites to log both the raw-key and the canonical hash so mismatches surface in telemetry.
- Compare issued Payment-Receipt counts against unique on-chain transaction hashes over rolling windows to identify duplicate crediting.
Monitoring Recommendations
- Ingest application and reverse-proxy logs into a central analytics platform and pivot on client IP plus canonical hash to detect replay patterns.
- Track downstream node responses that return canonical hashes for already-known transactions, as these paths yield the exploitable second receipt.
- Monitor the mpp dependency version across services and flag any deployment still running 0.2.0 through 0.16.1.
How to Mitigate CVE-2026-88255
Immediate Actions Required
- Upgrade the mpp dependency to version 0.16.2 or later across all Elixir services exposing Tempo endpoints.
- Implement the MPP.Tempo.Store behaviour with an ETS, Redis, or database backend so within-challenge replay is enforced by canonical keys.
- Audit issued Payment-Receipts against on-chain transaction hashes to identify any duplicate credits issued prior to patching.
Patch Information
The fix is delivered in mpp0.16.2. Remediation commits e12bd4a1 and f8904666 introduce the MPP.Tempo.Store behaviour and correct dedup keying so semantically equivalent transactions share a single key. See the GitHub Security Advisory GHSA-8x7x-5j8g-8hcx and the CNA advisory for the full disclosure.
Workarounds
- Normalize incoming transaction hex at the plug layer by decoding, canonicalizing the signature v byte, and re-encoding before reaching Tempo.
- Reject any Tempo submission whose signature v value is not in the canonical {27, 28} (or {0, 1}) set enforced by policy.
- Extend the plug-level credential replay store in lib/mpp/replay.ex to include Tempo so a canonical-hash gate exists outside of reserve_hash_atomic/2.
# Update mix.exs to require the patched version
# {:mpp, "~> 0.16.2"}
mix deps.update mpp
mix deps.get
mix compile --force
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

