How CometBFT's SecretConnection handshake spends CPU before authenticating the peer
A CometBFT node runs the full SecretConnection STS handshake — an X25519 Diffie–Hellman exchange (computeDHSecret) and an Ed25519 signature verification (remPubKey.VerifySignature) — for any peer that connects, before the caller checks whether that peer's node-ID is in the node's persistent/allowlist set and disconnects it. An unauthenticated remote source therefore forces the node to spend its asymmetric-crypto budget with no valid signing key required; NullRabbit measured one attacking host driving a single-validator cometbft v0.38.22 node from a 2.6% CPU baseline to 286.3% CPU (roughly three cores).
NRDAX-T0205 ("pre-handshake-crypto-cpu-burn") is a technique NullRabbit tracks across several chains and implementations, each recorded as its own instance. This brief documents only the CometBFT / Cosmos instance measured here — it is not a claim that NullRabbit reproduced, or that the mechanism is identical, on any other listed chain.
Why this matters
SecretConnection is the upstream P2P transport for the entire Cosmos-SDK family — roughly 100 chains build on the same cometbft/cometbft P2P engine. The attack needs no valid node key, no stake, and no prior relationship with the target: any TCP client that can reach the P2P listener (default port 26656) can drive the server through the expensive half of the handshake and then simply disconnect. By default CometBFT applies no per-IP connection or handshake rate-limit before that crypto runs, so a naïve, generic rate limit placed anywhere downstream of the TCP accept doesn't help — the cost is already paid by the time a request-level limiter would see anything to reject. The advisory rates this MEDIUM: it is a single-node CPU/availability issue, not a crash and not a consensus-safety or funds issue, but a validator operator whose node is the one being flooded still has to absorb the load.
How the attack works
- Entry point. The CometBFT P2P listener (default
tcp://0.0.0.0:26656), specifically the SecretConnection STS handshake performed byMakeSecretConnectioninp2p/conn/secret_connection.go. - Preconditions. A reachable P2P listener. The connecting side needs nothing but a freshly generated Ed25519 keypair — no allowlisted node-ID, no stake, no prior handshake.
- Processing path.
MakeSecretConnectionruns, in order: generate an ephemeral X25519 keypair, exchange ephemeral public keys, compute the shared secret viacomputeDHSecret(secret_connection.go:124), derive the transcript/HKDF and AEAD keys, sign a challenge, and finally verify the remote peer's Ed25519 signature viaremPubKey.VerifySignature(secret_connection.go:178). Only afterMakeSecretConnectionreturns does the caller, inp2p/peer.go, compare the now-known remote node-ID against the node's allowlist and disconnect an unwanted peer. - Resource boundary crossed. The node's asymmetric-crypto CPU budget — the X25519 DH and the Ed25519 verify are both paid before any authentication check runs. The package's own docstring (
secret_connection.go:58-61) documents that "consumers of the SecretConnection are responsible for authenticating the remote peer's pubkey against known information, like a nodeID. Otherwise they are vulnerable to MITM" — it does not name the second consequence, that the same pre-authentication ordering lets any unauthenticated source spend the node's crypto budget at will. - Resulting behaviour. Repeating the handshake from a worker pool sustains elevated CPU on the target for as long as the flood runs. In NullRabbit's single-validator measurement (
cometbft v0.38.22,--proxy_app=kvstore, 32 workers, 30 s), one attacking host drove 1,689,798 probes that reached server-side crypto (56,326 probes/sec), of which 26,999 completed a full two-sided handshake; server CPU rose from a 2.6% baseline to 286.3% (roughly three cores) with RSS rising from 51 MB to 225 MB (+174 MB), and cleanly recovered to 6.4% CPU within 5 seconds of the flood stopping. Block production continued at ~1 block/sec (30 blocks committed) throughout.
Affected systems
- Chain: Cosmos — SecretConnection is the shared upstream P2P transport for the Cosmos-SDK family; the advisory characterises it as affecting "every CometBFT-based chain" (~100 chains build on the same engine). Only the reference
cometbftbinary was independently measured; this is not a per-downstream-chain measurement. - Implementation: CometBFT (
cometbft/cometbft). - Component / method: P2P
SecretConnectionSTS handshake,p2p/conn.MakeSecretConnection, default listener port 26656. - Version measured:
cometbft v0.38.22, on a self-owned single-validator localnet (--proxy_app=kvstore) and separately on a 4-validator localnet quorum (cometbft testnet --v 4). No per-IP handshake rate-limit is applied before the crypto runs, so the default configuration is affected. - Vendor response: Contact
security@cometbft. The vendor scopes node-level availability/DoS out of its paid-impact categories; a class the vendor declines to treat as a paid impact carries no disclosure embargo, so this is published as an operator advisory and as open ML training data, without an embargo — the same posture NullRabbit used for the previously published CometBFT consensus-channel floods, NR-2026-007.
Evidence
- Publicly documented. No independent public CVE or GitHub Security Advisory covers this behaviour; this is original NullRabbit research (NRDAX instance: fidelity
lab, discovery_originoriginal-research). - Independently reproduced by NullRabbit. Reproduced against a self-owned single-validator CometBFT localnet using CometBFT's own client-side
p2p/conn.MakeSecretConnection(so the wire format is guaranteed correct): dial, run the full STS exchange, close on completion, repeat from a worker pool. The reproducer drives a locally-run node only and does not target any public or mainnet endpoint; it is not published as a separate downloadable artefact alongside the advisory. - Measured under controlled conditions.
- Single-validator (
cometbft v0.38.22,--proxy_app=kvstore, loopback, 32 workers, 30 s): CPU 2.6% baseline → 286.3% during the attack (≈3 cores) → 6.4% on recovery; RSS 51 MB → 225 MB (+174 MB) during the attack; 1,689,798 probes reached server-side crypto (56,326 probes/sec), 26,999 completed the full handshake; 30 blocks committed during the attack window (~1 block/sec, unchanged). - 4-validator quorum (
cometbft testnet --v 4, unique loopback IPs,--proxy_app=kvstore, one attacker driving node0 for 30 s at 14,199 probes/sec, 425,959 probes completing server-side crypto): node0 CPU rose to 558% (≈5.5 of 12 cores); the other three validators stayed at 3.2–3.3% CPU (baseline). Block height progressed 37→60 during the 30 s attack window (23 blocks, ≈0.77 blocks/sec) against a 30→37 baseline (7 blocks/10 s, 0.7 blocks/sec) — quorum block production continued at essentially the baseline rate.
- Single-validator (
- Inferred but not reproduced. Whether smaller-core validator hardware (4-core/8-core, closer to typical mainnet validator sizing than the 12-core quorum test host) or multiple attackers coordinated against more than one validator simultaneously would push consensus latency or block-time is not measured — this is flagged as an open question, not established.
Operator actions
Documented in the advisory:
- Rate-limit per-IP connections/handshakes at the P2P listener, applied before the STS crypto runs. A limiter placed after the handshake completes doesn't help — the CPU is already spent by then.
- Prioritise handshakes from allowlisted node-IDs so a flood of unauthenticated attempts cannot starve legitimate peer connections.
Not in the advisory as a named mitigation, but inferable from the measured signature:
- Detection. CPU load on the P2P listener process scales directly with inbound handshake-attempt volume, even when most of those handshakes never complete on the attacker side. A CPU spike on the P2P port correlated with a rise in inbound connection attempts from a small number of source IPs is a plausible exposure check — CometBFT does not expose a built-in handshake-rate metric for this.
The advisory does not describe a shipped upstream patch. A clean fix would move the node-ID allowlist check ahead of the STS crypto, or add a per-IP handshake rate limit in the P2P accept loop; that is a suggested direction, not a shipped fix, and operators do not need to wait for it — the per-IP rate limit above is applicable today.
Canonical references
- NRDAX technique: NRDAX-T0205 — pre-handshake-crypto-cpu-burn
- NullRabbit advisory: NR-2026-008 — CometBFT SecretConnection pre-authentication handshake CPU burn
- Evidence bundle (Hugging Face): NullRabbit/nr-bundles-public — primitive
cometbft_mconn_handshake_burn(familycompute_amp, 17 bundles) - Reproducer: Not published as a separate file; the reproduction method (CometBFT's own
MakeSecretConnectionclient, worker-pool driver against a self-owned node) is described in the advisory's Reproduction section. - Upstream source:
cometbft/cometbft,p2p/conn/secret_connection.goat tagv0.38.22—computeDHSecretcall, line 124;remPubKey.VerifySignaturecall, line 178. The module docstring at lines 58–61 documents the MITM-authentication caveat but not the pre-authentication CPU-burn consequence. No CVE or GitHub Security Advisory is cited for this finding.
Related attacks
- NR-2026-007 — CometBFT consensus-channel P2P floods. NullRabbit previously published this under the same no-embargo posture (node-level availability/DoS out of the vendor's paid-impact scope); NR-2026-008 covers a separate mechanism — the pre-authentication handshake path rather than the consensus channel.
- NRDAX-T0205 (pre-handshake-crypto-cpu-burn), other instances. NullRabbit's registry records separate instances of the same technique id on Bitcoin Core's BIP-324 v2 transport, Casper, Conflux, ICON goloop, libp2p, Qtum, and XRP/rippled. Each is its own implementation-specific instance with its own measurement; this brief makes no claim about their mechanisms or about reproduction status on any chain other than the CometBFT/Cosmos one described above.
Track attacks affecting CometBFT
New CometBFT and Cosmos-SDK infrastructure findings are catalogued as they are reproduced. Track attacks affecting CometBFT on NRDAX and in NullRabbit research.
Analysis plus reproducer. No weaponised proof-of-concept code.
Related Posts
How an unauthenticated TLS half-open flood pins rippled's memory and crashes it under low file-descriptor limits
rippled's inbound TLS listeners on the peer port (51235) and the RPC-HTTPS port (5006) accept a partial TLS handshake with no deadline and no per-IP half-open cap, so a single source IP can pin server memory indefinitely and, at a default file-descriptor limit, crash the process.
How unauthenticated simulateTransaction requests saturate an Agave RPC node's executor pool
Agave runs the simulateTransaction handler synchronously on its shared Tokio executor threads, so unauthenticated, gas-free simulate requests pin those workers and add queue latency across the validator's entire JSON-RPC tier — not just the simulate path.
How unbounded HTTP/2 concurrent streams let one TCP connection OOM-kill an IOTA node's public gRPC server
An IOTA node's public gRPC server accepts an unbounded number of concurrent HTTP/2 streams on a single TCP connection, so one client multiplexing around 200 concurrent GetTransactions calls drives resident memory from a few hundred MB to multiple GB in about 10 seconds until the kernel OOM-killer terminates the node.
