fix(core): never crash the terminal guard on NUL-byte paths - #79326
fix(core): never crash the terminal guard on NUL-byte paths#79326Shotflame wants to merge 2 commits into
Conversation
Add TestNulByteSafeGuard to the command-guard suite. A file path containing an embedded NUL byte (e.g. /tmp/foo\x00bar) is fed verbatim by the guard to tirith, which hands it to subprocess.run(); stdlib raises 'ValueError: embedded null byte' for NUL-bearing argv, propagating up through check_all_command_guards and crashing the terminal guard. This test drives the guard through the full (interactive) flow with tirith enabled and a NUL-aware subprocess.run stand-in, then asserts the guard returns a clean verdict instead of crashing. It FAILS (red) against the current code, proving the bug before the fix (issue fix(core) NousResearch#79279).
…sResearch#79279) A command/path containing an embedded NUL byte (\x00) caused tirith's subprocess.run to raise ValueError: embedded null byte, which escaped the guard and crashed every guarded terminal call. POSIX filenames and argv elements are NUL-terminated, so a NUL-bearing command can never be valid. Now reject NUL-bearing commands up front with a clear, user-facing block verdict (rule_id 'nul-byte') and add a ValueError handler at the subprocess boundary as defence-in-depth, mirroring the lifecycle guard fix (NousResearch#76762). The regression test added in d7f16b20a now passes (was RED); all existing command-guard / tirith / approval tests remain green.
|
Closing as superseded by #80258, which fixes this whole bug class architecturally rather than per-callsite: path candidates are sanitized once at the ingestion boundary (NUL/empty/unexpandable tokens rejected before any OS call), text from any Your report and fix targeted a real member of this class — thank you. The per-callsite patches kept leaving sibling frames exposed (#76762 → #77703 → #77780 → #78256 each crashed one frame away from the previous fix), which is why we went with the boundary fix instead of merging the fragments individually. #80258 carries regression tests for the NUL-path, binary-callback, oversized-read, unset-HOME, and walk-crash cases plus an adversarial never-raises sweep. |
What
Make the terminal security guard (
tools/tirith_security.py::check_command_security) total against commands containing embedded NUL bytes. A guarded command must never crash the guard.Why
subprocess.run()raisesValueError: embedded null bytewhen handed an argv element containing\x00(POSIX cannot represent NUL in a filename/argv element). The guard feeds the raw user command verbatim into such a call, so a path like/tmp/foo\x00barused to crash the entire command-security check with an unhandled exception instead of returning a clean verdict.What changed
check_command_securitynow returns ablockverdict withrule_id: nul-byte(severity HIGH) when the command contains an embedded NUL, mirroring the lifecycle-guard fix (terminal tool: lifecycle_guard crashes on absolute-path executables (ValueError: embedded null byte), blocks all such commands #76762).except ValueErrorat the subprocess boundary converts any NUL-bearing argv that slips through into the sameblock/nul-byteverdict instead of letting the exception escape.TestNulByteSafeGuardintests/tools/test_command_guards.pydrives the full interactive guard flow with a NUL-awaresubprocess.runstand-in and asserts the guard returns a clean verdict rather than crashing.Verification
ValueError: embedded null byteescapes the guard.allow,/tmp/foo\x00bar→block/nul-byte, no crash.Complements #79279 which covers the sibling crash sites in
cron/lifecycle_guard.py+tools/terminal_tool.py.