GitHub Advisoryadvisory · npm
GHSA-32mq-hpph-xfvr @libp2p/kad-dht: Unvalidated PUT_VALUE records allow unbounded disk exhaustion on DHT server nodes
HIGH
Official page ↗
- Aliases
- CVE-2026-45783
- Ecosystem
- npm
- Packages
- @libp2p/kad-dht
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- Weaknesses
- CWE-20, CWE-400
- Reviewed
- 2026-05-19
Affected packages
• npm @libp2p/kad-dht: < 16.2.6 → fixed in 16.2.6
Description
Summary
An unauthenticated remote peer can exhaust the disk storage of any `@libp2p/kad-dht` node running in server mode by sending an unbounded stream of `PUT_VALUE` messages whose keys bypass all content validation. No credentials, no prior relationship, and no protocol deviation beyond a crafted key are required. The victim node's datastore fills until the host disk is exhausted, making the node unavailable.
Details
Two cooperating defects combine to produce the vulnerability.
Defect 1: `verifyRecord` silent early-return (`packages/kad-dht/src/record/validators.ts:19-21`)
```typescript
export async function verifyRecord(validators: Validators, record: Libp2pRecord, options?: AbortOptions): Promise {
const key = record.key
const keyString = uint8ArrayToString(key) // decode as UTF-8
const parts = keyString.split('/')
if (parts.length `, `/ipns/`) have exactly 3 slash-delimited parts and are routed to registered validators. Any key whose UTF-8 representation splits into fewer than 3 parts, single-byte keys, or any value without two `/` characters, thus, bypasses validation entirely and is written to the datastore unconditionally. There is no audit log and no error returned to the caller.
Defect 2: Unbounded RPC message loop (`packages/kad-dht/src/rpc/index.ts:103-152`)
```typescript
let signal = AbortSignal.timeout(this.incomingMessageTimeout) // 10 s inactivity timer
signal.addEventListener('abort', abortListener)
const messages = pbStream(stream).pb(Message) // DEFAULT_MAX_DATA_LENGTH = 4 MB
while (true) {
if (stream.readStatus !== 'readable') { await stream.close({ signal }); break }
const message = await messages.read({ signal })
await this.handleMessage(connection.remotePeer, message)
// ...
signal.removeEventListener('abort', abortListener)
signal = AbortSignal.timeout(this.incomingMessageTimeout) // timer RESET each message
signal.addEventListener('abort', abortListener)
}
```
The inactivity timeout is reset after every successfully received message. There is no per-stream message count limit, no per-peer byte budget, and no rate limiter. An attacker who delivers each message within the 10-second window can stream an unlimited number of messages indefinitely.
Combined impact
• `DEFAULT_MAX_DATA_LENGTH = 4 MB` per message (from `@libp2p/utils`)
• `DEFAULT_MAX_INBOUND_STREAMS = 32` concurrent streams per `kad-dht` instance
• Attack throughput: 4 MB × unlimited messages × 32 streams
• Minimum attacker cost: standard libp2p TLS handshake (no authentication beyond that)
Differential note: `go-libp2p-kad-dht` enforces `record.Validator.Validate()` per-key at the RPC layer; records with unrecognised namespaces are rejected with an error, not silently stored. This divergence is JS-specific.
PoC
The proof-of-concept is a mocha test checked in alongside the package test suite. It uses an in-memory stream pair, thus, no network traffic, no external connections.
File: `packages/kad-dht/test/rpc/poc-put-value-unvalidated.spec.ts`:
```typescript
/**
• PoC: kad-dht PUT_VALUE stored without validation for keys with {
const peerId = await createPeerIdWithPrivateKey()
datastore = new MemoryDatastore()
const components: RPCComponents = {
peerId: peerId.peerId,
datastore,
peerStore: stubInterface(),
addressManager: stubInterface(),
logger: defaultLogger()
}
components.peerStore = persistentPeerStore({
...components,
events: new TypedEventEmitter()
})
await start(...Object.values(components))
// Default validators: only 'pk' and 'ipns' in production.
// Empty {} means: any key with ≥3 parts but unknown type throws; any key
// with <3 parts silently passes (the bypass under test).
const validators: Validators = {}
rpc = new RPC(components, {
routingTable: Sinon.createStubInstance(RoutingTable),
providers: Sinon.createStubInstance(Providers),
peerRouting: Sinon.createStubInstance(PeerRouting),
validators,
logPrefix: '',
metricsPr…
Related
Alias · CVE (1)
- CVE-2026-45783 libp2p is a JavaScript Implementation of libp2p networking stack. vulnerability