diff --git a/tests/tools/test_hardline_blocklist.py b/tests/tools/test_hardline_blocklist.py index e084e61fe8f..38f9d4d7a84 100644 --- a/tests/tools/test_hardline_blocklist.py +++ b/tests/tools/test_hardline_blocklist.py @@ -150,6 +150,16 @@ _HARDLINE_ALLOW = [ "rm -rf $HOME/tmp", "rm foo.txt", "rm -rf some/path", + # Literal root-level directories that only LOOK like root-collapse + # spellings. Each inter-slash segment must be exactly "." or ".." to + # count as a collapse back to "/" — "/..." is a dir literally named + # "..." and "/.foo" is an ordinary root dotfile. These must NOT be + # swept into the "recursive delete of root filesystem" hardline rule + # (regression guard for the collapse-spelling tightening). + "rm -rf /...", + "rm -rf /....", + "rm -rf /.foo", + "rm -rf /.config/foo", # A dangerous-looking command embedded as a quoted *argument* to another # command must not trip the floor: the path is immediately followed by a # closing quote with no matching opening quote of its own, so the @@ -401,7 +411,8 @@ def test_root_collapse_pattern_leaves_real_paths_alone(clean_session): 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"]: + "rm -rf /.config", "rm -rf ./build", "rm -rf /opt/foo", + "rm -rf /...", "rm -rf /....", "rm -rf /.foo"]: is_hl, _ = detect_hardline_command(cmd) assert not is_hl, f"{cmd!r} must not be hardline-blocked (over-match)" diff --git a/tools/approval.py b/tools/approval.py index 6ad4b59bdf2..e01db823139 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -370,15 +370,16 @@ HARDLINE_PATTERNS = [ # # 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"), + # "."/".." current/parent segments ("/.", "/./", "/..", "/../..") all + # resolve to root, optionally followed by a trailing glob ("/*", "//*"). + # Each inter-slash segment must be exactly "." or "..", so a longer dot + # run or any real name is a literal directory, NOT root — "/tmp", "/home", + # "/.ssh", "/.config" and even "/..." (a dir literally named "...") fall + # through to the softer DANGEROUS_PATTERNS / system-directory rules + # instead of being unconditionally hardline-blocked. The explicit "/ \*" + # alt preserves the slash-space-glob spelling (`rm -rf / *`, which the + # shell sees as two args: "/" plus the "*" glob). + (_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