Listen to this Post
The vulnerability exists in the `@libp2p/gossipsub` module of the `js-libp2p` networking stack. The core issue is that the `gossipsub` protocol implementation processes `IHAVE` and `IWANT` control messages by synchronously iterating over every received message ID before performing any other action.
Crucially, there is no cap on the number of message IDs a single frame can contain. The default length-prefixed (LP) frame limit is 4 MB, which is sufficient to hold approximately 180,000 message IDs. Iterating over this many IDs synchronously blocks the Node.js event loop for roughly 135-200ms per call.
The `IHAVE` and `IWANT` variants have different severities due to existing rate-limiting mechanisms. For IHAVE, there is a per-peer, per-heartbeat counter (iasked, peerhave) that limits a single peer to one full iteration per heartbeat. While this prevents a single peer from causing a sustained denial of service, an attacker can circumvent this by using approximately 10 Sybil (synthetic) peers. Each Sybil peer sends one oversized `IHAVE` per heartbeat, resulting in a total event-loop block of around 1500ms against a 1000ms heartbeat interval.
The `IWANT` variant is more severe as it has no such rate-limiting mechanism. A single malicious peer can continuously stream 4 MB frames containing `IWANT` messages for non-existent messages. At 1 Gbps, a 4 MB frame arrives every ~32ms and takes ~135ms to process, resulting in approximately 81% event-loop utilization from a single connection, effectively causing a permanent denial of service.
The default configuration (defaultDecodeRpcLimits) in `message/decodeRpc.ts` sets `maxIhaveMessageIDs` and `maxIwantMessageIDs` to Infinity, which allows this attack. A `TODO` comment in the code even acknowledges the missing check.
DailyCVE Form
Platform: `js-libp2p`
Version: `< 16.0.0`
Vulnerability: CPU DoS
Severity: High (7.5)
Date: 2026-07-08
Prediction: Patch available (16.0.0)
What Undercode Say
This section provides analytics and technical details extracted from the blog .
Default Decode Limits (Vulnerable Configuration)
The following code from `message/decodeRpc.ts` shows the vulnerable default limits, which lack caps on message ID counts:
export const defaultDecodeRpcLimits: DecodeRPCLimits = {
maxSubscriptions: Infinity,
maxMessages: Infinity,
maxIhaveMessageIDs: Infinity,
maxIwantMessageIDs: Infinity,
maxIdontwantMessageIDs: Infinity,
maxControlMessages: Infinity,
maxPeerInfos: Infinity
}
IHAVE Processing (Synchronous Iteration)
The `handleIHave` function in `gossipsub.ts` iterates all IDs before truncation, causing the event loop to block:
messageIDs.forEach((msgId) => {
const msgIdStr = this.msgIdToStrFn(msgId)
if (!this.seenCache.has(msgIdStr)) {
iwant.set(msgIdStr, msgId)
}
})
// truncation to GossipsubMaxIHaveLength (5000) only happens after the loop finishes
IWANT Processing (No Rate Limit)
The `handleIWant` function lacks any peer-specific rate limiting, making it exploitable by a single peer:
messageIDs?.forEach((msgId) => {
const msgIdStr = this.msgIdToStrFn(msgId)
const entry = this.mcache.getWithIWantCount(msgIdStr, id)
// ...
})
Exploit
Attack Vector 1: IHAVE (Requires ~10 Sybil Peers)
An attacker connects 10 Sybil peers to the victim, each subscribing to a topic the victim is on. New peers start with a score of 0, which is above the default `gossipThreshold` of -10, so their `IHAVE` messages are processed immediately. Each peer sends one 4MB RPC per heartbeat containing a single `ControlIHave` entry with ~180,000 random message IDs. The victim processes all IDs for each peer, resulting in a total event-loop block of approximately 1500ms per 1000ms heartbeat.
Attack Vector 2: IWANT (Single Peer, No Sybil Required)
A single attacker connects to the victim and streams 4MB `IWANT` RPCs continuously, each containing ~180,000 random message IDs that do not exist in the victim’s cache. No rate limit applies, and sending `IWANT` for non-existent messages does not affect the attacker’s score, so there is no automatic disconnect. At datacenter bandwidth, the event loop stays above 80% utilisation indefinitely.
Protection
The primary protection is to upgrade to `js-libp2p` version 16.0.0 or later, where the issue is fixed.
For users who cannot upgrade immediately, a workaround is to explicitly configure finite values for opts.decodeRpcLimits. The recommended fix is to set finite defaults in decodeRpc.ts, matching the `GossipsubMaxIHaveLength` (5000) to bound the iteration cost:
export const defaultDecodeRpcLimits: DecodeRPCLimits = {
maxSubscriptions: 128,
maxMessages: 256,
maxIhaveMessageIDs: 5_000,
maxIwantMessageIDs: 5_000,
maxIdontwantMessageIDs: 5_000,
maxControlMessages: 128,
maxPeerInfos: 16
}
Impact
Any node running `@libp2p/gossipsub` with default options that accepts inbound connections is affected. This includes:
Ethereum consensus clients using `js-libp2p` (e.g., Lodestar)
IPFS nodes with pubsub enabled
Any application calling `createLibp2p({ services: { pubsub: gossipsub() } })`
With 10 Sybil peers, the `IHAVE` variant blocks the event loop for 1.5x the heartbeat interval continuously. The node cannot forward messages, run its heartbeat, or respond to legitimate peers. The `IWANT` variant achieves the same result from a single connection at datacenter bandwidth.
Nodes that explicitly configure `opts.decodeRpcLimits` with finite values are not affected.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

