@@ -34,7 +34,7 @@ def log_escape(*args, **kwargs): pass
3434
3535
3636# === BEGIN AGENTWIRE HOOK STAMP (generated — do not edit) ===
37- AGENTWIRE_HOOK_STAMP = {"core_sha256" : "33f11ebb4e4472aedb37164dcb4c5d1010c5472bc516028d9aaa2c12070bdebc " , "generated_at" : "2026-10-19T00:29:08Z " }
37+ AGENTWIRE_HOOK_STAMP = {"core_sha256" : "6053126c348d26b19caf090c3db81414833fcb056f8b1181debb0c96a0a6e614 " , "generated_at" : "2026-10-19T11:24:11Z " }
3838# === END AGENTWIRE HOOK STAMP ===
3939# === BEGIN GENERATED FROM agentwire/safety/_core.py ===
4040"""
@@ -4973,28 +4973,52 @@ def _realpath_variants(command: str) -> List[str]:
49734973 """
49744974 out : List [str ] = []
49754975 seen = set ()
4976- home = os .path .expanduser ("~" )
49774976 for m in itertools .islice (_PATH_WORD .finditer (command ), _REALPATH_WORD_CAP ):
49784977 word = m .group (0 )
49794978 if word in seen or len (word ) < 2 :
49804979 continue
49814980 seen .add (word )
4982- path = os .path .expanduser (word )
4983- try :
4984- if "/" not in path and not os .path .lexists (path ):
4985- continue
4986- real = os .path .realpath (path )
4987- target = real if real != posixpath .normpath (os .path .abspath (path )) and _under_protected (real ) else None
4988- if target is None and os .path .isfile (path ) and os .stat (path ).st_nlink > 1 :
4989- st = os .stat (path )
4990- target = next ((p for p , ps in _protected_inodes (home ) if os .path .samestat (st , ps )), None )
4991- except (OSError , ValueError ):
4992- continue
4993- if target and target != path :
4981+ target = _alias_target (os .path .expanduser (word ))
4982+ if target and target != os .path .expanduser (word ):
49944983 out .append (command .replace (word , _quote_word (target )))
49954984 return out
49964985
49974986
4987+ def _alias_target (path : str ) -> Optional [str ]:
4988+ """Where ``path`` really lands, when that is a protected file or directory it is only an alias of; else None."""
4989+ try :
4990+ if "/" not in path and not os .path .lexists (path ):
4991+ return None
4992+ real = os .path .realpath (path )
4993+ if real != posixpath .normpath (os .path .abspath (path )) and _under_protected (real ):
4994+ return real
4995+ if os .path .isfile (path ) and os .stat (path ).st_nlink > 1 :
4996+ st = os .stat (path )
4997+ return next ((p for p , ps in _protected_inodes (os .path .expanduser ("~" )) if os .path .samestat (st , ps )), None )
4998+ except (OSError , ValueError ):
4999+ pass
5000+ return None
5001+
5002+
5003+ def _cwd_realpath_variants (command : str ) -> List [str ]:
5004+ """``_realpath_variants`` for a relative word after a literal ``cd <dir> &&`` (#1065).
5005+
5006+ ``cd /tmp/d && echo x > lnk`` names no path until the cwd is applied, and
5007+ ``_cwd_variants`` only rewrites words that are lexically protected. The
5008+ aliased word is resolved against the same cwd and judged as what it is.
5009+ """
5010+ out : List [str ] = []
5011+ for cwd , rest in _cwd_scopes (command )[0 ]:
5012+ for m in itertools .islice (_PATH_WORD .finditer (rest ), _REALPATH_WORD_CAP ):
5013+ word = m .group (0 )
5014+ if word .startswith (("/" , "~" , "-" )) or len (word ) < 2 :
5015+ continue
5016+ target = _alias_target (posixpath .normpath (posixpath .join (cwd , word )))
5017+ if target :
5018+ out .append (rest .replace (word , _quote_word (target )))
5019+ return out
5020+
5021+
49985022def _cwd_variants (command : str ) -> List [str ]:
49995023 """Relative operands resolved against a literal ``cd <dir> &&`` / ``git -C <dir>`` (#1101).
50005024
@@ -5046,6 +5070,57 @@ def _named_control_plane_files() -> List[Tuple[str, "frozenset[str]"]]:
50465070 return [(d , frozenset (names )) for d , names in sorted (by_dir .items ())]
50475071
50485072
5073+ _INLINE_INTERPRETER_RE = _rx (r'\b' + _INTERPRETERS + r'\b[^\n|;&]*?\s-[A-Za-z]*[ceE]\b' )
5074+ # Words the program text uses for a control-plane directory or file, even when the
5075+ # path itself is assembled at run time (``'damage'+'control.yml'``, ``join(h, '.claude')``).
5076+ _CONTROL_PLANE_WORDS = (".agentwire" , ".claude" , "damagecontrol" , "settings.json" )
5077+ # A call that writes, deletes, or runs something, whatever it is handed…
5078+ _INLINE_SINK_RE = _rx (
5079+ r'\b(?:system|popen|subprocess|Popen|check_call|check_output|exec\w*|spawn\w*|write\w*'
5080+ r'|append\w*|unlink|rmtree|rename|replace|copy\w*|move|chmod|remove)\b'
5081+ )
5082+ # …and ``open``, which only writes in a write mode.
5083+ _INLINE_OPEN_RE = _rx (r'\b(?:open|Path|File|IO)\s*[.(]' )
5084+ _INLINE_WRITE_MODE_RE = _rx (r'''['"](?:[wax][bt+]*|r\+[bt]*|\s*\+?>{1,2}[^'"]*)['"]''' )
5085+ # The first argument of a sink that is not one plain string literal: a run-time path.
5086+ _INLINE_NONLITERAL_ARG_RE = _rx (
5087+ r'''\b(?:open|Path|write\w*|append\w*|system|popen|unlink|remove|rmtree|rename|replace|copy\w*|move'''
5088+ r'''|chmod|exec\w*|spawn\w*|run|call|check_call|check_output|Popen|join)\s*\(\s*'''
5089+ r'''(?!(['"])[^'"]*\1\s*[,)])'''
5090+ )
5091+
5092+
5093+ def _opaque_inline_write (command : str ) -> Optional [str ]:
5094+ """Why an inline interpreter program needs a human: it writes to a path it computes, and names the control plane (#1065).
5095+
5096+ ``python3 -c "open(os.path.join(h, '.agentwire', 'damage'+'control.yml'), 'w')"``
5097+ assembles its target at run time — no text match can read it, and no spelling
5098+ list ever will. The unknowable SHAPE is what is refused: an inline program
5099+ (``-c`` / ``-e``) whose call sinks take a non-literal first argument AND that
5100+ mentions a control-plane directory or file name. A literal path is the verb
5101+ rules' business; a program that never names the control plane, or only reads
5102+ (``open(p)`` with no write mode), is untouched.
5103+ """
5104+ if "<<" in command : # a heredoc body is data (prose, a script): only an inline ``-c`` argument is the program
5105+ command = _hide_heredoc_bodies (command , keep_programs = False )
5106+ m = _INLINE_INTERPRETER_RE .search (command )
5107+ if not m :
5108+ return None
5109+ program = command [m .end ():]
5110+ folded = program .casefold ()
5111+ if not any (w in folded for w in _CONTROL_PLANE_WORDS ):
5112+ return None
5113+ sink = _INLINE_SINK_RE .search (program ) or (
5114+ _INLINE_OPEN_RE .search (program ) and _INLINE_WRITE_MODE_RE .search (program )
5115+ )
5116+ if sink and _INLINE_NONLITERAL_ARG_RE .search (program ):
5117+ return (
5118+ "an inline interpreter program writes to a path it computes at run time "
5119+ "and names the control plane"
5120+ )
5121+ return None
5122+
5123+
50495124def unverifiable_target_verdict (
50505125 command : str , allowed_paths : List [Dict [str , Any ]], depth : int = 0
50515126) -> Optional [str ]:
@@ -5068,6 +5143,9 @@ def unverifiable_target_verdict(
50685143 return reason
50695144 if "{" in command and mentions_control_plane (command ) and brace_variants (canonicalize_command (command )) is None :
50705145 return "a brace expansion past the cap cannot be enumerated"
5146+ opaque = _opaque_inline_write (command )
5147+ if opaque :
5148+ return opaque
50715149 known , unknown = _cwd_scopes (command )
50725150 for cwd , rest in known :
50735151 if cwd in _protected_dirs () or cwd == os .path .expanduser ("~" ):
@@ -5132,6 +5210,7 @@ def _derived_commands(command: str) -> List[str]:
51325210 + _glob_variants (visible )
51335211 + _cwd_variants (visible )
51345212 + _realpath_variants (visible )
5213+ + _cwd_realpath_variants (visible )
51355214 )
51365215 return list (dict .fromkeys (derived ))[:_RESCAN_FANOUT ]
51375216
@@ -8477,11 +8556,41 @@ def hard_exit(code: int) -> None:
84778556 os ._exit (code )
84788557
84798558
8480- def run_hook_guarded (main : Any ) -> None :
8481- """Run a hook ``main``; any unexpected exception becomes a blocking exit 2."""
8559+ HOOK_WATCHDOG_SECONDS = 20 # past every scan deadline, well inside HOOK_TIMEOUT_SECONDS
8560+
8561+
8562+ def _watchdog_fire () -> None :
8563+ """The hook outlived its budget: block. Runs on the watchdog thread, so it exits the PROCESS."""
8564+ hard_exit (_block_exit (f"the hook exceeded its { HOOK_WATCHDOG_SECONDS } s budget" ))
8565+
8566+
8567+ def run_hook_guarded (main : Any , deadline_seconds : Optional [float ] = None ) -> None :
8568+ """Run a hook ``main``; any unexpected exception becomes a blocking exit 2.
8569+
8570+ The harness runs a hook that outlives its timeout as NO DECISION, so a hook
8571+ must answer inside it. Two bounds, independent of each other:
8572+
8573+ * ``deadline_seconds`` — the SIGALRM scan deadline (:func:`scan_deadline`)
8574+ around all of ``main``, for the hooks that do not carry their own per-scan
8575+ deadline (edit/write/read/mcp; the bash hook's ``check_command`` does).
8576+ The signal reaches a pattern stuck inside the regex engine, which checks
8577+ for signals; it needs POSIX and the main thread.
8578+ * a watchdog thread that exits the process with a block at
8579+ ``HOOK_WATCHDOG_SECONDS``. It needs neither SIGALRM nor the main thread, so
8580+ it covers Windows and any wait that releases the GIL (a hung ``stat`` /
8581+ ``realpath`` on a network mount). It cannot fire while one C call holds the
8582+ GIL for good (a runaway regex) — that is what the SIGALRM bound is for.
8583+ """
8584+ watchdog = threading .Timer (HOOK_WATCHDOG_SECONDS , _watchdog_fire )
8585+ watchdog .daemon = True
8586+ watchdog .start ()
84828587 code = 0
84838588 try :
8484- main ()
8589+ if deadline_seconds is None :
8590+ main ()
8591+ else :
8592+ with scan_deadline (deadline_seconds ):
8593+ main ()
84858594 except SystemExit as e :
84868595 code = e .code if isinstance (e .code , int ) else (0 if e .code is None else 2 )
84878596 except HookInputError as e :
@@ -8492,6 +8601,7 @@ def run_hook_guarded(main: Any) -> None:
84928601 code = _block_exit ("input nested too deeply to analyze safely" )
84938602 except BaseException as e : # KeyboardInterrupt included: never exit 1
84948603 code = _block_exit (f"the hook failed ({ type (e ).__name__ } : { e } )" )
8604+ watchdog .cancel ()
84958605 hard_exit (code )
84968606
84978607
0 commit comments