fix(cron): tolerate NUL-byte path tokens from the command string in lifecycle guard - #77898
fix(cron): tolerate NUL-byte path tokens from the command string in lifecycle guard#77898namredips wants to merge 1 commit into
Conversation
…ifecycle guard NousResearch#76762 taught _read_referenced_script to skip binaries whose contents contain NUL bytes, and to tolerate ValueError at Path.resolve(). But a path token containing a literal NUL can also arrive straight from the command string (e.g. an agent emitting "\x00" inside a JSON-encoded terminal command). os.open() raises ValueError — not OSError — for such a path, which escaped _read_referenced_script's handler and crashed the guard, failing every terminal tool call in the agent turn. A NUL-bearing path can never exist on POSIX and can never reach execve, so treating it as 'nothing to read' is fail-safe. Repro (before this fix): contains_gateway_lifecycle_command_or_referenced_script( "bash /tmp/junk\x00fragment.sh") ValueError: open: embedded null character in path
Follow-up: same root cause has a second symptom this PR doesn't coverI was hit by this crash in production (agent terminal calls failing every turn). After applying this exact Root cause
if script_text is None and read_remote_script is not None:
script_text = read_remote_script(str(script_path))For local backends Fix (mirror the local NUL guard in the remote fallback) if script_text is None and read_remote_script is not None:
# Local path missing; try the remote backend if one is available.
script_text = read_remote_script(str(script_path))
# The remote fallback may return raw binary decoded as text (e.g.
# `cat` of an ELF executable). NUL bytes mean binary — treat as
# "nothing to scan", mirroring the local-path NUL guard above,
# so a binary can't explode the recursion or false-positive.
if script_text and "\x00" in script_text:
script_text = NoneTests
Verified in production after a gateway reload: the previously-blocked command passes, and the real lifecycle guard still fires on actual restart commands. |
|
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. |
Problem
_read_referenced_scriptcatches onlyOSErroraroundos.open(), butos.open()raisesValueErrorfor a path containing an embedded NUL byte. A NUL can arrive in a path token straight from the command string — e.g. an agent emitting\x00inside a JSON-encoded terminal command — and the guard crashes:In production this failed every terminal tool call in the affected agent turn, and agent retry loops multiplied the damage (we measured this driving a runaway token burn on a cron-driven agent before diagnosing it).
Relation to #76762
#76762 fixed the sibling vector — NUL bytes read out of a referenced binary's contents — by skipping binaries in
_read_referenced_scriptand toleratingValueErrorat thePath.resolve()site. But theos.open()call two lines above the binary check still catches onlyOSError, so a NUL-bearing path token that arrives directly from the command string still crashes the guard before the content check can run.Fix
Catch
(OSError, ValueError)at theos.open()site, matching the treatment #76762 gavePath.resolve(). This is fail-safe: a NUL-bearing path can never exist on POSIX and can never reachexecve, so there is no script to scan and nothing for the guard to miss.Testing
Added
test_nul_byte_in_command_string_does_not_crash_guardbeside the existing #76762 regression test. Verified:ValueErrorbefore the fix, passes aftertest_absolute_path_binary_does_not_crash_guardandtest_binary_script_does_not_silently_bypassstill passbash -c "hermes gateway restart"still returnsTrue)