How Unvalidated PUT_VALUE Records Happen in Node.js @libp2p/kad-dht and How to Fix It
Summary
CVE-2026-45783 is a high-severity vulnerability in @libp2p/kad-dht versions prior to 16.2.6 where DHT server nodes accept and store PUT_VALUE records without validating their content or enforcing storage limits, allowing any peer on the network to exhaust disk space. The fix upgrades the package from 16.1.3 to 16.2.6, which introduces proper record validation before writes are committed to disk. Developers building decentralized applications on libp2p are directly affected if they run DHT server nodes exposed to untrusted peers.
Introduction
The package-lock.json file in this Node.js project pins @libp2p/kad-dht at version 16.1.3 — a version that contains a critical flaw in how its DHT server nodes handle incoming PUT_VALUE RPC messages. When a remote peer sends a PUT_VALUE record to a DHT server node, the node is supposed to validate the record (checking its schema, size, and authenticity) before persisting it to its local datastore. In vulnerable versions, this validation step is either absent or bypassable, meaning the node will dutifully write whatever arbitrary payload a peer sends — over and over — until the disk is full.
This matters because libp2p-based applications frequently run as long-lived server nodes on fixed infrastructure. A single malicious peer (or a botnet of peers) can target these nodes and render them completely inoperable with nothing more than a flood of crafted PUT_VALUE messages. No authentication is required to participate in a Kademlia DHT, making this attack accessible to anyone who can connect to the network.
The Vulnerability Explained
How Kademlia DHT PUT_VALUE Works
In a Kademlia DHT, the PUT_VALUE RPC allows any node to ask a server node to store an arbitrary key-value record. The design intent is that server nodes validate incoming records — checking that the value conforms to an expected schema (e.g., a signed provider record or IPNS record), that its size is within bounds, and that the node is actually responsible for storing it based on its position in the keyspace.
What Went Wrong in 16.1.3
In @libp2p/kad-dht 16.1.3, the record validation logic applied to incoming PUT_VALUE messages on DHT server nodes was insufficient. Specifically:
- No size cap on record values: An attacker could send
PUT_VALUEmessages containing arbitrarily large byte payloads. Each message causes a disk write proportional to the payload size. - No rate or quota enforcement: The server node places no upper bound on how many distinct keys it will store from a single peer or in total, meaning storage grows without limit as requests arrive.
- Validation bypass: Records that should fail schema validation (e.g., malformed or unsigned records) could still be written to the underlying datastore.
A simplified representation of the vulnerable flow looks like this:
// Vulnerable behavior in @libp2p/kad-dht 16.1.3 (pseudocode)
async function handlePutValue(peerId, key, value) {
// ❌ No size check on `value`
// ❌ No schema/signature validation before storage
// ❌ No per-peer or global storage quota
await this.datastore.put(key, value); // Writes directly to disk
}
The value buffer received from the remote peer flows directly into datastore.put() without any of the guards that should precede a disk write.
Attack Scenario
An attacker running a libp2p node connects to a target DHT server node. They craft a loop that sends thousands of PUT_VALUE messages, each carrying a key derived from a random hash and a value payload of, say, 64 KB of arbitrary bytes. Because the target node does not validate record size or enforce a storage quota, it writes each payload to disk. At 64 KB per record and thousands of records per minute, the target node's disk fills within hours. Once the disk is exhausted, the host OS can no longer write log files, temporary files, or any other data — the entire node becomes unavailable, not just the DHT service.
Because Kademlia DHT participation requires no authentication, the attacker does not need any credentials, API keys, or special privileges. Any node that can dial the target's multiaddr can execute this attack.
Real-World Impact
- Denial of Service: The most immediate impact is that the target node's disk fills up, crashing the libp2p process and potentially the entire host.
- Cascading failures: Applications built on top of libp2p (e.g., IPFS nodes, decentralized identity services, blockchain light clients) lose their DHT routing capability, breaking peer discovery and content routing.
- Downstream consumers affected: Because
@libp2p/kad-dhtis a library, every Node.js application that depends on it and runs a DHT server node inherits this vulnerability.
The Fix
What Changed in the Upgrade
The fix is a dependency version bump from @libp2p/kad-dht 16.1.3 to 16.2.6. This is reflected in package-lock.json (and the corresponding package.json entry):
- "@libp2p/kad-dht": "^16.1.3",
+ "@libp2p/kad-dht": "^16.2.6",
Version 16.2.6 of @libp2p/kad-dht introduces record validation that runs before any disk write is attempted on PUT_VALUE messages:
// Fixed behavior in @libp2p/kad-dht 16.2.6 (pseudocode)
async function handlePutValue(peerId, key, value) {
// ✅ Validate record size is within configured bounds
if (value.byteLength > MAX_RECORD_SIZE) {
throw new Error('Record exceeds maximum allowed size');
}
// ✅ Validate record schema and signature
await this.validators[recordType].validate(key, value);
// ✅ Storage quota check before write
await this.enforceStorageQuota();
// Only reaches here if all checks pass
await this.datastore.put(key, value);
}
Cascading Dependency Updates
The upgrade also pulls in updated transitive dependencies, visible in the diff:
- "@libp2p/crypto": "5.1.14"
+ "@libp2p/crypto": "5.1.21"
The @libp2p/crypto bump (5.1.14 → 5.1.21) updates its own dependencies to compatible major versions:
- "multiformats": "^13.4.0",
- "protons-runtime": "^6.0.1",
- "uint8arraylist": "^2.4.8",
- "uint8arrays": "^5.1.0"
+ "multiformats": "^14.0.0",
+ "protons-runtime": "^7.0.0",
+ "uint8arraylist": "^3.0.2",
+ "uint8arrays": "^6.1.1"
These are not incidental — protons-runtime (the protobuf runtime used for DHT message serialization) and multiformats (the multiformat codec library) are both part of the record encoding and decoding pipeline. Updating them ensures that the validation logic in 16.2.6 operates on correctly parsed record structures, closing any deserialization edge cases that could have been used to bypass validation in older codec versions.
Why Each Change Was Necessary
| Change | Why It Matters |
|---|---|
@libp2p/kad-dht 16.1.3 → 16.2.6 |
Adds PUT_VALUE record validation and storage quotas |
@libp2p/crypto 5.1.14 → 5.1.21 |
Aligns crypto primitives with the new validation pipeline |
multiformats 13 → 14 |
Updated codec required by new validation logic |
protons-runtime 6 → 7 |
New protobuf runtime fixes deserialization edge cases |
uint8arraylist 2 → 3, uint8arrays 5 → 6 |
Byte array utilities aligned with new codec major versions |
Prevention & Best Practices
1. Always Validate Before Persisting Externally Supplied Data
Any data received from an untrusted network peer should be validated for type, size, and schema before it touches persistent storage. In libp2p DHT contexts, this means using the built-in validator interface:
const dht = createKadDHT({
validators: {
pk: libp2pRecord.validator, // Public key records
ipns: ipnsValidator, // IPNS records
},
selectors: {
pk: libp2pRecord.selector,
ipns: ipnsSelector,
}
});
2. Pin Dependencies to Non-Vulnerable Ranges
Use exact version pinning or tight semver ranges in package-lock.json for security-sensitive libraries. A ^16.1.3 range that was never updated left this project exposed for the entire time between the vulnerability introduction and this fix.
3. Run Automated Dependency Scanning in CI
Tools like Trivy, npm audit, and Dependabot can catch known CVEs in package-lock.json before they reach production. Add a scanning step to your CI pipeline:
# Example: Trivy in GitHub Actions
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
exit-code: '1'
4. Enforce Storage Quotas at the Application Layer
Even with a patched @libp2p/kad-dht, consider configuring disk quotas at the OS or container level for processes running DHT server nodes. This provides defense-in-depth against future resource exhaustion vectors.
5. Monitor Disk Usage on DHT Nodes
Alert on abnormal disk growth rates. A DHT server node that is consuming gigabytes of disk per hour is a signal of either a bug or an active attack.
Security Standards Reference
- CWE-400: Uncontrolled Resource Consumption — https://cwe.mitre.org/data/definitions/400.html
- OWASP: Denial of Service — https://owasp.org/www-community/attacks/Denial_of_Service
- OWASP Input Validation Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
Key Takeaways
PUT_VALUEmessages in Kademlia DHT are attacker-controlled: Any peer on the network can send them, so DHT server nodes must treat every incoming record as untrusted input and validate it before storage.- Size and quota checks must happen before the
datastore.put()call: Deferring validation until after a write — or skipping it entirely — is what made 16.1.3 exploitable. - Transitive dependency updates matter: The fix required bumping
protons-runtimefrom v6 to v7 andmultiformatsfrom v13 to v14 because the new validation logic depends on correctly functioning codec layers in those packages. package-lock.jsonis a security surface: Pinned versions in the lockfile can hold your project at a vulnerable version even ifpackage.jsonuses a permissive range. Review and update lockfiles as part of your security process.- Resource exhaustion vulnerabilities are often overlooked: Unlike RCE or data exfiltration bugs, disk exhaustion doesn't steal data — but it can take down production infrastructure just as effectively.
How Orbis AppSec Detected This
- Source: Incoming
PUT_VALUERPC messages from untrusted remote peers on the libp2p DHT network, carrying arbitrarykeyandvaluebyte payloads. - Sink: The
datastore.put(key, value)call within@libp2p/kad-dht'sPUT_VALUEmessage handler, which writes the peer-suppliedvaluebuffer directly to the node's persistent datastore without size or schema validation. - Missing control: No record size limit, no schema/signature validation before the storage write, and no per-peer or global storage quota enforcement in
@libp2p/kad-dht16.1.3. - CWE: CWE-400 — Uncontrolled Resource Consumption.
- Fix:
@libp2p/kad-dhtwas upgraded from 16.1.3 to 16.2.6 inpackage-lock.jsonandpackage.json, introducing record validation and storage quota enforcement before anyPUT_VALUEpayload is written to disk.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2026-45783 is a sharp reminder that peer-to-peer networking libraries carry a unique threat model: unlike HTTP servers where you can rely on some degree of client authentication, DHT networks are explicitly designed to accept connections from anonymous peers. Every PUT_VALUE message is attacker-controlled input, and treating it as anything less is a path to disk exhaustion. The fix — upgrading @libp2p/kad-dht from 16.1.3 to 16.2.6 — closes this gap by enforcing record validation and storage quotas at the DHT layer, before any byte hits the datastore. If you're running libp2p DHT server nodes, apply this upgrade immediately and add dependency scanning to your CI pipeline to catch similar issues before they reach production.