Skip to content

id(world): world list plain output renders host-supplied names raw — a newline in a world name forges extra rows, and a tab forges columns #3089

Closed

Description

@dezren39

Problem

The plain-text output of id world list prints each world name raw in a tab-separated, one-row-per-world format. The names come from the host's worlds reply over the wire and are accepted by the client with no validation, so a name containing a newline forges an extra row and a tab forges extra columns — the same row-forgery class fixed for peers in #1613 (plain_field, commands/text.rs) and filed for blob names (#1742) and meta-tags (#1735), but never filed for the world-directory renderer, which uses neither plain_field nor the tokens() renderer.

Evidence (verified this run on main 33b6cb36)

  • Rendering: pkgs/id/src/commands/world.rs:677-688 — the non-JSON branch prints println!("{name}{default_marker}\t{}", if open { "open" } else { "closed" }) with name interpolated verbatim.
  • Wire receipt accepts anything: pkgs/id/src/world_net.rs:690-697 — WorldClient::list_worlds collects w["name"].as_str() from the host's JSON reply with no validation or re-check against validate_world_name.
  • A conforming current hub does validate at create time (pkgs/id/src/world_hub.rs:48-63, a-z0-9-_ only), so the payload requires a non-conforming, older, compromised, or hand-provisioned host/storage — but the ruling recorded in commands/text.rs:1-17 (id(peers): a peer's announced name is printed raw — a newline in a gossip announcement forges extra TSV rows in id peers and the REPL #1613) is that the contract lives at the printer: a printer never lets a value break row structure, precisely because a store/host may already hold a value ingest validation could not have cleaned. The --json branch (world.rs:668-676, via directory_json) is safe.
  • Probe (verbatim replication of the format string, python; output in the goal's hidden_files/probe-2026-10-31-laneB-worldlist/probe.txt): worlds lobby, evil\nfake\topen, a\tb render as 4 physical lines for 3 worlds — the second line is a fully-formed forged row fake\topen — with tab-field counts [2, 1, 3, 3] instead of 2 per row.

Why it matters

id world list plain output is the operator/script surface for the world directory (the JSON shape is the documented machine feed, #812; the plain rows are what terminals and shell pipelines consume). A hostile or merely buggy host can plant a phantom "open" world row in every listing a client prints, or shift the open/closed column — the exact output-forgery pattern #1611/#1613/#1742/#1735 eliminate renderer by renderer. This is the remaining unguarded TSV renderer in commands/.

Acceptance criteria

  • A world name (and the default-world marker comparison) in the plain branch of id world list renders through plain_field (commands/text.rs) or an equivalent quoting rule: a name containing \n, \r, \t, or other control characters produces exactly one physical line with exactly 2 tab fields.
  • Names with no structure-breaking character (including ordinary valid world names) render byte-identically to today; the --json output is unchanged.
  • Regression tests at the renderer level, mirroring the commands/text.rs / peers tests: a name with an embedded newline + forged <name>\topen payload produces one line; a tabbed name stays one field.
  • Existing id crate tests still pass (laptop or CI).

Dedup

Searched open + recently closed issues for world-list / TSV / forgery / plain_field / validate_world_name terms. #1613 is peers/gossip, #1742 is list/find blob names, #1735 is meta-tags, #2839 is a plain_field trigger-set gap for peers, #2960/#2890 are journal-restore display-name/scope validation in the world engine. None covers the commands/world.rs world-directory plain renderer or the world_net.rs receipt path.

Activity

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

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (lane B, new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-31 09:20 CDT
    • Found via: filed this run
    • Task: id world list plain output renders host-supplied world names raw — a newline in a name forges an extra TSV row and a tab forges columns; the client accepts names from the host's worlds reply with no validation (world_net.rs), and this renderer uses neither plain_field nor tokens(). Verified this run by code read on main 33b6cb3 plus a verbatim format-string replication (3 worlds → 4 physical lines, forged 'fake⇥open' row).
    • Expectation: effort s — triage routes a fix that renders the name through plain_field (commands/text.rs) with renderer-level regression tests; --json unchanged.
    • Links: queue packet packet-3089-triage.json (repo-health-code-pilot)
  3. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Adopted as Phase 3a of design #3455 (a value cannot act on the terminal — one format-character contract for pkgs/id plain output; design doc files/designs/code-id-plain-output-rendering.md in the keep-the-four-repos-healthy goal workspace). Triage: do NOT re-file, re-scope or split this issue — record the phase structure instead.

    This phase is NOT gated: routing the world name through plain_field is correct today for structure characters (newline/tab forgery, this issue's acceptance), and its format-character (Cf) coverage arrives automatically when Phase 1 (#2839) widens plain_field's predicate — no change needed in this issue when that lands. The default-marker comparison stays on the raw name; --json unchanged.

  4. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. 10 remaining items

  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    status/triagedTriaged and routed (see agent note)
    on Nov 1, 2026
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done / handed off

  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    status/triagedTriaged and routed (see agent note)
    on Nov 1, 2026
  10. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions