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:
parent
d15a288812
commit
51feecc2b1
2 changed files with 55 additions and 1 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue