← Back to Research
Research · August 28, 2026

The Internet Computer's per-peer ingress quota is keyed to the wrong peer, so one sender evicts everyone

Simon Morley·6 min read

The Internet Computer replica's ingress pool enforces a per-peer quota that is supposed to stop one noisy peer saturating ingress, but for HTTP-arriving messages the replica keys that quota on its own NodeId rather than the signing principal, so 10,001 valid signed messages from one unauthenticated attacker fill a bucket shared by everyone and purge all unvalidated HTTP ingress on that replica at the next state change.

Why this matters

A per-peer quota is a fairness mechanism. Its entire purpose is isolation: your traffic should not be able to affect mine. When the key is wrong, that mechanism inverts. It stops being the thing that keeps one sender from crowding out the others and becomes the thing that lets one sender evict all of them, because they share a bucket they were never meant to share.

Nothing here is a bypass. The attacker sends genuinely valid, correctly signed ingress messages, pays the ordinary fees, and holds no special role. The quota does exactly what it was written to do, on a key that does not mean what the surrounding code assumes it means.

The cost asymmetry is stark. One keypair signing 10,001 small messages costs sub-second CPU and about 1 MB of bandwidth to knock out one replica's in-flight HTTP ingress. Holding that state costs roughly 100 Mbit/s, which is single-host territory. Doing it across a 40-replica subnet takes about 40 MB in total.

DFINITY already knows. There is a TODO in the repository, at interfaces/src/ingress_pool.rs:22-25, saying the originator should be derived from the signature. The signer is available at the HTTP boundary; it is simply not threaded through.

How the attack works

Entry point. POST /api/v2/canister/<id>/call, the public HTTP ingress endpoint.

Preconditions. None beyond the ability to make HTTP requests and sign valid ingress envelopes. No special role, no completed session, no balance beyond ordinary fees. Reach is public.

The mis-key. rs/http_endpoints/public/src/call.rs:391 sends every HTTP-arriving ingress message down ingress_tx with node_id = self.node_id, the carrier replica's own id, set at call.rs:104. It is not the originating principal. rs/artifact_pool/src/ingress_pool.rs:240-242 then constructs the pool object with that value as peer_id, and the per-peer counters at peer_counter.rs:42-74 accumulate every HTTP-arriving message under that one key.

The trip. exceeds_limit at ingress_pool.rs:226-232 compares the shared bucket against ingress_pool_max_count (10,000) and ingress_pool_max_bytes (100 MB) using a strict >. Exactly 10,000 does not trip it. Message 10,001 does.

The purge. ingress_handler.rs:60-76 responds to exceeds_limit being true for a peer by removing all unvalidated ingress originating from that peer at the next on_state_change, which runs on roughly a 200 ms cadence. Because every HTTP-arriving message carries the carrier NodeId, "that peer" is every HTTP submitter at once. Legitimate users' messages submitted in the same window are removed along with the attacker's.

Resulting behaviour. For as long as the attacker sustains the fill rate, 100% of unvalidated HTTP-arriving ingress on the targeted replica is purged every cycle. New user transactions cannot land. Work that was already validated continues.

Affected systems

  • Chain: Internet Computer.
  • Implementation: dfinity/ic replica, source-traced at HEAD.
  • Components: the HTTP call endpoint (rs/http_endpoints/public/src/call.rs) and the ingress pool (rs/artifact_pool/src/ingress_pool.rs, ingress_handler.rs, peer_counter.rs).
  • Configuration measured: production caps, ingress_pool_max_count = 10000 and ingress_pool_max_bytes = 100000000.
  • Reach: public. Any HTTP client.

The mis-key affects HTTP-arriving ingress specifically, which is the path that carries the carrier NodeId.

Evidence

Publicly documented. No CVE. The defect is, however, acknowledged in DFINITY's own source: the TODO at interfaces/src/ingress_pool.rs:22-25 calls for deriving originator_id from the signature.

Independently reproduced by NullRabbit. The published corpus reproducer captures the attack traffic: a burst of small POST /api/v2/canister/<id>/call requests, each carrying a roughly 250-byte signed-ingress-shaped envelope, across several postures (single-source low-volume and saturating, distributed across source IPs, and paced mimicry). The flood count in the capture is scaled down for capture speed, with the true boundary preserved in the bundle provenance. NRDAX records this instance at fidelity: lab, discovery_origin: original-research.

Measured under controlled conditions. An in-process Rust harness instantiates IngressPoolImpl at the exact production caps (10,000 messages, 100,000,000 bytes) and drives the real insert and exceeds_limit path:

  • Boundary confirmed. 10,000 HTTP-shape inserts do not exceed the limit, because the comparison is a strict >. 10,001 sets exceeds_limit(carrier) to true. The three tests ran 30,003 inserts in 0.04 s in release mode.
  • Principal diversity is irrelevant. Inserts with deliberately diverse payloads and principals, but the same carrier peer_id, all bucket together. The quota does not distinguish signers, which is the finding stated as a measurement rather than as a reading of the code.

Attacker cost, derived from the measurement. One keypair signs 10,001 messages of about 250 bytes: sub-second CPU and roughly 1 MB of bandwidth per replica. Sustaining the purge requires refilling the bucket every 200 ms cycle, about 50,000 signed ingress per second, which at 250 bytes each is roughly 100 Mbit/s and feasible from a single host. Parallelised across a 40-replica subnet, about 40 MB in total.

Explicitly not claimed. The advisory is clear that it rests on the source trace and the production-cap boundary measurement, not on the reproducer traffic alone. There is no authentication bypass: the messages are genuinely valid. No funds, no state corruption, no consensus halt. Subnet liveness continues on already-validated work.

Operator actions

The fix, as the vendor's own TODO prescribes:

  • Key the quota on the signer. Extract the signing principal from the signed ingress envelope and use it as originator_id. The advisory characterises this as a 1 to 3 line change at call.rs:391 to thread the signer through, plus the associated bookkeeping. That restores per-principal isolation, so one submitter cannot evict another's ingress.

Interim, for operators rather than the vendor:

  • Per-source-IP or per-principal admission limiting at the boundary node in front of the replica bounds the fill rate an attacker can sustain. Be clear about what this buys: it is a rate cap, not a fix for the mis-key. The shared bucket still exists, and a distributed submitter can still fill it.

Canonical references

  • NRDAX technique: NRDAX-T0246 (rate-limit-key-confusion), instance ic_ingress_pool_quota_miskey
  • NullRabbit advisory: NR-2026-036, published 2026-07-09
  • Evidence bundle: primitive ic_ingress_pool_quota_miskey in the public dataset NullRabbit/nr-bundles-public
  • Source trace and measurement: chains/ic/findings/IC_N1_INGRESS_POOL_QUOTA_MISKEY/
  • Upstream: dfinity/ic, rs/http_endpoints/public/src/call.rs, rs/artifact_pool/src/ingress_pool.rs, rs/interfaces/src/ingress_pool.rs

Related attacks

NRDAX-T0246 (rate-limit-key-confusion) is the technique for exactly this shape: a limit that exists, functions, and is keyed on something that does not identify the party it is meant to isolate. This is currently its only briefed instance.

Three other Internet Computer availability findings sit alongside it in the registry and are separate primitives with their own advisories: ic_orchestrator_cup_body_bomb (an unbounded body.collect() on the CUP-pull path), ic_xnet_concurrency_cap (a 4-permit concurrency cap on the XNet endpoint), and ic_quic_halfopen_pin (an unbounded half-open connection pin on the QUIC P2P transport). The XNet one is a useful contrast: there the cap exists and is simply too small, where here the cap is a reasonable size and points at the wrong key.

Track attacks affecting the Internet Computer in NRDAX.

Analysis plus reproducer. No weaponised proof-of-concept code.

internet-computerdfinityingress-poolrate-limit-key-confusionrpcinfrastructure-security
Publication policy:Why we publish these findings →

Related Posts