Skip to content

fix(cli): reject JSON-escaped newlines before signing - #5114

Open
johnely19 wants to merge 1 commit into
block:mainfrom
johnely19:fix/multiline-publication
Open

fix(cli): reject JSON-escaped newlines before signing#5114
johnely19 wants to merge 1 commit into
block:mainfrom
johnely19:fix/multiline-publication

Conversation

@johnely19

Copy link
Copy Markdown

Summary

Messages built by shell wrappers can reach the relay carrying literal \n escape text instead of real LF bytes, so recipients see \n where paragraph and list breaks were intended. The CLI signs and publishes the mangled content without complaint, and the damage is only visible after the fact in someone else's client.

This validates content before signing and fails closed when escape text appears outside Markdown code regions at paragraph/list boundaries (\n\n, \n- , \n* , \n# ), pointing the caller at --content -.

The check is deliberately narrow. Intentional \n inside inline and fenced code is preserved, because that is legitimate content — a message explaining printf 'a\nb' must still be sendable. It only rejects the structural positions that a JSON-escaped multiline message produces, which is where the mistake actually shows up.

Why this is separate from #2121

#2121 (fix(acp): teach agents to send real newlines, merged July) addressed this at the prompt level in base_prompt.md. That reduces the mistake but cannot prevent it: any wrapper, script, or non-ACP caller building a message with single-quoted shell strings still publishes broken content, and instructions are not a mechanism.

I hit this in practice today, well after #2121 shipped, which is what prompted the change. Guidance tells the caller what to do; this makes the signing boundary refuse to do the wrong thing.

Related issue

None found. Searched open and merged PRs and issues for newline/escape handling; the closest is #2121 above, which is complementary rather than overlapping — it changes ACP prompt text, this changes CLI validation. No open issue covers the CLI path.

Testing

cargo test -p buzz-cli at c92b5b0a: 324 passed, 0 failed.

Two new cases cover both directions, because a validator that only proves the failure case is half-tested:

  • multiline_markdown_keeps_real_lf_bytes — real LF content passes untouched, including \n inside inline and fenced code.
  • json_escaped_markdown_boundaries_fail_before_signing — escaped boundaries are rejected before signing, so nothing reaches the relay.

The second is the one that matters: the failure has to happen ahead of the signature, not after, or the event is already published by the time anyone notices.

No UI change.

Messages built by shell wrappers could reach the relay with literal `\n`
escape text instead of real LF bytes, so recipients saw backslash-n where
paragraph and list breaks were intended.

Validate content before signing and fail closed when escape text appears
outside Markdown code regions at paragraph/list boundaries, pointing the
caller at `--content -`. Intentional `\n` inside inline and fenced code is
left untouched rather than rewritten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: John Ely <john@heyricordo.com>
@crgallego

Copy link
Copy Markdown

Thanks for landing the detector — the structural-boundary check (\\n\\n / \\n- / \\n* / \\n# / ordered list, skipping code regions) is the right scope.

I'd like to invert the polarity from reject to decode.

Fail-closed on this nest would drop ~half of Codex replies (49/100 Codex events still carry literal \\n after #2121; details on #4891). Codex does not rewrite to --content - on retry, so a usage error becomes a silent turn rather than an ugly-but-present message. docs/welcome-kickoff-silent-failures.md is the failure mode that already costs us.

Proposed: same boundary set, but replace those argv escapes with real LF before signing. Leave --content - byte-exact (intentional \\n still has an escape hatch). Isolated \\n such as C:\\new\\tmp stays put.

Opening a sibling PR against #4891 with that decode. Happy to close it if you'd rather retarget this branch — the fixtures here are the right ones, just with the assertion flipped.

@crgallego

Copy link
Copy Markdown

Sibling PR with decode (not reject): #6566

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.

2 participants