fix(tui): a copied NUL byte no longer panics the TUI on Windows - #876
Conversation
syscall.StringToUTF16 PANICS rather than returning an error when the string contains a NUL — documented standard-library behaviour — and github.com/atotto/clipboard's Windows path calls it directly. So one NUL anywhere in a copied transcript selection killed the whole program: "zero: tui error: program was killed: program experienced a panic", reported from a real Windows machine. The NUL reaches the clipboard because ansi.Strip removes escape SEQUENCES and leaves C0 bytes untouched, and the transcript carries them from two independent directions: tool output (a binary read, git's -z NUL-separated listings — which this repo itself parses in files_git_sweep.go) and Zero's own card protocol, whose row prefixes are literally "\x00command-card\x00" and "\x00plan-card\x00". Sanitised at the WRITE rather than at each source, because the sources are unbounded: any tool result a user can select from is one, and a fix per source is a fix the next tool re-breaks. The same pass protects the OSC52 fallback, where a stray control byte terminates the escape sequence early and silently corrupts the copy instead of crashing — which is likely why this went unnoticed off Windows, where the identical NUL produces a bad copy rather than a dead process. \n and \t survive: a multi-line selection is the normal case and both are legal clipboard content. Fixes Gitlawb#875
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughClipboard selections now remove unsafe control characters before native clipboard and OSC52 writes. New tests cover NUL bytes from transcript sources and verify that readable content, newlines, and tabs remain intact. ChangesClipboard safety
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Approving. Good diagnosis and the right place to fix it.
I checked the structural question, which is whether anything can still reach a clipboard write unsanitised: three callers of copyTranscriptSelectionCmd (composer.go:253, model.go:4741, transcript_selection.go:1458), both write sites inside it, and the sanitise happens before the closure so the OSC52 fallback is covered too. Nothing bypasses it. Stripping at the write rather than per source is the correct call, and your reasoning for it is the argument I would have made.
One nit, and it is the tests rather than the fix. They exercise clipboardSafeText directly, so they pin the helper and not the wiring. I deleted the text = clipboardSafeText(text) line at the call site and both tests still passed, which means a later refactor can drop it and reintroduce the panic with CI green. Worth a seam that lets a test drive copyTranscriptSelectionCmd and assert no NUL reaches the native write, even something as small as taking the clipboard writer as a parameter.
Not blocking, since I verified the wiring by hand this time. But this exact shape has bitten us three times recently, most memorably #866, which shipped as a complete no-op with six green tests because they all called the helper instead of the path.
Minor question rather than a request: \r is stripped along with the other C0 bytes. On Windows the clipboard convention is CRLF, so if any transcript row carries CRLF a paste will arrive LF-only. Probably irrelevant since the rows look LF-native, but you are closer to that code than me.
|
@jatmn this one needs a third approval and it is small. It is a crash fix: a copied NUL takes the whole TUI down on Windows, because I approved it, and I checked the coverage question by hand rather than reading the diff alone: three callers of One nit is already on the PR so you do not spend time rediscovering it: the tests exercise |
Fixes #875
What was wrong
syscall.StringToUTF16panics rather than returning an error when handed a string containing a NUL — documented standard-library behaviour — andgithub.com/atotto/clipboard's Windows path calls it directly. One NUL anywhere in a copied transcript selection therefore killed the whole program:The NUL gets through because
ansi.Stripremoves escape sequences and leaves C0 bytes untouched. The transcript carries NULs from two independent directions:git's-zNUL-separated listings, which this repo itself parses infiles_git_sweep.go:134"\x00command-card\x00"and"\x00plan-card\x00"The fix
Sanitise once at the write, not per source. The sources are unbounded — any tool result a user can select from is one — so a fix per source is a fix the next tool re-breaks.
clipboardSafeTextdrops C0 controls (keeping\nand\t, which are legal clipboard content and the normal case for a multi-line selection), applied before both destinations.The OSC52 fallback needed it too: a stray control byte terminates the escape sequence early and silently corrupts the copy. That is very likely why this went unnoticed off Windows — the identical NUL produces a bad copy there rather than a dead process.
Verification
TestNulNeverReachesTheClipboard— four real sources (command-card prefix, plan-card prefix, binary tool output,git -zlisting); asserts no NUL survives and that readable content is not dropped, so this sanitises rather than truncates.TestClipboardSanitisingKeepsLayout— clean multi-line text with tabs is returned byte-identical; other C0 bytes are removed.clipboardSafeTextcall makes the panic-guard test fail.make fmt-check,go vet ./...,go test ./..., release build + smoke,git diff --checkall clean.GOOS=windows go build ./...andGOOS=windows go vetclean.Two pre-existing failures on
mainare unrelated and unaffected:TestRunDoctorFormatsRedactedProviderDiagnosticsandTestRunDoctorConnectivityProbesProviderfail identically with this change stashed.I could not execute the Windows path itself (no Windows machine); the crash mechanism is pinned by the standard library's own documented contract and the reported stack, and the sanitiser is proven by the tests above.
Summary by CodeRabbit