Sep 17, 2026·9 min read·25 visits
The `consumePeerRecord()` function verifies the signature of a wrapping envelope but does not validate that the signer's identity equals the peer ID specified inside the payload, allowing attackers to poison address stores of network nodes.
A logic verification vulnerability in `@libp2p/peer-store` (part of the `js-libp2p` ecosystem) allows unauthenticated remote attackers to bypass identity verification and poison a victim node's peer store database with arbitrary network multiaddresses. This occurs because `consumePeerRecord()` fails to ensure that the signature's identity matches the inner record payload's identity.
The @libp2p/peer-store module acts as the centralized directory of known peer nodes within the JavaScript implementation of the libp2p networking stack (js-libp2p). This module handles critical tasks such as caching, updating, and querying peer records, which include peer identifiers (peerId) and their associated network multiaddresses (multiaddrs). Because peer discovery occurs dynamically in decentralized environments, the peer store must maintain rigorous standards of cryptographic trust to prevent unauthorized modification of routing and peer connectivity state.
Cryptographic trust in address exchanges is achieved using signed PeerRecord objects wrapped within a protective construct called a RecordEnvelope. Nodes dynamically discover their peers' latest active addresses by consuming these envelopes, which are signed with the private keys of the respective origin nodes. This decentralized routing relies on the integrity of these signed envelopes to ensure that a malicious actor cannot broadcast fraudulent multiaddresses on behalf of a victim node, preventing attackers from hijacking the routing entries of others in the network topology.
A logical verification flaw exists within the consumePeerRecord() method located in the packages/peer-store/src/index.ts file of @libp2p/peer-store from version 8.0.0 to 12.0.24. Although the module validates that the wrapping RecordEnvelope is cryptographically signed by a valid key, it fails to ensure that the identity represented within the enclosed payload matches the identity of the signer. Consequently, an attacker can construct and sign an envelope containing an arbitrary victim's peer ID linked to attacker-controlled network addresses, resulting in the target peer store caching the invalid entries under the victim's identifier.
The root cause of this vulnerability lies in the missing alignment check between the outer transport envelope's cryptographic identity and the inner application payload's claimed identity. In libp2p, the RecordEnvelope is decoded using RecordEnvelope.openAndCertify(), which extracts the public key of the signer, verifies the signature over the envelope's data, and returns the derived peerId of the signing node. This cryptographic operation guarantees that the envelope indeed originated from the entity possessing the private key corresponding to the signer's public key.
Once the envelope's signature is verified, consumePeerRecord() validates the optional expectedPeer parameter, if provided. In scenarios such as Gossipsub Peer Exchange (PX), the receiving node expects to process records from the sender, matching the envelope signer's identity against the sender's known peer ID. If this identity check succeeds, the logic proceeds to decode the serialized protobuf payload inside the envelope to instantiate a PeerRecord. However, the engine extracts the peer ID of the record directly from the protobuf-deserialized peerRecord.peerId property instead of utilizing the authenticated signer peerId of the envelope.
Because the implementation did not validate that the authenticated envelope signer (peerId) matched the subject of the application record (peerRecord.peerId), a logical gap was introduced. An attacker is capable of generating a cryptographically valid envelope using their own private key, thus satisfying both the signature check and the expectedPeer validation. However, inside the sealed payload, the attacker embeds a victim's peer ID coupled with the attacker's own physical multiaddresses, bypassing the system's identification mapping checks.
The system's behavior when inserting this data into the address book is to index the record by the inner peerRecord.peerId value. When the database insertion is performed, the legitimate node's cached IP addresses are overwritten by the attacker-controlled multiaddresses. This structural mismatch permits an unauthenticated attacker to manipulate the network's routing directory on any node that processes their gossip or direct-exchange messages.
The file responsible for handling incoming peer records is packages/peer-store/src/index.ts. In vulnerable versions of @libp2p/peer-store, the logic inside the consumePeerRecord function lacks a validation branch that maps the cryptographically certified signer ID to the internal deserialized record owner. The function parses the protobuf payload, and without any identity reconciliation, uses the inner identity to overwrite the localized databases of the peer store.
The official patch, merged in commit 3bf5d395cbca1488eea6e87cd771e4613b661c30, introduces a strict identity assertion before any processing or database write operations. It decodes the payload to extract the peer record, and subsequently calls a peer ID comparison function !peerRecord.peerId.equals(peerId). If this comparison returns true (indicating mismatch), the engine discards the update, writes a warning log, and returns false, preventing the data poisoning.
Below is the specific patch showing how the logical verification step was added to the code:
// File: packages/peer-store/src/index.ts
// ...
const peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
+
+ // CRITICAL FIX: Ensure the signer of the envelope matches the identity described in the record
+ if (!peerRecord.peerId.equals(peerId)) {
+ this.log('signing key did not match peer id in the peer record - signer: %p record: %p', peerId, peerRecord.peerId)
+ return false
+ }
+
let peer: Peer | undefinedThis code modification ensures that an envelope containing a record for a victim peer (e.g., Peer B) signed by an attacker (e.g., Peer A) is rejected. The check relies on the immutable cryptographically verified peerId derived directly from RecordEnvelope.openAndCertify(). Because peerId represents the proven cryptographic origin of the message, matching it against the payload's peerId guarantees that only a peer possessing the corresponding private key can update its own addresses.
To exploit this vulnerability, an attacker leverages standard peer discovery vectors, specifically the Peer Exchange (PX) mechanism of the Gossipsub routing protocol. When utilizing Gossipsub, connected peers share recommendations of active nodes via PX metadata. The receiving node validates that the recommendations come from the peer it is communicating with by passing the peer's known identifier as the expectedPeer parameter to consumePeerRecord().
The attack begins with the construction of a malicious PeerRecord. The attacker sets the peerId field to that of a high-value victim peer on the network. They specify their own IP addresses or proxy servers in the multiaddrs list. Crucially, the attacker sets the sequence number (seqNumber) to a very high value (such as 10n or more) to ensure the target node treats this update as the most recent, authoritative record for the victim peer, overriding any previously cached, legitimate addresses.
The attacker then signs this constructed record with their own private key, producing a serialized, cryptographically valid RecordEnvelope. When the attacker transmits this envelope to a target node through Gossipsub PX, the target validates the envelope signature against the attacker's public key. The target confirms that the signature is valid and matches the expectedPeer parameter (the attacker). Due to the flaw in consumePeerRecord, the target processes the inner payload and writes the attacker's multiaddresses into its local PeerStore under the victim's peer ID.
While this attack successfully diverts outgoing connection attempts targeting the victim to the attacker's infrastructure, it does not achieve complete identity takeover. When the target node attempts to dial the victim's peer ID and routes to the attacker's IP, the low-level transport upgrade protocol (such as noise-handshake or TLS) verifies the remote peer's cryptographic host key. Because the attacker lacks the victim's private key, this cryptographic handshake fails, immediately terminating the connection. The primary operational impact is therefore a highly effective, selective denial of service (DoS) and routing blackhole.
The security impact of CVE-2026-86039 is classified as High, with a CVSS v3.1 base score of 8.2. The primary consequence is the total integrity compromise of the local network topology mapping on affected libp2p nodes. Because nodes rely on the PeerStore to determine where to route connections and messages, poisoning this database allows attackers to systematically isolate nodes or redirect their communications.
An attacker can exploit this flaw to execute targeted routing manipulation and denial of service. By associating the multiaddresses of active peers with dead-ends, non-routable IPs, or loopback interfaces, an attacker can split-brain a libp2p-based network or isolate specific validator nodes in decentralized blockchains. This address book corruption occurs silently, without triggering connection errors until a node actually attempts to dial the poisoned peer.
Additionally, this vulnerability facilitates traffic analysis and resource exhaustion. An attacker can map high-value target peer IDs to their own controlled IP addresses. Although cryptographic transport handshakes will fail when trying to authenticate, the initial connection attempts will consume CPU cycles, socket connections, and bandwidth on both the target and the proxy interfaces. This capability presents a critical vulnerability for systems relying on libp2p for low-latency decentralized communications.
Remediation of CVE-2026-86039 requires upgrading the @libp2p/peer-store dependency to version 12.0.24 or higher. Applications that bundle js-libp2p should ensure that their dependency tree resolved the sub-package correctly. Downstream developers can verify the fix by checking their package-lock.json or yarn.lock files to confirm that all instances of @libp2p/peer-store are resolved to the patched release.
If an immediate upgrade is not feasible, temporary mitigation strategies can be applied by filtering peer recommendations. In networks where Gossipsub Peer Exchange is not strictly necessary, disabling PX or limiting connection parameters can reduce the attack surface. However, because peer records are consumed during normal discovery operations, upgrading to the patched library remains the only comprehensive defense against this logical verification bypass.
Security operations teams can detect exploitation attempts by monitoring node application logs. The patched version of @libp2p/peer-store explicitly prints warning logs when an invalid envelope is detected. Monitoring systems should ingest node stdout/stderr streams and trigger alerts on the specific log string: "signing key did not match peer id in the peer record". Regular occurrences of this log entry from a specific peer indicate active exploitation attempts or an adversarial node injecting fraudulent routing data into the network.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
@libp2p/peer-store libp2p | >= 8.0.0 < 12.0.24 | 12.0.24 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-290 / CWE-345 |
| Attack Vector | Network |
| CVSS Score | 8.2 (High) |
| EPSS Score | N/A |
| Impact | Integrity (High), Availability (Low) |
| Exploit Status | None (No public PoC) |
| KEV Status | Not in CISA KEV |
The application accepts data from a remote sender and authenticates it based on a wrapper signature, but fails to verify that the internal payload matches the identity of the signature generator.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.