fix: make lifecycle guard total against NUL-byte paths - #77894
fix: make lifecycle guard total against NUL-byte paths#77894luckrucksack wants to merge 1 commit into
Conversation
The NousResearch#76762 fix covered Path.resolve() only; sibling sites (os.open, script-directory resolve) still raised ValueError: embedded null byte when a referenced binary was decoded as text and re-tokenized. The terminal tool's remote-script fallback resurrects binaries as decoded text, feeding machine code into the scan. - tolerate ValueError at every path-resolution site in the guard walk - fail closed with a size bound on referenced-script reads - reject NUL bytes in the terminal tool's script reader (local+remote) - regression tests for all three crash sites (red before, green after)
|
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. |
Summary
The gateway lifecycle guard (
cron/lifecycle_guard.py) can crash withValueError: embedded null bytewhen a terminal command references a binary (e.g..venv/bin/python). Issue class #76762 was previously fixed at one crash site only —Path.resolve()— while sibling sites (os.open, script-directory resolution) still raised. A security guard must be a total function: every input maps to a decision, none raises.Root cause
/-containing executable as a "referenced script" and scans it._read_referenced_scriptcorrectly detects binaries and skips them locally — but the caller then fires a remote fallback (tools/terminal_tool.py→cat <binary>), which succeeds on binaries and decodes the raw bytes as UTF-8 with NUL bytes intact.os.open→ uncaughtValueError. The terminal tool: lifecycle_guard crashes on absolute-path executables (ValueError: embedded null byte), blocks all such commands #76762 fix coveredresolve()but missed this sibling (the incomplete-fix pattern).Two real consequences: any command referencing a binary can crash the guard (observed in production, crashing the terminal tool on ordinary commands), and decoded binary content can contain literal lifecycle-command strings (Python binaries carry docstrings) → false-positive blocking of innocent commands.
Fix
ValueErrorat every path-resolution site in the guard's referenced-script walk (in addition to the existingOSErrorhandling).Testing
tests/hermes_cli/test_gateway_restart_loop.py; all failed pre-fix with the exact bug signatures (red proven), pass post-fix.origin/main.