feat(approvals): detect exec-via-flag escapes on read-only commands (port kilocode#11890)

Flags on otherwise read-only commands that execute an arbitrary program
(sort --compress-program, rg --pre/--hostname-bin, ag --pager,
man -P/--pager/--html) were invisible to approval detection: the flag
value is opaque argument text, so 'sort --compress-program=sh f' ran
without a prompt and a hardline payload smuggled through the flag
('sort --compress-program="rm -rf /" f') bypassed the unconditional
floor entirely.

Two layers, mirroring Kilo-Org/kilocode#11890:
- New DANGEROUS_PATTERNS entries flag the mechanism itself, so the
  command requires approval even when the payload is a script whose
  contents we cannot see.
- _exec_flag_payloads() surfaces each flag's program value as its own
  detection variant in _command_detection_variants(), so hardline
  payloads anchor at command position and hit the floor.

E2E: 11 attack shapes detected, 5 hardline payloads reach the floor,
11 legit commands (rg --pretty, grep -P, pip install --pre, man -k
pager) unflagged. 554 approval-suite tests green.
This commit is contained in:
teknium1 2026-07-06 17:35:48 -07:00
parent 830165473e
commit 2e2a0dfe8b
No known key found for this signature in database
2 changed files with 169 additions and 0 deletions

View file

@ -0,0 +1,100 @@
"""Exec-via-flag escape detection (port of Kilo-Org/kilocode#11890).
Otherwise read-only commands can execute an arbitrary program through a
flag: ``sort --compress-program``, ``rg --pre`` / ``--hostname-bin``,
``ag --pager``, ``man -P`` / ``--pager`` / ``-H`` / ``--html``. Two layers
are under test:
1. The mechanism itself is flagged dangerous (approval required) even when
the payload is opaque (``--pre=sh``).
2. A hardline payload smuggled through the flag value reaches the
unconditional floor via ``_exec_flag_payloads`` detection variants.
"""
import pytest
from tools.approval import (
_exec_flag_payloads,
detect_dangerous_command,
detect_hardline_command,
)
@pytest.mark.parametrize("command", [
"sort --compress-program='rm -rf ~' names.txt",
'sort -S 1b --compress-program "sh" names.txt',
"sort --compress-program=sh names.txt",
"rg --pre sh -e . names.txt",
"rg --pre=sh -e . names.txt",
"rg --hostname-bin=sh pattern",
"ag --pager sh foo",
"ag --pager=sh foo",
"man -P sh ls",
"man -Psh ls",
"man --pager=sh ls",
"man --html=sh ls",
])
def test_exec_via_flag_is_dangerous(command):
is_dangerous, _key, desc = detect_dangerous_command(command)
assert is_dangerous, f"exec-via-flag escape not detected: {command}"
@pytest.mark.parametrize("command", [
# A hardline payload inside the flag value must reach the unconditional
# floor, not just the softer dangerous prompt.
"sort --compress-program='rm -rf --no-preserve-root /' names.txt",
'sort --compress-program "rm -rf --no-preserve-root /" names.txt',
"rg --pre 'rm -rf --no-preserve-root /' -e . x",
"man -P 'rm -rf --no-preserve-root /' ls",
# man -H (browser) is invisible to the lowercased dangerous-pattern
# matcher (would collide with `man -h` help), but the payload extractor
# runs on the original-case command, so a dangerous payload still
# reaches the floor.
"man -H 'rm -rf --no-preserve-root /' ls",
"ag --pager 'rm -rf --no-preserve-root /' foo",
])
def test_hardline_payload_in_exec_flag_hits_floor(command):
is_hardline, desc = detect_hardline_command(command)
assert is_hardline, f"hardline payload escaped the floor: {command}"
assert desc == "recursive delete of root filesystem"
@pytest.mark.parametrize("command", [
"sort names.txt",
"sort -k2 -n data.csv -o out.csv",
"rg 'compress-program' docs/",
"rg -n pattern src/",
"rg --pretty pattern src/", # --pre must not match --pretty
"ag foo src/",
"man ls",
"man -k pager", # -k arg happens to be the word "pager"
"grep -P 'foo(?=bar)' file.txt", # -P is perl-regex on grep, not a pager
"pip install --pre somepackage", # --pre without rg context
"git log --oneline",
])
def test_legit_commands_not_flagged(command):
is_hardline, _ = detect_hardline_command(command)
is_dangerous, _key, desc = detect_dangerous_command(command)
assert not is_hardline, f"legit command hardlined: {command}"
assert not (is_dangerous and "via" in (desc or "")), (
f"legit command flagged as exec-via-flag: {command} ({desc})"
)
def test_exec_flag_payload_extraction():
payloads = list(_exec_flag_payloads(
"sort --compress-program='rm -rf /' a.txt"))
assert payloads == ["rm -rf /"]
payloads = list(_exec_flag_payloads("man -Psh ls"))
assert payloads == ["sh"]
payloads = list(_exec_flag_payloads("rg --pre=sh -e . x"))
assert payloads == ["sh"]
# Long flag with no rg/man/sort context still extracts, but a benign
# payload matches no dangerous pattern (defense-in-depth only).
assert list(_exec_flag_payloads("sort data.txt")) == []
# A flag value that is itself another flag is not a payload.
assert list(_exec_flag_payloads("man --pager --html ls")) == []

View file

@ -612,6 +612,28 @@ DANGEROUS_PATTERNS = [
(rf'\btee\b.*["\']?{_PROJECT_SENSITIVE_WRITE_TARGET}["\']?{_WRITE_TARGET_BOUNDARY}', "overwrite project env/config via tee"),
(rf'>>?\s*["\']?{_PROJECT_SENSITIVE_WRITE_TARGET}["\']?{_WRITE_TARGET_BOUNDARY}', "overwrite project env/config via redirection"),
(r'\bxargs\s+.*\brm\b', "xargs with rm"),
# Exec-via-flag escapes on otherwise read-only commands (port of
# Kilo-Org/kilocode#11890). These flags make an innocuous-looking
# command execute an ARBITRARY PROGRAM: sort spawns its compressor for
# temp runs, rg runs a preprocessor per file, ag/man spawn a pager or
# browser. Without these rules the flag value is opaque argument text,
# so `sort --compress-program='rm -rf ~' f` looked like a plain sort.
# The payload is additionally surfaced to the hardline floor via
# _exec_flag_payloads() in _command_detection_variants below — these
# entries make the *mechanism itself* require approval even when the
# payload points at a script whose contents we cannot see.
(r'\bsort\b[^\n;|&]*--compress-program', "arbitrary program execution via sort --compress-program"),
(r'\brg\b[^\n;|&]*--pre[=\s]', "arbitrary program execution via rg --pre"),
(r'\brg\b[^\n;|&]*--hostname-bin', "arbitrary program execution via rg --hostname-bin"),
(r'\bag\b[^\n;|&]*--pager', "arbitrary program execution via ag --pager"),
# man -P/--pager runs an arbitrary pager program; -H/--html runs an
# arbitrary browser. Detection input is lowercased, so `-p` here also
# covers the rare legit `man -p` (preprocessor list) — an acceptable
# approval-prompt false positive. `(?!-)` keeps `--help`/`--html` long
# flags out of the short-flag branch.
(r'\bman\b[^\n;|&]*\s-(?!-)\w*p', "arbitrary program execution via man pager flag"),
(r'\bman\b[^\n;|&]*\s--pager', "arbitrary program execution via man pager flag"),
(r'\bman\b[^\n;|&]*\s--html', "arbitrary program execution via man --html"),
# find -exec rm / -execdir rm — the -execdir variant (same semantics,
# runs in the directory of each match) was previously missed. Claude
# Code 2.1.113 tightened their equivalent find rule to stop auto-
@ -1349,10 +1371,57 @@ def _iter_shell_command_word_spans(command: str):
break
_EXEC_FLAG_PAYLOAD_RE = re.compile(
r"""(?:--compress-program|--pre|--hostname-bin|--pager|--html)
(?:=|\s+) # =value or separate word
(?P<payload>'[^']*'|"[^"]*"|\S+)""",
re.VERBOSE,
)
# man -P/-H short flags (possibly bundled, e.g. ``man -Psh ls``). Scoped to a
# ``man`` command context: ``-P`` is a common flag elsewhere (``grep -P`` is a
# perl-regex switch) and extracting its argument there would false-positive on
# legitimate searches for dangerous-looking strings.
_MAN_EXEC_FLAG_PAYLOAD_RE = re.compile(
r"""\bman\b[^\n;|&]*?
\s-\w*[PH]
(?:=|\s*) # bundled (-Psh), separate word, or =
(?P<payload>'[^']*'|"[^"]*"|\S+)""",
re.VERBOSE,
)
def _exec_flag_payloads(command: str):
"""Yield the program values passed to exec-via-flag options.
Flags like ``sort --compress-program``, ``rg --pre``, ``ag --pager`` and
``man -P`` make an otherwise read-only command execute an arbitrary
program. The flag value is normally opaque argument text, so a hardline
payload smuggled through it (``sort --compress-program='rm -rf /' f``)
never reached the unconditional floor. Surfacing each payload as its own
detection variant lets the hardline/dangerous patterns match the wrapped
command directly. Port of Kilo-Org/kilocode#11890.
"""
for regex in (_EXEC_FLAG_PAYLOAD_RE, _MAN_EXEC_FLAG_PAYLOAD_RE):
for match in regex.finditer(command):
payload = _strip_optional_shell_quotes(match.group("payload"))
payload = payload.strip()
if payload and not payload.startswith("-"):
yield payload
def _command_detection_variants(command: str):
normalized = _normalize_command_for_detection(command)
seen = {normalized}
yield normalized
# Exec-via-flag payloads: expose the program argument of flags that make
# a read-only command execute an arbitrary program, so a hardline command
# hidden inside (e.g.) --compress-program='rm -rf /' anchors at command
# position and hits the unconditional floor.
for payload in _exec_flag_payloads(normalized):
if payload not in seen:
seen.add(payload)
yield payload
# Subshell `(cmd)` and brace-group `{ cmd; }` openers put `cmd` at a real
# command position, but the flat `_CMDPOS`-anchored patterns can't see it:
# their start-position class deliberately omits `(`/`{` because a bare