Skip to content

Harden subprocess output handling (LOW-1, INFO-1, LOW-2) - #11

Merged
bsg62 merged 9 commits into
mainfrom
worktree-output-sanitization-hardening
Jun 21, 2026
Merged

Harden subprocess output handling (LOW-1, INFO-1, LOW-2)#11
bsg62 merged 9 commits into
mainfrom
worktree-output-sanitization-hardening

Conversation

@bsg62

@bsg62 bsg62 commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

Fixes three security-audit findings in how child-process output is captured and echoed, and DRYs the previously-duplicated capture code in server.rs/command.rs into a single shared src/core/output.rs.

  • LOW-1 — unbounded in-progress line buffer (memory DoS): LineAccumulator force-flushes a line once it reaches MAX_LINE_BYTES (64 KiB), bounding memory when a child emits a long run of bytes with no newline.
  • INFO-1 — UTF-8 corruption across chunk boundaries: raw bytes are buffered and decoded (from_utf8_lossy) only at newline boundaries, so multibyte characters split across 8 KB reads are no longer mangled.
  • LOW-2 — terminal-injection via teed escape sequences: AnsiTeeFilter (a streaming state machine) preserves SGR colour/style (ESC[…m) but strips OSC/DCS/APC/PM/SOS string payloads (window-title, clipboard), cursor-movement, screen/line-clear CSI, overlong CSI, and stray C0 control bytes. The TUI render path was already safe (ratatui renders into cells).

Captured log lines additionally have control characters (except tab) stripped. The README Security Model section documents the new behavior.

Threat model

Config files and the commands they spawn are trusted (documented); the residual risk mitigated here is untrusted data flowing through a trusted server's output (e.g. logged HTTP request data). The 8-bit C1 introducers (0x9b/0x9d) are intentionally passed through — they're indistinguishable from UTF-8 continuation bytes in a byte-wise filter — which only matters on legacy Latin-1 terminals; this is documented in a code comment.

Test Plan

  • cargo test --bins — 52/52 passing (deterministic; includes new core::output unit tests for line capping, UTF-8 boundary decoding, control stripping, and the full ANSI filter matrix)
  • cargo fmt -- --check clean
  • cargo clippy --all-targets -- -D warnings clean
  • preserves_final_command_stdout_and_stderr integration test passes (output forwarding unchanged)
  • Note: tests/cli.rs has pre-existing flaky failures (process-group-kill timing races, "Failed to stop process …") that exist on main and are unrelated to this change

🤖 Generated with Claude Code

bsg62 and others added 9 commits June 21, 2026 19:56
…uences

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the obsolete #![allow(dead_code, unused_imports)] from
output.rs now that all symbols are wired up in server.rs and command.rs.
Add a paragraph to the Security Model section of the README describing
the defensive output-capture and ANSI-filtering behaviour. Also apply
cargo fmt which collapses a stray double-blank-line in command.rs and
updates one test helper in output.rs to use repeat_n (clippy::manual_repeat_n).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bsg62
bsg62 merged commit f865db4 into main Jun 21, 2026
4 checks passed
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