high
CVE
CVE-2026-45783
CWE
CWE-20, CWE-400
Affected Surface
@libp2p/kad-dht versions before 16.2.6, JavaScript libp2p nodes running kad-dht in server mode, Node.js services that expose libp2p DHT endpoints to untrusted peers, Hosts and containers whose DHT datastore shares disk with application workloads
CVE-2026-45783 is a newly published denial-of-service vulnerability in @libp2p/kad-dht that matters anywhere JavaScript libp2p nodes accept untrusted peers. The bug is not a generic “P2P traffic can be noisy” problem. It is a concrete validation failure in the PUT_VALUE handling path that lets attacker-controlled records land in the datastore without ever passing through the package’s normal namespace validators.
The result is simple: any unauthenticated remote peer can keep sending syntactically valid PUT_VALUE messages and force a server-mode DHT node to write attacker-chosen data until the disk backing the datastore is exhausted.
Affected package
Affected npm package:
@libp2p/kad-dht< 16.2.6
Affected deployments:
- any libp2p node running
kad-dhtin server mode - Node.js services that expose DHT functionality to arbitrary peers
- containerized workloads where the DHT datastore shares the same filesystem budget as logs, caches, or application data
Patched release:
@libp2p/kad-dht16.2.6
The bug is JavaScript-specific. The upstream advisory explicitly notes that go-libp2p-kad-dht rejects unrecognized record namespaces at the RPC layer instead of silently accepting them.
Why the record validator fails open
The first defect sits in packages/kad-dht/src/record/validators.ts. Public advisory text shows the vulnerable logic:
export async function verifyRecord(validators, record, options) {
const keyString = uint8ArrayToString(record.key)
const parts = keyString.split('/')
if (parts.length < 3) {
return
}
}
That return is the problem. Legitimate DHT records such as /pk/<multihash> and /ipns/<peerId> contain the slash-delimited namespace information needed to route them to a validator. Crafted keys that decode to fewer than three slash-separated parts never reach a validator at all, but they are also not rejected. They pass through as an implicit success case.
Operationally, that means an attacker does not need to forge a valid ipns or pk record. They only need a key whose UTF-8 representation avoids the normal namespace shape:
[0x01, 0x02, 0x03] -> "\x01\x02\x03" -> split('/') length = 1
Once the key bypasses validation, the record is still written to the node’s datastore.
Why the write can continue indefinitely
The second defect is in the RPC message loop. According to the advisory, the package resets its inactivity timeout after every successfully processed message:
let signal = AbortSignal.timeout(this.incomingMessageTimeout)
const messages = pbStream(stream).pb(Message)
while (true) {
const message = await messages.read({ signal })
await this.handleMessage(connection.remotePeer, message)
signal = AbortSignal.timeout(this.incomingMessageTimeout)
}
That design creates a clean denial-of-service primitive:
- Open a normal libp2p connection.
- Send a crafted
PUT_VALUEmessage before the 10 second inactivity timer expires. - Let the loop reset the timeout.
- Repeat forever.
There is no per-peer byte budget in this path, no message-count ceiling, and no rate limiter that turns a stream of valid-but-malicious PUT_VALUE records into a hard error. The advisory also calls out two defaults that make the math worse:
DEFAULT_MAX_DATA_LENGTH = 4 MBper messageDEFAULT_MAX_INBOUND_STREAMS = 32perkad-dhtinstance
That gives an attacker a straightforward pressure formula:
4 MB/message x unlimited messages x up to 32 concurrent inbound streams
Even if real throughput is lower than the theoretical maximum, the impact is still predictable: the datastore grows until the underlying filesystem or quota boundary fails.
Proof-of-concept shape
The upstream proof of concept uses an in-memory stream pair and a deliberately short key:
const craftedKey = new Uint8Array([0x01, 0x02, 0x03])
const largeValue = new Uint8Array(64 * 1024).fill(0xAB)
const msg = {
type: MessageType.PUT_VALUE,
key: craftedKey,
record: new Libp2pRecord(craftedKey, largeValue, new Date()).serialize()
}
The important detail is not the exact payload size used in the test. It is the fact that the datastore remains empty before the message and contains a new /record/ entry after the message, even though the key never belonged to a valid DHT record namespace. Once that state transition is possible, disk exhaustion becomes a bandwidth-and-time problem instead of an exploit-development problem.
What breaks in real deployments
If you use @libp2p/kad-dht as a sidecar capability inside a larger application, this issue is bigger than a single DHT node becoming unavailable.
Possible downstream effects include:
- persistent volume exhaustion in Kubernetes or Nomad
- container eviction when the writable layer or attached volume fills
- log and telemetry loss when the same filesystem backs both the datastore and observability agents
- cascading outages when application state, temp files, or cache directories share the same disk budget
Because the vulnerability does not require credentials or protocol deviation, it is especially relevant for public or semi-public peer networks where “unknown peers” is the default trust model.
Detection and scoping
Start by inventorying the package version:
npm ls @libp2p/kad-dht
pnpm why @libp2p/kad-dht
yarn why @libp2p/kad-dht
Then determine whether the vulnerable nodes are actually operating in DHT server mode and whether their datastore is exposed to untrusted peers.
On running systems, investigate for:
- rapid datastore growth under the
kad-dhtstorage path - repeated inbound
PUT_VALUEtraffic from the same or rotating peers - storage alerts on nodes that also host application workloads
- container restarts or pod evictions caused by disk pressure
If you log libp2p message types or peer IDs, review the exposure window for sustained PUT_VALUE activity that does not correspond to normal application behavior.
Remediation
Upgrade immediately:
npm install @libp2p/kad-dht@^16.2.6
Then rebuild and redeploy every artifact that resolves the vulnerable package. Package-manager upgrades alone are not enough if the old version is already baked into long-lived images or bundled application releases.
If you cannot patch immediately, reduce the blast radius:
- Restrict DHT access to trusted peers or private network boundaries.
- Put the datastore on a quota-limited volume that cannot starve the rest of the application.
- Add storage monitoring specifically for the DHT data path.
- Rate-limit or firewall peer traffic wherever your deployment model allows it.
Those steps can slow exploitation, but they do not fix the validator bypass. The durable fix is 16.2.6.
Why this issue matters
@libp2p/kad-dht is not a developer-tooling package and this is not a registry compromise, but it still fits the pattern AppSec teams need to watch closely in package ecosystems: a security boundary existed at the library API level, the implementation failed open, and the failure mode let untrusted input become durable state on disk.
For any team using JavaScript libp2p in production, CVE-2026-45783 should be treated as a real availability issue, not just an academic protocol bug. Once the node is reachable by arbitrary peers, writing unbounded attacker data becomes part of the threat model.