fix(cron): distinguish binary skip from missing file in referenced-script scan - #80164
fix(cron): distinguish binary skip from missing file in referenced-script scan#80164applex250 wants to merge 1 commit into
Conversation
…ript scan _read_referenced_script returns (None, False) both when a referenced file doesn't exist and when it's a binary (NUL bytes). Callers treat None as 'local file missing' and fall back to read_remote_script, which decodes the binary as text and feeds machine-code junk paths back into the recursion — re-triggering the embedded-null-byte crash chain (NousResearch#77703 fixed the os.open ValueError, but the fallback re-entry path remained). Return ('', False) for binaries so callers see 'binary, skip' and never invoke the remote-read fallback on binary content.
|
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
_read_referenced_scriptincron/lifecycle_guard.pyreturns(None, False)for two very different cases:Callers treat
Noneas "local file missing" and fall back toread_remote_script(terminal_tool's_read_script_in_env), which decodes the bytes as UTF-8 text and feeds machine-code junk back into the recursion. That junk gets tokenized into bogus script paths containing embedded NUL bytes — the exact crash chain #77703 fixed at theos.opensite (ValueError: embedded null byte) can still re-enter through the remote-read fallback path.This PR makes the "binary, skip" case return
""instead ofNone, so callers never mistake a binary for a missing file and never invoke the remote-read fallback on binary content.How it was found
Reproduction: a multi-line inline
python3 -ccommand containing an absolute path inside parentheses (e.g.Image.open('/abs/path.png')). The guard's line-based tokenizer mis-parses the unquoted-cpayload, picks the path up as a "referenced script", reads the PNG as binary → skips via the NUL check →None→ remote-read fallback decodes the PNG as text → recursion tokenizes junk →os.open()raisesValueError: embedded null byteand the guard crashes instead of returning a clean block.The upstream
os.openguard from #77703 prevents some of these paths from crashing, but the fallback re-entry makes the crash still reachable — this is the remaining defense-in-depth gap.Changes
cron/lifecycle_guard.py: binary (NUL-byte) files return("", False)— "nothing to scan, and don't bother the remote-read fallback" — instead of(None, False).No behavior change for real shell scripts: text scripts still return their decoded text; missing files still return
None(so normal path validation errors are unaffected).Test plan
python3 -cwith an absolute PNG path inImage.open(...)) no longer crashes the guard — returns a clean result.systemctl restart hermes-gateway,pkill -f hermes-gateway,sh -c 'hermes gateway stop', and shell scripts referencing gateway commands are still blocked.ls -la) and absolute-executable invocations (/usr/bin/python3 script.py) still pass.tests/for the guard run clean.Related: #77703 (same crash chain, different entry point — already fixed upstream).