Skip to content

fix(cli): fold kind 40003 edits into messages get/thread, author-matched - #6474

Open
sanjay3290 wants to merge 2 commits into
block:mainfrom
sanjay3290:spark2/messages-edit-fold
Open

fix(cli): fold kind 40003 edits into messages get/thread, author-matched#6474
sanjay3290 wants to merge 2 commits into
block:mainfrom
sanjay3290:spark2/messages-edit-fold

Conversation

@sanjay3290

@sanjay3290 sanjay3290 commented Aug 21, 2026

Copy link
Copy Markdown

Fixes #6388.

buzz messages edit publishes a kind 40003 event, the relay stores it, and messages get never shows it — 40003 was simply absent from the kinds filter. messages thread fetches it and then prints the edit and the stale original side by side.

What this does

A single pure function, apply_edits(&[Value]) -> Vec<Value>, folds each target's latest 40003 onto the original, drops the raw edit rows, and stamps edited_at. Both cmd_get_messages and cmd_get_thread call it after their sort, so the two commands can no longer disagree. The kinds list moves into get_message_kinds() so the filter and its test read the same code path.

Semantics:

  • An edit is applied only if edit.pubkey == target.pubkey.
  • Latest created_at wins; ties break on the lexicographically larger event id.
  • Edits whose target is outside the returned window are dropped, not rendered.

The author gate runs before ranking, not after

This is the part worth reviewing. Checking the author on the winning edit is not equivalent to checking it while building the map, and the difference is a message-suppression bug.

If ranking happens first, an attacker publishes a 40003 tagging your message with a created_at above your genuine edit. Their forgery takes the map slot; your real edit is discarded. The author check then rejects the forgery, and the reader falls back to the original content. Anyone in the channel can blank out any edit anyone makes.

A one-edit fixture cannot see this — with a single edit in the window the two placements behave identically. hostile_edit_must_not_suppress_a_genuine_author_edit uses the three-event case that distinguishes them: target(author) / edit(author, t=2000, "genuine edit") / edit(other, t=3000, "hijack"). Expected and actual is "genuine edit".

Note for reviewers: the CLI edit-surfacing in the still-open #2781 has the same ordering, and no author check at all. I left the details there.

Tests

7 tests in apply_edits_tests, written before the fix and watched fail. Deletion probes, both compiling and both producing real failures:

  • Remove the author comparison from the map-construction loop → edit_by_non_author_is_ignored and hostile_edit_must_not_suppress_a_genuine_author_edit fail.
  • Remove 40003 from get_message_kinds()get_messages_filter_includes_edit_kind fails.
./scripts/run-tests.sh unit                              -> All tests passed (exit 0)
cargo clippy --workspace --all-targets -- -D warnings    -> exit 0
cargo fmt --all -- --check                               -> exit 0

Out of scope: messages search, tag overlay from the edit event, and any desktop-side change.

Coverage limitation

apply_edits and get_message_kinds are each pinned by tests, but nothing asserts that cmd_get_messages and cmd_get_thread actually call apply_edits — both are async and do network I/O, so there is no unit-testable seam. Unwiring both call sites is caught only by dead_code under -D warnings; unwiring one is not caught at all. Flagging it rather than adding a mock harness for two call sites.

Signed-off-by: Sanjay Ramadugu <sramadugu1@gmail.com>
Signed-off-by: Sanjay Ramadugu <sramadugu1@gmail.com>
@sanjay3290
sanjay3290 requested a review from a team as a code owner August 21, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

messages edit is accepted but get/history still show the original kind 9

1 participant