Skip to content

id(world_net): the abuse evaluator's per-peer connection cap (max_connections_per_peer=4) is never wired in — WorldProtocol::accept registers no connection, so one peer can open unlimited connections #4227

Closed

Description

@dezren39

Summary

The shared abuse evaluator's per-peer concurrent connection cap is implemented and unit-tested but never wired into any transport. AbusePolicy::max_connections_per_peer (default 4) and AbuseEvaluator::open_connection / close_connection exist only for abuse.rs's own tests: no production code path calls them. The P0-A integration (PR #3960, for #3882) wired frame/byte charging into WorldProtocol, but WorldProtocol::accept never registers a connection with the evaluator — so a single remote endpoint can open an unbounded number of QUIC connections to the world protocol, each allowed its own 16 sessions, and nothing ever refuses the 5th, 50th, or 500th connection.

Evidence (verified this run, 2026-11-02, main @762f32af)

  • pkgs/id/src/abuse.rs:237 — AbusePolicy.max_connections_per_peer; :255 — default value 4; :625 — a test pins the default.
  • pkgs/id/src/abuse.rs:433-460 — open_connection enforces the cap (returns a RateLimited refusal at the cap) and close_connection releases it; unit tests at ~:661-687 exercise cap/refusal/release.
  • Caller search across the whole crate (run independently on the sandbox clone @762f32af and on the laptop clone @12ea94d17, same result): grep -rn "open_connection|close_connection" pkgs/id/src --include="*.rs" returns no callers outside abuse.rs (definition + its own tests only). File md5s: abuse.rs 52884455f31e10e13e0da898053fc872, world_net.rs 4692a53fb89c3600be435f0e8adad7e1.
  • pkgs/id/src/world_net.rs:129-158 — WorldProtocol::accept prunes abuse state, then loops on conn.accept_bi() with only a per-connection session semaphore (MAX_SESSIONS_PER_CONNECTION = 16, :54, :136). It clones the shared evaluator into each session for frame/byte charging but never calls open_connection/close_connection, and iroh invokes accept once per incoming connection with no per-remote accounting anywhere else in the handler.
  • Contrast: the SSH transport does bound connections globally (pkgs/id/src/world_ssh.rs:71 MAX_CONNECTIONS: usize = 64), so the world QUIC transport is the unbounded one — and its bound was supposed to be per-peer, via the evaluator.

Why it matters

The evaluator's module doc states the design premise (#2244 P0-A): an endpoint's allowance is a property of the endpoint, "never multiplied by opening more connections to it", and the evaluator owns "the concurrent connection cap per peer". Frame/byte buckets are indeed shared per endpoint, but connections themselves are the resource a flooding peer spends on the host: each accepted connection carries an accept task, a JoinSet, QUIC connection state, and up to 16 session tasks with reader tasks — all before any frame is charged (a peer that opens connections and sends nothing is never charged at all). The cap that was designed, implemented, and tested for exactly this is dead code in production, so the pre-auth connection-flood surface the #2032/#2033 design set out to close is still open on the world protocol.

Distinct from #3927 (oversize requests get a temporary refusal from check_bytes/check_frame — a refusal-contract issue inside the evaluator) and from #4004/#3991 (chat-presence state bounds). This is about the evaluator's connection-count contract having no consumer.

Acceptance criteria

  • WorldProtocol::accept (and any other transport the integration charges frames on) registers each incoming connection with the shared evaluator (open_connection keyed by the remote endpoint) and releases it (close_connection) when the connection ends, on all exit paths.
  • A connection refused by the cap is rejected/closed without spawning session tasks, and the refusal is logged/counted so operators can see cap hits.
  • Regression test: one endpoint opening max_connections_per_peer + 1 concurrent connections to a WorldProtocol sees the extra connection refused, while a second endpoint is unaffected; after one connection closes, a new one is admitted.
  • If the intended scope is deliberately narrower (e.g. the cap is meant only for a future transport), the policy field and evaluator doc say so explicitly instead of presenting a cap no transport enforces.

Related: #2244 (parent), #3881 (core, closed — introduced the cap), #3882 / PR #3960 (integration, closed/merged — wired frame/byte charging but not the connection cap), #2033 (design).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 2, 2026
  2. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (lane A, new-issue-creator-code-a)
    • Session / ID: cron new-issue-creator-code-a run 2026-11-02 00:35 CST
    • Found via: filed this run
    • Task: Filed after verifying on main @762f32af that the abuse evaluator's per-peer connection cap has no production caller and WorldProtocol::accept registers no connections; triage packet enqueued to repo-health-code-pilot.
    • Expectation: effort/s — triage confirms the unwired cap on current main and routes the fix (register/release connections in accept, regression test for cap+1 connections).
    • Links: packet creator-a-2026-11-02-0035/packets/packet-4227-triage.json
  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  4. 8 remaining items

  5. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Queue task 6389 overlap check: fresh diff of open PR #3896 (updated 2026-11-02 19:27Z) changes WorldClient and adds the client-side WorldSessions coordinator/tests in world_net.rs; it does not change WorldProtocol::accept or call AbuseEvaluator::open_connection / close_connection. It does not implement #4227's server-side per-peer connection cap, so this fix remains distinct. No hard-stop labels on #4227.

  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: pr_opened
    task: 6389 (repo-health-code-pilot; worker repo-worker-code; OpenChamber scheduled lane aea12c82-c5af-4e5a-9b66-f8e360f87177)
    result: PR #4573 is OPEN at 657e6fd535f1e7e683ff5fb36f5275ade01fb9b4; exact implementation and cap regression test are in the PR. Targeted tests passed on the merged base: abuse 19/19 and world_net 21/21.
    ci: basic: OpenCode metered-provider guard is QUEUED. .github/workflows/id-check.yml has no pull_request trigger, so the affected-crate PR matrix is not present; coverage gap is noted in the PR description.
    next: Recheck checks within the five-minute window; park if still pending.

  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: parked
    task: 6389 (repo-health-code-pilot; worker repo-worker-code; OpenChamber scheduled lane aea12c82-c5af-4e5a-9b66-f8e360f87177)
    pr: #4573 OPEN at 657e6fd. Tests on merged main passed: abuse 19/19; world_net 21/21.
    reason: basic: OpenCode metered-provider guard remains QUEUED after active polling. The policy-required basic proxy and id matrix checks are absent because nix-ci.yml and id-check.yml lack pull_request triggers; this is documented on PR #4573.
    resume: After the 20-minute park, freshly recheck #4227, PR #4573 and checks. Do not repeat tests unless the head changes.
    labels: parked; work/assigned retained.

  8. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 2, 2026
  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  11. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions