Skip to content

Commit 3f34f30

Browse files
BradGrouxBrad Groux
authored andcommitted
Fix NUL byte validation comment and error guidance
The doc comment incorrectly claimed truncation occurs in relay JSON serialization. Rust strings and JSON can represent NUL — the real boundary is the OS command line on Windows, where a NUL terminates the native command line before buzz.exe starts, so Clap never sees the NUL. The error message recommended --content - (stdin), but validation runs after read_or_stdin, so stdin content hits the same check. The actionable workaround is a PowerShell literal here-string (single-quoted) piped through stdin, which avoids backtick expansion entirely. Add a help-contract regression test that verifies the error mentions stdin and does not recommend --content -. Add a PowerShell here-string example to the CLI README. Addresses themiguelamador's review feedback. Co-authored-by: Brad Groux <brad@digitalmeld.com> Signed-off-by: Brad Groux <brad@digitalmeld.com>
1 parent bfb2193 commit 3f34f30

2 files changed

Lines changed: 31 additions & 7 deletions

File tree

crates/buzz-cli/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ export BUZZ_RELAY_URL="https://relay.example.com"
3232
buzz messages send --channel <uuid> --content "Hello"
3333
buzz messages send --channel <uuid> --content "Reply" --reply-to <event-id> --broadcast
3434
buzz messages send --channel <uuid> --content - < message.md # read body from stdin
35+
36+
# On Windows, use a PowerShell literal here-string to avoid backtick expansion:
37+
# buzz messages send --channel <uuid> --content - <<'EOF'
38+
# Content with `backticks` and $variables — preserved literally.
39+
# EOF
3540
buzz messages get --channel <uuid> --limit 20
3641
buzz messages thread --channel <uuid> --event <event-id>
3742
buzz messages thread --link 'buzz://message?channel=<uuid>&id=<event-id>&thread=<root-id>'

crates/buzz-cli/src/validate.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,22 @@ pub fn validate_content_size(content: &str) -> Result<(), CliError> {
7474

7575
/// Reject content containing NUL bytes (0x00).
7676
///
77-
/// On Windows, a NUL byte inside a `--content` argument (e.g. from PowerShell
78-
/// expandable here-string `` `0 ``) silently truncates the string at the
79-
/// C-string boundary when it crosses into the relay's JSON serialization,
80-
/// but the CLI still returns `accepted:true`. Rejecting it here turns silent
81-
/// data loss into a hard error (#5916).
77+
/// Rust strings and JSON can represent NUL, so this is not a Rust-level
78+
/// truncation. The real boundary is the OS command line: on Windows, a NUL
79+
/// byte in `--content` terminates the native command line before `buzz.exe`
80+
/// starts, so Clap receives only the prefix and the validator never sees the
81+
/// NUL. This validator is a defense for NULs that reach Buzz through other
82+
/// paths (stdin, file, or platforms where the NUL survives into the process).
83+
///
84+
/// The actionable workaround for the Windows `--content` case is to use a
85+
/// PowerShell literal here-string (single-quoted, so backtick escapes do not
86+
/// expand) and pipe it through stdin.
8287
pub fn validate_no_nul_bytes(content: &str) -> Result<(), CliError> {
8388
if content.bytes().any(|b| b == 0) {
8489
return Err(CliError::Usage(
85-
"content contains a NUL byte (0x00), which would be silently truncated — \
86-
remove the NUL byte or use stdin (`--content -`) to pass the full content"
90+
"content contains a NUL byte (0x00), which can cause silent truncation — \
91+
remove the NUL byte. On Windows, pass content via stdin using a \
92+
PowerShell literal here-string (single-quoted) to avoid backtick expansion"
8793
.to_string(),
8894
));
8995
}
@@ -326,6 +332,19 @@ mod tests {
326332
assert!(matches!(err, CliError::Usage(_)));
327333
}
328334

335+
#[test]
336+
fn validate_no_nul_bytes_error_mentions_stdin_workaround() {
337+
// The error must steer the operator toward stdin as the workaround,
338+
// not toward --content (which would hit the same validator).
339+
let err = validate_no_nul_bytes("hello\0world").unwrap_err();
340+
let msg = err.to_string();
341+
assert!(msg.contains("stdin"), "error should mention stdin: {msg}");
342+
assert!(
343+
!msg.contains("--content -"),
344+
"error should not recommend --content - (it hits the same validator): {msg}"
345+
);
346+
}
347+
329348
// --- percent_encode ---
330349

331350
#[test]

0 commit comments

Comments
 (0)