Skip to content

id(world): journal restore issues participant 0 — an Issued entry for the reserved host identity restores, and its token chats and projects as the host #2972

Closed

Description

@dezren39

Problem

WorldCore::restore (pkgs/id/src/world/core.rs:138) accepts a JournalEntry::Issued for participant id 0 — the reserved host/system identity that mint can never issue — and the restored capability then authorizes as participant 0.

Participant 0 is reserved throughout the world code:

  • WorldCore::new starts next_participant_id: 1 (core.rs:89) and mint assigns ids only from that counter (core.rs:873), so no live path ever issues id 0.
  • Host-authored chat is committed as participant 0: "Commit a host-authored chat line as participant 0" (system_chat, core.rs:574-576).
  • Journal System events are only accepted when they claim participant_id == 0 (core.rs:303), and ProgramInstalled documents "participant_id is zero for this host-authored event" (pkgs/id/src/world/mod.rs:341-342).

The restore Issued arm (core.rs:172-232) checks for a duplicate id, a known parent, uses >= 1, and a delegate not outliving its parent — but never that participant.id != 0. A journal carrying an Issued entry for id 0 therefore rebuilds a capability record for the host identity itself, with a digest the journal author chose.

Verified this run (laptop probe, main 1712287)

A probe test added to src/world/tests.rs on the laptop (worktree ~/git/fleet/probe-restore-id0, since removed after the run) built a journal of Created + one Issued entry with participant.id = 0, display name host, GUEST scopes, and the digest of a self-chosen token 0.<secret>:

  • WorldCore::restore succeeded (the probe's first expectation).
  • core.chat(&cap, "forged host line") with the forged token succeeded, and the committed event carries participant_id == 0 — indistinguishable in the event log and in viewer projections from a genuine host/system line.
  • core.snapshot(&cap) lists participant 0 among the world's participants.

Probe result: world::tests::tests::probe_restore_issues_participant_zero ... ok (1 passed, 1581 filtered out, cargo test --lib, default features).

Why it matters

  • Host impersonation. Anyone who can present a journal (world import, fork, backup restore, a compromised or malicious world export) gains a working capability for the host identity: chat and inputs committed as participant 0 read as host-authored to every client and to the world program.
  • It composes with id(world): journal restore never validates Committed/Checkpoint event authors — inputs and chat from never-issued participants restore, replay, and project #2894. That issue covers restore never validating Committed/Checkpoint event authors. The natural fix there — "the author must be an issued participant or 0" — is defeated if a journal can also issue id 0 to itself: the forged record makes host-authored events "valid" and gives the forger the matching live token.
  • It persists. checkpoint_entries (core.rs:448-460) re-emits an Issued entry for every capability record, so once restored, the id-0 record is carried into every compacted journal.
  • Restore's own contract says a journal with "unknown participants" is refused, never partially applied (core.rs:130-136). Participant 0 is the one id that is never a participant.

Related but distinct: #2890 (Issued scopes), #2894 (Committed/Checkpoint authors), #2958 (participant count), #2960 (Issued display names), umbrella #2898.

Acceptance criteria

  • WorldCore::restore refuses a journal whose Issued entry names participant id 0, with an error that names the reserved id; the refusal happens before any later entry is applied (no partial restore).
  • A regression test in pkgs/id/src/world/tests.rs (in the style of restore_refuses_a_delegate_outliving_its_parent) covers the id-0 Issued case, including when the id-0 entry follows legitimate entries.
  • Legitimate journals are unaffected: existing restore/checkpoint round-trip tests pass unchanged, and id 0 remains usable exactly where it is today — host-authored System/ProgramInstalled events and system_chat (participant 0 as an author, never as an issued capability).
  • The fix does not mint or reserve any new participant id, and does not change the journal wire format.

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/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  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 1, 2026
  4. 5 remaining items

  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    task/triageQueue packet kind: verify, reproduce, and route this item
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  7. added a commit that references this issue on Nov 1, 2026
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done — fixed and merged

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/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