fix(cli): reject JSON-escaped newlines before signing - #5114
Conversation
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>
|
Thanks for landing the detector — the structural-boundary check ( 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 Proposed: same boundary set, but replace those argv escapes with real LF before signing. Leave 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. |
|
Sibling PR with decode (not reject): #6566 |
Summary
Messages built by shell wrappers can reach the relay carrying literal
\nescape text instead of real LF bytes, so recipients see\nwhere 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
\ninside inline and fenced code is preserved, because that is legitimate content — a message explainingprintf '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 inbase_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-cliatc92b5b0a: 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\ninside 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.