Skip to content

fix(buzz-cli): stop silent no-op mem patches; surface NIP-44 ciphertext cap - #7654

Open
gozepolat wants to merge 1 commit into
block:mainfrom
gozepolat:mem-encode-fixes
Open

gozepolat wants to merge 1 commit into
block:mainfrom
gozepolat:mem-encode-fixes

Conversation

@gozepolat

Copy link
Copy Markdown

Summary

Two buzz mem defects that seats hit in the wild, both reproduced against a live relay and fixed client-side:

  1. mem patch silently succeeds on no-op patches. A patch whose result is byte-identical to the current value (context-only hunks, delete-then-reinsert pairs, or the empty diff diff -u emits for identical files) applied cleanly, published a fresh head event carrying identical content, echoed the input diff, printed wrote … with a sha256 equal to the base-hash (by construction), and exited 0 — while the stored value never changed and the event chain still advanced. Neither the wrote line nor a new event id is mutation evidence; the only mutation evidence is post-write hash inequality, which a no-op can never produce. cmd_patch now refuses byte-identical results before the dry-run branch: nonzero exit, no event published.

  2. Writes sized against NIP44_PLAINTEXT_MAX (65,535) bounce deep inside nip44::encrypt with a generic message too long. The NIP-44 v2 pad caps the serialized body at 65_536 - 128 = 65,408 bytes, and the serialized body is the raw value plus the JSON envelope (slug + ~22–28 bytes) plus one byte per escaped newline/tab/quote/backslash. A raw 65,080-byte value with 400 newlines (65,518 serialized) passed the 65,535 raw-value check and the 65,535 serialized-body check, then failed at the crypto layer with no actionable detail. build_event now enforces the real 65,408 serialized cap up front with an error naming the serialized length, the raw length, and the exact trim amount.

Also for release notes: the currently-deployed CLI vintage maps a relay duplicate: (NIP-33 LWW-dominated write) response to success — concurrent patches against the same base both report wrote while only one lands. Current source already converts this to a Conflict; that fix rides the same release as this one.

End-to-end verification (source-built CLI vs. live relay)

  • No-op refusal triad: context-only patch → refusal message + exit 1 + head event id unchanged (no event published).
  • Ciphertext cap boundary: at-cap serialized body (65,408) accepted and read back intact; one byte over (65,409) → serialized body exceeds 65408-byte NIP-44 v2 limit (65409 bytes; raw value 65372 bytes + JSON envelope and escape overhead). Trim the raw value by at least 2 bytes and retry.
  • Repro of the wild failure: 65,080 raw + 400 newlines → named error with exact arithmetic (65,517 serialized for that slug).
  • Positive control: a real patch lands, read-back shows the edit, post-write hash differs from base-hash.

Tests

Four no-op refusal shapes pinned (context-only, delete-reinsert, empty-diff, real-change control); ciphertext-cap boundary tests (at-cap accepted, one-over rejected with exact trim advice, escape-overhead arithmetic matching the 65,080 + 400-newline repro). buzz-core 257 passed, buzz-cli 477 passed; cargo clippy --all-targets and cargo fmt clean; cargo check --workspace clean.

Effective-budget guidance (docs in the const)

For markdown-dense values, budget against the serialized body: raw value ≤ 65,408 − envelope(~28) − newlines/tabs/quotes count. Plain values cap at ~65,370–65,380 depending on slug length.

@gozepolat
gozepolat requested a review from a team as a code owner September 15, 2026 07:23
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 7c789dee09d198469bded1cb5be902eaf5562ad9...cea7045f1a867c685b64740fe9803eef7222930d.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review cea7045f1a867c685b64740fe9803eef7222930d to authorize a new review.
Any previous review applies only to its recorded range.

…xt cap

Two memory-store defects seats hit in the wild, both reproduced and fixed:

1. mem patch silently succeeds on no-op patches. A patch whose result is
   byte-identical to the current value (context-only hunks, delete-then-
   reinsert pairs, or the empty diff diff -u emits on identical files)
   applied cleanly, published a fresh head event with identical content,
   echoed the input diff, printed 'wrote ...' with a sha256 equal to the
   base-hash (by construction), and exited 0 — while the stored value
   never changed and the event chain still advanced. Neither the 'wrote'
   line nor a new event id is mutation evidence; the only evidence is
   post-write hash inequality. cmd_patch now refuses byte-identical
   results before the dry-run branch: nonzero exit, no event published.

2. Writes sized against NIP44_PLAINTEXT_MAX (65,535) bounce deep inside
   nip44::encrypt with a generic 'message too long'. The NIP-44 v2 pad
   caps the *serialized body* at 65,536 - 128 = 65,408 bytes, and the
   serialized body is the raw value plus the JSON envelope (slug +
   ~22-28 bytes) plus one byte per escaped newline/tab/quote/backslash.
   A raw 65,080-byte value with 400 newlines (65,518 serialized) passed
   the 65,535 raw-value check and the 65,535 serialized-body check, then
   failed at the crypto layer with no actionable detail. build_event now
   enforces the real 65,408 serialized cap up front with an error naming
   the serialized length, raw length, and the exact trim amount.

Also worth noting for release notes: the currently-deployed CLI vintage
maps a relay 'duplicate:' (NIP-33 LWW-dominated write) response to
success — concurrent patches against the same base both report 'wrote'
while only one lands. Current source already converts this to a
Conflict; that fix rides the same release as this one.

Tests: four no-op refusal shapes pinned (context-only, delete-reinsert,
empty-diff, real-change control), ciphertext-cap boundary tests (at-cap
accepted, one-over rejected with exact trim advice, escape-overhead
arithmetic matching the 65,080 + 400-newline repro). buzz-core 257,
buzz-cli 477, all green; clippy and fmt clean.

Signed-off-by: Aydin Goze Polat <mrchesstr@gmail.com>
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.

1 participant