Skip to content

Commit e3d63e1

Browse files
authored
damage-control: hidden input behind same-line openers and wrapper -c (#1144) (#1146)
* damage-control: here-string/heredoc payload behind same-line openers and wrapper -c is judged (#1144) Cut the feed line at its last segment break before stripping openers, and let the -c payload scan step past prefix wrappers. Differential matrix test: every hidden-input form x opener/wrapper == top-level decision. Built by dotdev.dev * damage-control: producer pipe to shell behind wrappers/openers is judged (#1144) Built by dotdev.dev
1 parent 23efdbe commit e3d63e1

8 files changed

Lines changed: 303 additions & 62 deletions

File tree

‎agentwire/hooks/damage-control/bash-tool-damage-control.py‎

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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": "dfd26d6da44441566c101a5c8abc2e2767eb2d03188e93a749eacb60d9978fca", "generated_at": "2026-10-19T11:35:13Z"}
3838
# === END AGENTWIRE HOOK STAMP ===
3939
# === BEGIN GENERATED FROM agentwire/safety/_core.py ===
4040
"""
@@ -4000,6 +4000,25 @@ def _sftp_batch_commands(text: str, consumer_words: List[str]) -> List[str]:
40004000
return out
40014001

40024002

4003+
def _producer_commands(producer: str) -> List[List[str]]:
4004+
"""Word lists of the commands a pipe stage's text may end in, past openers and prefix wrappers.
4005+
4006+
``if t; then sudo cat <<< X`` feeds the pipe from ``cat``: each tail after a segment break
4007+
is stripped of compound openers, then of ``sudo``/``env``/``nohup``/``timeout N``.
4008+
"""
4009+
words = _words(producer)
4010+
tails = [words] + [words[i + 1 :] for i, w in enumerate(words) if w in ("&&", "||", "&") or w.endswith(";")]
4011+
out: List[List[str]] = []
4012+
for tail in tails:
4013+
tail = strip_command_opener_words(tail)
4014+
k = _wrapped_command_index(tail) if tail else None
4015+
if k is not None:
4016+
tail = tail[k:]
4017+
if tail:
4018+
out.append(tail)
4019+
return out
4020+
4021+
40034022
def _stdin_program_commands(command: str, rules: bool = False) -> List[str]:
40044023
"""Text that a stdin-reading runner will execute as commands (#1099).
40054024
@@ -4027,22 +4046,23 @@ def emit(text: str, words: List[str]) -> None:
40274046
visible = _hide_heredoc_bodies(command, keep_programs=True)
40284047
for m in _here_strings(visible): # ``'lua <<< x'`` in a message body is text
40294048
line = visible[visible.rfind("\n", 0, m.start()) + 1 : m.start()]
4030-
words = _feed_words(line)
4049+
cut = [b.end() for b in _SEGMENT_BREAK.finditer(line)] # ``then sh <<< X``: the command after the last break
4050+
words = _feed_words(line[cut[-1] :] if cut else line)
40314051
emit(" ".join(_words(m.group(1))), words)
40324052
if "|" in command:
40334053
stages = _pipe_stages(_hide_heredoc_bodies(command, keep_programs=False))
40344054
for (_, producer), (op, consumer) in zip(stages, stages[1:]):
40354055
consumer_words = _feed_words(consumer)
40364056
if op != "|":
40374057
continue
4038-
words = strip_command_opener_words(_words(producer))
4039-
if words and os.path.basename(words[0]) in ("echo", "printf"):
4040-
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4041-
emit(text, consumer_words)
4042-
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4043-
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4044-
elif words and os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4045-
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
4058+
for words in _producer_commands(producer):
4059+
if os.path.basename(words[0]) in ("echo", "printf"):
4060+
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4061+
emit(text, consumer_words)
4062+
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4063+
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4064+
elif os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4065+
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
40464066
return out
40474067

40484068

@@ -4419,6 +4439,9 @@ def _exec_payload_commands(command: str, rules: bool = False, _depth: int = 0) -
44194439
# A shell's ``-c`` text and an ssh remote command are judged by their own
44204440
# rules where they sit; a hidden command INSIDE them is still hidden.
44214441
k = next((j for j, w in enumerate(words) if not _ASSIGN_TOKEN_RE.match(w)), None)
4442+
if k is not None: # ``sudo|env|nohup|timeout N bash -c '…'``: the wrapped shell's payload is hidden too
4443+
w = _wrapped_command_index(words[k:])
4444+
k = k + w if w is not None else k
44224445
head = words[k].rsplit("/", 1)[-1] if k is not None else ""
44234446
if head in _SHELL_NAMES and head != "su":
44244447
inner, _rest = _eval_shell_invocation(words[k + 1 :], False)

‎agentwire/hooks/damage-control/edit-tool-damage-control.py‎

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def log_disabled(*args, **kwargs): pass
3030

3131

3232
# === BEGIN AGENTWIRE HOOK STAMP (generated — do not edit) ===
33-
AGENTWIRE_HOOK_STAMP = {"core_sha256": "33f11ebb4e4472aedb37164dcb4c5d1010c5472bc516028d9aaa2c12070bdebc", "generated_at": "2026-10-19T00:29:08Z"}
33+
AGENTWIRE_HOOK_STAMP = {"core_sha256": "dfd26d6da44441566c101a5c8abc2e2767eb2d03188e93a749eacb60d9978fca", "generated_at": "2026-10-19T11:35:13Z"}
3434
# === END AGENTWIRE HOOK STAMP ===
3535
# === BEGIN GENERATED FROM agentwire/safety/_core.py ===
3636
"""
@@ -3996,6 +3996,25 @@ def _sftp_batch_commands(text: str, consumer_words: List[str]) -> List[str]:
39963996
return out
39973997

39983998

3999+
def _producer_commands(producer: str) -> List[List[str]]:
4000+
"""Word lists of the commands a pipe stage's text may end in, past openers and prefix wrappers.
4001+
4002+
``if t; then sudo cat <<< X`` feeds the pipe from ``cat``: each tail after a segment break
4003+
is stripped of compound openers, then of ``sudo``/``env``/``nohup``/``timeout N``.
4004+
"""
4005+
words = _words(producer)
4006+
tails = [words] + [words[i + 1 :] for i, w in enumerate(words) if w in ("&&", "||", "&") or w.endswith(";")]
4007+
out: List[List[str]] = []
4008+
for tail in tails:
4009+
tail = strip_command_opener_words(tail)
4010+
k = _wrapped_command_index(tail) if tail else None
4011+
if k is not None:
4012+
tail = tail[k:]
4013+
if tail:
4014+
out.append(tail)
4015+
return out
4016+
4017+
39994018
def _stdin_program_commands(command: str, rules: bool = False) -> List[str]:
40004019
"""Text that a stdin-reading runner will execute as commands (#1099).
40014020
@@ -4023,22 +4042,23 @@ def emit(text: str, words: List[str]) -> None:
40234042
visible = _hide_heredoc_bodies(command, keep_programs=True)
40244043
for m in _here_strings(visible): # ``'lua <<< x'`` in a message body is text
40254044
line = visible[visible.rfind("\n", 0, m.start()) + 1 : m.start()]
4026-
words = _feed_words(line)
4045+
cut = [b.end() for b in _SEGMENT_BREAK.finditer(line)] # ``then sh <<< X``: the command after the last break
4046+
words = _feed_words(line[cut[-1] :] if cut else line)
40274047
emit(" ".join(_words(m.group(1))), words)
40284048
if "|" in command:
40294049
stages = _pipe_stages(_hide_heredoc_bodies(command, keep_programs=False))
40304050
for (_, producer), (op, consumer) in zip(stages, stages[1:]):
40314051
consumer_words = _feed_words(consumer)
40324052
if op != "|":
40334053
continue
4034-
words = strip_command_opener_words(_words(producer))
4035-
if words and os.path.basename(words[0]) in ("echo", "printf"):
4036-
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4037-
emit(text, consumer_words)
4038-
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4039-
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4040-
elif words and os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4041-
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
4054+
for words in _producer_commands(producer):
4055+
if os.path.basename(words[0]) in ("echo", "printf"):
4056+
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4057+
emit(text, consumer_words)
4058+
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4059+
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4060+
elif os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4061+
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
40424062
return out
40434063

40444064

@@ -4415,6 +4435,9 @@ def _exec_payload_commands(command: str, rules: bool = False, _depth: int = 0) -
44154435
# A shell's ``-c`` text and an ssh remote command are judged by their own
44164436
# rules where they sit; a hidden command INSIDE them is still hidden.
44174437
k = next((j for j, w in enumerate(words) if not _ASSIGN_TOKEN_RE.match(w)), None)
4438+
if k is not None: # ``sudo|env|nohup|timeout N bash -c '…'``: the wrapped shell's payload is hidden too
4439+
w = _wrapped_command_index(words[k:])
4440+
k = k + w if w is not None else k
44184441
head = words[k].rsplit("/", 1)[-1] if k is not None else ""
44194442
if head in _SHELL_NAMES and head != "su":
44204443
inner, _rest = _eval_shell_invocation(words[k + 1 :], False)

‎agentwire/hooks/damage-control/mcp-tool-damage-control.py‎

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def log_escape(*args, **kwargs): pass
4949

5050

5151
# === BEGIN AGENTWIRE HOOK STAMP (generated — do not edit) ===
52-
AGENTWIRE_HOOK_STAMP = {"core_sha256": "33f11ebb4e4472aedb37164dcb4c5d1010c5472bc516028d9aaa2c12070bdebc", "generated_at": "2026-10-19T00:29:08Z"}
52+
AGENTWIRE_HOOK_STAMP = {"core_sha256": "dfd26d6da44441566c101a5c8abc2e2767eb2d03188e93a749eacb60d9978fca", "generated_at": "2026-10-19T11:35:13Z"}
5353
# === END AGENTWIRE HOOK STAMP ===
5454
# === BEGIN GENERATED FROM agentwire/safety/_core.py ===
5555
"""
@@ -4015,6 +4015,25 @@ def _sftp_batch_commands(text: str, consumer_words: List[str]) -> List[str]:
40154015
return out
40164016

40174017

4018+
def _producer_commands(producer: str) -> List[List[str]]:
4019+
"""Word lists of the commands a pipe stage's text may end in, past openers and prefix wrappers.
4020+
4021+
``if t; then sudo cat <<< X`` feeds the pipe from ``cat``: each tail after a segment break
4022+
is stripped of compound openers, then of ``sudo``/``env``/``nohup``/``timeout N``.
4023+
"""
4024+
words = _words(producer)
4025+
tails = [words] + [words[i + 1 :] for i, w in enumerate(words) if w in ("&&", "||", "&") or w.endswith(";")]
4026+
out: List[List[str]] = []
4027+
for tail in tails:
4028+
tail = strip_command_opener_words(tail)
4029+
k = _wrapped_command_index(tail) if tail else None
4030+
if k is not None:
4031+
tail = tail[k:]
4032+
if tail:
4033+
out.append(tail)
4034+
return out
4035+
4036+
40184037
def _stdin_program_commands(command: str, rules: bool = False) -> List[str]:
40194038
"""Text that a stdin-reading runner will execute as commands (#1099).
40204039
@@ -4042,22 +4061,23 @@ def emit(text: str, words: List[str]) -> None:
40424061
visible = _hide_heredoc_bodies(command, keep_programs=True)
40434062
for m in _here_strings(visible): # ``'lua <<< x'`` in a message body is text
40444063
line = visible[visible.rfind("\n", 0, m.start()) + 1 : m.start()]
4045-
words = _feed_words(line)
4064+
cut = [b.end() for b in _SEGMENT_BREAK.finditer(line)] # ``then sh <<< X``: the command after the last break
4065+
words = _feed_words(line[cut[-1] :] if cut else line)
40464066
emit(" ".join(_words(m.group(1))), words)
40474067
if "|" in command:
40484068
stages = _pipe_stages(_hide_heredoc_bodies(command, keep_programs=False))
40494069
for (_, producer), (op, consumer) in zip(stages, stages[1:]):
40504070
consumer_words = _feed_words(consumer)
40514071
if op != "|":
40524072
continue
4053-
words = strip_command_opener_words(_words(producer))
4054-
if words and os.path.basename(words[0]) in ("echo", "printf"):
4055-
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4056-
emit(text, consumer_words)
4057-
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4058-
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4059-
elif words and os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4060-
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
4073+
for words in _producer_commands(producer):
4074+
if os.path.basename(words[0]) in ("echo", "printf"):
4075+
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4076+
emit(text, consumer_words)
4077+
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4078+
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4079+
elif os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4080+
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
40614081
return out
40624082

40634083

@@ -4434,6 +4454,9 @@ def _exec_payload_commands(command: str, rules: bool = False, _depth: int = 0) -
44344454
# A shell's ``-c`` text and an ssh remote command are judged by their own
44354455
# rules where they sit; a hidden command INSIDE them is still hidden.
44364456
k = next((j for j, w in enumerate(words) if not _ASSIGN_TOKEN_RE.match(w)), None)
4457+
if k is not None: # ``sudo|env|nohup|timeout N bash -c '…'``: the wrapped shell's payload is hidden too
4458+
w = _wrapped_command_index(words[k:])
4459+
k = k + w if w is not None else k
44374460
head = words[k].rsplit("/", 1)[-1] if k is not None else ""
44384461
if head in _SHELL_NAMES and head != "su":
44394462
inner, _rest = _eval_shell_invocation(words[k + 1 :], False)

‎agentwire/hooks/damage-control/read-tool-damage-control.py‎

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def log_disabled(*args, **kwargs): pass
3636

3737

3838
# === BEGIN AGENTWIRE HOOK STAMP (generated — do not edit) ===
39-
AGENTWIRE_HOOK_STAMP = {"core_sha256": "33f11ebb4e4472aedb37164dcb4c5d1010c5472bc516028d9aaa2c12070bdebc", "generated_at": "2026-10-19T00:29:08Z"}
39+
AGENTWIRE_HOOK_STAMP = {"core_sha256": "dfd26d6da44441566c101a5c8abc2e2767eb2d03188e93a749eacb60d9978fca", "generated_at": "2026-10-19T11:35:13Z"}
4040
# === END AGENTWIRE HOOK STAMP ===
4141
# === BEGIN GENERATED FROM agentwire/safety/_core.py ===
4242
"""
@@ -4002,6 +4002,25 @@ def _sftp_batch_commands(text: str, consumer_words: List[str]) -> List[str]:
40024002
return out
40034003

40044004

4005+
def _producer_commands(producer: str) -> List[List[str]]:
4006+
"""Word lists of the commands a pipe stage's text may end in, past openers and prefix wrappers.
4007+
4008+
``if t; then sudo cat <<< X`` feeds the pipe from ``cat``: each tail after a segment break
4009+
is stripped of compound openers, then of ``sudo``/``env``/``nohup``/``timeout N``.
4010+
"""
4011+
words = _words(producer)
4012+
tails = [words] + [words[i + 1 :] for i, w in enumerate(words) if w in ("&&", "||", "&") or w.endswith(";")]
4013+
out: List[List[str]] = []
4014+
for tail in tails:
4015+
tail = strip_command_opener_words(tail)
4016+
k = _wrapped_command_index(tail) if tail else None
4017+
if k is not None:
4018+
tail = tail[k:]
4019+
if tail:
4020+
out.append(tail)
4021+
return out
4022+
4023+
40054024
def _stdin_program_commands(command: str, rules: bool = False) -> List[str]:
40064025
"""Text that a stdin-reading runner will execute as commands (#1099).
40074026
@@ -4029,22 +4048,23 @@ def emit(text: str, words: List[str]) -> None:
40294048
visible = _hide_heredoc_bodies(command, keep_programs=True)
40304049
for m in _here_strings(visible): # ``'lua <<< x'`` in a message body is text
40314050
line = visible[visible.rfind("\n", 0, m.start()) + 1 : m.start()]
4032-
words = _feed_words(line)
4051+
cut = [b.end() for b in _SEGMENT_BREAK.finditer(line)] # ``then sh <<< X``: the command after the last break
4052+
words = _feed_words(line[cut[-1] :] if cut else line)
40334053
emit(" ".join(_words(m.group(1))), words)
40344054
if "|" in command:
40354055
stages = _pipe_stages(_hide_heredoc_bodies(command, keep_programs=False))
40364056
for (_, producer), (op, consumer) in zip(stages, stages[1:]):
40374057
consumer_words = _feed_words(consumer)
40384058
if op != "|":
40394059
continue
4040-
words = strip_command_opener_words(_words(producer))
4041-
if words and os.path.basename(words[0]) in ("echo", "printf"):
4042-
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4043-
emit(text, consumer_words)
4044-
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4045-
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4046-
elif words and os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4047-
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
4060+
for words in _producer_commands(producer):
4061+
if os.path.basename(words[0]) in ("echo", "printf"):
4062+
text = " ".join(w for w in words[1:] if not re.fullmatch(r"-[neE]+", w))
4063+
emit(text, consumer_words)
4064+
if os.path.basename(words[0]) == "printf" and len(words) > 2 and "%" in words[1]:
4065+
emit(" ".join(words[2:]), consumer_words) # ``printf '%s' X``: X is the text
4066+
elif os.path.basename(words[0]) == "cat" and "<<<" in words[1:-1]:
4067+
emit(words[words.index("<<<") + 1], consumer_words) # ``cat <<< X | sh``
40484068
return out
40494069

40504070

@@ -4421,6 +4441,9 @@ def _exec_payload_commands(command: str, rules: bool = False, _depth: int = 0) -
44214441
# A shell's ``-c`` text and an ssh remote command are judged by their own
44224442
# rules where they sit; a hidden command INSIDE them is still hidden.
44234443
k = next((j for j, w in enumerate(words) if not _ASSIGN_TOKEN_RE.match(w)), None)
4444+
if k is not None: # ``sudo|env|nohup|timeout N bash -c '…'``: the wrapped shell's payload is hidden too
4445+
w = _wrapped_command_index(words[k:])
4446+
k = k + w if w is not None else k
44244447
head = words[k].rsplit("/", 1)[-1] if k is not None else ""
44254448
if head in _SHELL_NAMES and head != "su":
44264449
inner, _rest = _eval_shell_invocation(words[k + 1 :], False)

0 commit comments

Comments
 (0)