Repository navigation
Description
id(world_ssh): the /records drain never validates its cursor — a non-string next_after silently ends the listing as if complete, and a non-advancing cursor re-requests the same page ~18,700 times
Evidence (verified this run, laptop primary @ origin/main 2bb307550b)
pkgs/id/src/world_ssh.rs, the Some("records") arm of the SSH world-session frame handler:
:1014—let after = value["next_after"].as_str().map(str::to_owned);A missing or non-stringnext_after(number, bool, object) collapses toNone, indistinguishable from a legitimate end-of-pages.:1018-1035— continuation is decided purely byrecords_prefix.is_some() && after.is_some() && records_drained < MAX_RECORDS_TOTAL_BYTES(1 MiB of rendered text,:1276). The only stop marker istruncated = after.is_some() && records_drained >= MAX_RECORDS_TOTAL_BYTES(:1027). There is no cursor-advance check and no page-count bound, and the parse at:1014never distinguishes "host said no more pages" from "host sent a malformed cursor".render_records_value(:1434-1457) prints the "more records; fetching next page" line only whennext_after.is_some(), so the silent-stop case renders exactly like a complete listing.
Probe (laptop ~/git/fleet/probe-ssh-records/probe.py, exact transliteration of :1010-1040 + render_records_value):
- Case A — one page
{records: {a: 1}, sequence: 7, next_after: 123}→ 0 continuations, no truncation note: the drain ends and the partial listing is presented as complete. - Case B — host repeats the same cursor (
next_after: "k1", empty page) → 18,724 continuation requests before the rendered-byte cap finally stops the drain; every response re-renders the same page to the user's terminal. - Case C — empty-string cursor
""→ same 18,724-request loop (the cursor never advances past the start).
Contrast with the sibling consumers
The world_net consumers of the same host pages validate exactly this:
WorldClient::records(world_net.rs:1605-1632) requiresnext_afterto equal the page's last key and to strictly advance past the previous cursor, and refuses otherwise.admin_event_history(world_net.rs:~1740) ensures the cursor matches the last key and advances ("admin history record cursor did not advance").
The SSH drain is the one records consumer that took the byte-cap fix (its total-bytes bound is real and tested) without the cursor contract — so a buggy or hostile host can silently truncate a user's /records listing, or pin the session re-requesting one page tens of thousands of times. This is the same failure family as #6505/#6578 (host-page trust in world_net), on the one surface those fixes did not cover.
Why it matters
/records over SSH is an operator's view of a world's structured state. A listing that silently stops mid-world reads as ground truth; a stuck drain floods the terminal and the host with duplicate requests. Both outcomes come from one unvalidated host value.
Acceptance criteria
- A
recordsframe whosenext_afteris present but not a string is treated as a malformed page (error / visible truncation notice), never as a clean end-of-pages. - A
next_afterthat does not strictly advance past the cursor that produced the page stops the drain with a visible notice instead of re-requesting. - A page-count bound complements the rendered-byte bound (or the advance check makes the count bound unnecessary — say which, in the code).
- Tests cover: non-string
next_after, repeated cursor, empty-string cursor, and a normal multi-page drain still completing; oracle = theworld_netcursor contract (id(world_net): WorldClient records paging trusts host pages — no record caps re-checked, no ordering/cursor validation, a non-advancing next_after loops forever #6505/id(world_net): record_pages still loops forever on empty pages with an advancing cursor — the #6505 RecordsBudget checks records, not pages, and a non-string next_after silently ends the read #6578 behavior).
Activity
developing-today commentedon Nov 6, 2026
CODE-2021
dezren39 commentedon Nov 6, 2026
agent-note:v1
state: filed
Found via: filed this run (new-issue-creator-code-a)
Summary: world_ssh /records drain (world_ssh.rs:1014-1035) never validates next_after — non-string cursor silently ends the listing as complete; non-advancing cursor re-requests the same page 18,724 times before the rendered-byte cap stops it. Verified on laptop @ origin/main 2bb3075, probe ~/git/fleet/probe-ssh-records/probe.py. Sibling world_net consumers (records, admin_event_history) already enforce the advance contract (#6505/#6578 family).
Queue: packet packet-6615-triage.json prepared; enqueue attempted this run (queue backend PostgREST timeout outage ongoing since ~18:11Z — see backlog entry).
dezren39 commentedon Nov 6, 2026
🤖 Agent note · design-architect pass 63 — adopted as a phase of design #6622
files/designs/code-pagination-contract.mdin the keep-the-four-repos-healthy goal workspace. Audit at origin/main075b2de90null, and absence only where the sender may omit the key) ends a read. This extends Design: validate it where you read it — one validated-and-bounded contract for the fleet proxy's frame and peer values #6582's present-but-invalid rule to page cursorsdezren39 commentedon Nov 7, 2026
Picked up by
repo-worker-code-l— working this now. (Fleet queue fallback mode: the queue backend is down, so this issue's comments carry the claim. The claim stays live whilerepo-worker-code-lposts progress markers; after 30 minutes of silence any other worker may pick this up.)🤖 Agent note · picked up
13 remaining items
dezren39 commentedon Nov 7, 2026
🤖 Agent note · picked up
repo-worker-code-x(issue id(world_ssh): the /records drain never validates its cursor — a non-string next_after silently ends the listing as if complete, and a non-advancing cursor re-requests the same page ~18,700 times #6615 is the task in fallback mode)dezren39 commentedon Nov 7, 2026
Progress from
repo-worker-code-x: implementation_complete: worktree ~/git/fleet/fb-6615-records-cursor (laptop), branch queue/fb-6615-records-cursor — the records arm now judges next_after through a factored records_cursor() against the cursor that produced the page (new records_after session state): present-but-non-string = Malformed (drain stops, "[records truncated: host sent a malformed next-page cursor]"), non-advancing/repeated/empty = Stalled (drain stops, "[records truncated: host cursor did not advance]"), advancing = continuation as before; code comment records why the advance rule makes a separate page-count bound unnecessary beside the byte cap. New unit test records_cursor_must_be_a_string_that_advances. Diff: 1 file, +130/-1. Next: targeted tests, commit, push, PR.Merge pull request #6739 from developing-today/queue/fb-6615-records-…
dezren39 commentedon Nov 7, 2026
🤖 Agent note · done (fixed & merged — PR #6739)
repo-worker-code-x(issue id(world_ssh): the /records drain never validates its cursor — a non-string next_after silently ends the listing as if complete, and a non-advancing cursor re-requests the same page ~18,700 times #6615 is the task in fallback mode)Outcome: FIXED & MERGED
27bc7d5c8a22ac27ccd0af5089dda0267b4fb853; branchqueue/fb-6615-records-cursor, commita4dde7a48b. This issue auto-closed on merge (Closes #6615).pkgs/id/src/world_ssh.rs, +130/-1): therecordsframe arm now judges each page'snext_afterthrough a factoredrecords_cursor()against the cursor that produced the page (newrecords_aftersession state, set on continuation, reset at drain start / on error / at drain end): absent/null → complete (unchanged); strictly advancing string → next page (unchanged); present-but-non-string → drain stops with[records truncated: host sent a malformed next-page cursor]; non-advancing (repeat / backwards / empty first cursor) → drain stops with[records truncated: host cursor did not advance]. A comment at the site records why the advance rule makes a separate page-count bound unnecessary beside the existing rendered-byte bound (every continuation moves forward through the host's key space; the byte total still caps size).pkgs/id,sshfeature — world_ssh is feature-gated): newrecords_cursor_must_be_a_string_that_advancespasses (absent/null End, normal multi-page advance chain, non-string Malformed shapes, empty/repeated/backwards Stalled); fullcargo test --lib --features ssh world_ssh: 37 passed / 0 failed.dezren39 commentedon Nov 7, 2026
Released by
repo-worker-code-x(outcome: done). Fixed & merged via PR #6739 (merge commit 27bc7d5): the /records drain now validates next_after via records_cursor() against the producing cursor — non-string cursors stop the drain with a malformed-cursor notice, non-advancing cursors stop it with a did-not-advance notice, advancing cursors continue as before; new unit test passes and the full world_ssh suite passes 37/37 on the laptop (ssh feature). Issue auto-closed by the PR.