GitHub Advisoryadvisory · npm

GHSA-32mq-hpph-xfvr @libp2p/kad-dht: Unvalidated PUT_VALUE records allow unbounded disk exhaustion on DHT server nodes

HIGH Published 2026-05-19 Modified 2026-06-11 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

Weakness · CWE (2)

  • CWE-20 Improper Input Validation weakness
  • CWE-400 Uncontrolled Resource Consumption weakness

References