fix(security): block shell-collapse rm -rf / spellings at the hardline floor

rm -rf //, /., /./, /.. and //* all resolve to / in the shell but slipped
past the root-filesystem hardline pattern, whose target group only matched
the literal / and /* tokens. They fell to the softer DANGEROUS_PATTERNS
'delete in root path' rule, which --yolo / approvals.mode=off / cron
approve-mode are designed to bypass — leaving the one unconditional floor
open to a full root wipe under yolo.

Broaden the root token from '/|/\\*|/ \\*' to '/[/.]*\\**' inside
_hardline_rm_path so any root-anchored path whose components collapse back
to / (repeated slashes plus ./.. segments) with an optional trailing glob
is caught. A trailing real segment (/tmp, /home, /.ssh) still fails to
match and stays with the softer rules.

Co-authored-by: kernel-t1 <214165399+kernel-t1@users.noreply.github.com>
This commit is contained in:
teknium1 2026-07-01 02:29:09 -07:00 committed by Teknium
parent d15a288812
commit 51feecc2b1
2 changed files with 55 additions and 1 deletions

View file

@ -31,6 +31,18 @@ _HARDLINE_BLOCK = [
# rm -rf targeting root / system dirs / home
"rm -rf /",
"rm -rf /*",
# Shell-equivalent spellings of "rm -rf /": repeated slashes and
# current/parent-dir segments all collapse back to root, so they must
# hit the hardline floor too (regression: these used to slip through the
# root pattern's target group and fall to the softer DANGEROUS_PATTERNS
# rule, which --yolo / approvals.mode=off / cron approve-mode bypass).
"rm -rf //",
"rm -rf /.",
"rm -rf /./",
"rm -rf /..",
"rm -rf //*",
"rm -fr /./",
"ls && rm -rf //",
"rm -rf /home",
"rm -rf /home/*",
"rm -rf /etc",
@ -329,6 +341,37 @@ def test_yolo_env_var_cannot_bypass_hardline(clean_session, monkeypatch):
assert r2.get("hardline") is True
def test_root_collapse_forms_cannot_bypass_hardline(clean_session, monkeypatch):
"""Shell-equivalent spellings of "rm -rf /" stay blocked under yolo.
"//", "/.", "/./", "/..", "//*" all collapse to the root filesystem in
the shell. They previously matched only the softer DANGEROUS_PATTERNS
rule, which yolo bypasses leaving the hardline floor open to a full
root wipe under --yolo / approvals.mode=off / cron approve-mode.
"""
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
for cmd in ["rm -rf //", "rm -rf /.", "rm -rf /./", "rm -rf /..", "rm -rf //*"]:
is_hl, _ = detect_hardline_command(cmd)
assert is_hl, f"{cmd!r} should be hardline-blocked"
result = check_all_command_guards(cmd, "local")
assert result["approved"] is False, f"yolo leaked hardline on {cmd!r}"
assert result.get("hardline") is True
def test_root_collapse_pattern_leaves_real_paths_alone(clean_session):
"""The broadened root token must not over-match real trailing segments.
A path with a real component after the root-collapse prefix (/tmp,
/home/user/x, /.ssh, ./build) is recoverable-or-legitimate and must NOT
be pulled onto the hardline floor by the "collapse to /" broadening.
"""
for cmd in ["rm -rf /tmp", "rm -rf /home/user/x", "rm -rf /.ssh",
"rm -rf /.config", "rm -rf ./build", "rm -rf /opt/foo"]:
is_hl, _ = detect_hardline_command(cmd)
assert not is_hl, f"{cmd!r} must not be hardline-blocked (over-match)"
def test_line_continuation_root_wipe_cannot_bypass_hardline(clean_session, monkeypatch):
"""A line-continuation root wipe must stay blocked even under yolo.

View file

@ -367,7 +367,18 @@ HARDLINE_PATTERNS = [
# `${HOME}` brace form and quoted paths (`rm -rf "/"`, `rm -rf "$HOME"`)
# are handled via _hardline_rm_path so the floor cannot be bypassed with
# the ordinary quoting/brace shell idioms.
(_RM_FLAG_PREFIX + _hardline_rm_path(r'/|/\*|/ \*'), "recursive delete of root filesystem"),
#
# The path token matches any root-anchored path whose components collapse
# back to "/" in the shell: a bare "/", repeated slashes ("//"), and
# "."/".." current/parent segments ("/.", "/./", "/..") all resolve to
# root, optionally followed by a trailing glob ("/*", "//*"). The earlier
# "/|/\*|/ \*" form only caught the literal "/" / "/*" spellings, so
# `rm -rf //`, `rm -rf /.`, `rm -rf /./`, `rm -rf /..` and `rm -rf //*`
# silently slipped the hardline floor and executed under --yolo /
# approvals.mode=off / cron approve-mode. A trailing real segment
# (e.g. "/tmp", "/home", "/.ssh") still fails to match here and stays
# with the softer DANGEROUS_PATTERNS / system-directory rules.
(_RM_FLAG_PREFIX + _hardline_rm_path(r'/[/.]*\**'), "recursive delete of root filesystem"),
(_RM_FLAG_PREFIX + _hardline_rm_path(_HARDLINE_SYSTEM_DIRS), "recursive delete of system directory"),
(_RM_FLAG_PREFIX + _hardline_rm_path(r'(?:~|\$\{?HOME\}?)(?:/?|/\*)?'), "recursive delete of home directory"),
# Filesystem format