Skip to content

Commit 955787b

Browse files
committed
feat: enhance safety manager to block absolute-path deletions in various contexts
1 parent b4d3512 commit 955787b

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

libs/safety_manager.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,13 @@ class ExecutionSafetyManager:
6262

6363
DANGEROUS_PATTERNS = [
6464
(r"\brm\s+-rf\b", "Recursive deletion is blocked."),
65+
(r"\brm\s+/", "Absolute-path deletion is blocked."),
6566
(r"\bdel\s+/(?:f|q|s)", "Destructive delete command is blocked."),
67+
(r"\bdel\s+[A-Za-z]:(?:\\\\|/)", "Absolute-path deletion is blocked."),
6668
(r"\brmdir\s+/(?:s|q)", "Recursive directory removal is blocked."),
6769
(r"\brd\s+/s\s+/q\b", "Recursive directory removal is blocked."),
6870
(r"Remove-Item\s+.+-Recurse", "Recursive PowerShell deletion is blocked."),
71+
(r"Remove-Item\s+[\"'](?:[A-Za-z]:\\\\|/)", "Deleting absolute-path items in PowerShell is blocked."),
6972
(r"\bformat\s+[a-z]:", "Disk formatting is blocked."),
7073
(r"\bmkfs\b", "Filesystem formatting is blocked."),
7174
(r"\bshutdown\b", "System shutdown commands are blocked."),
@@ -75,8 +78,24 @@ class ExecutionSafetyManager:
7578
(r"\bcipher\s+/w\b", "Secure wipe commands are blocked."),
7679
(r"\bdiskpart\b", "Disk management commands are blocked."),
7780
(r"shutil\.rmtree\s*\(", "Recursive directory deletion in code is blocked."),
81+
# Block direct absolute-path deletes.
7882
(r"os\.remove\s*\(\s*[\"'](?:[A-Za-z]:\\\\|/)", "Deleting absolute-path files is blocked."),
7983
(r"os\.rmdir\s*\(\s*[\"'](?:[A-Za-z]:\\\\|/)", "Removing absolute-path directories is blocked."),
84+
# Block absolute-path deletes when the path is constructed via os.path.join().
85+
(r"os\.remove\s*\(\s*os\.path\.join\s*\(\s*[\"'](?:[A-Za-z]:\\\\|/)", "Deleting absolute-path files is blocked."),
86+
(r"os\.rmdir\s*\(\s*os\.path\.join\s*\(\s*[\"'](?:[A-Za-z]:\\\\|/)", "Removing absolute-path directories is blocked."),
87+
(r"shutil\.rmtree\s*\(\s*os\.path\.join\s*\(\s*[\"'](?:[A-Za-z]:\\\\|/)", "Recursive directory deletion in code is blocked."),
88+
# Catch absolute-path string literals anywhere inside delete function calls.
89+
(r"os\.remove\s*\(\s*[^)]*[\"'](?:[A-Za-z]:\\\\|/)", "Deleting absolute-path files is blocked."),
90+
(r"os\.rmdir\s*\(\s*[^)]*[\"'](?:[A-Za-z]:\\\\|/)", "Removing absolute-path directories is blocked."),
91+
# Node.js filesystem deletions on absolute paths:
92+
# In practice we see patterns like:
93+
# const directory = 'D:\\Temp';
94+
# const filePath = path.join(directory, file);
95+
# fs.unlinkSync(filePath);
96+
# The absolute path isn't inside unlinkSync(...), so we match both in the same script.
97+
(r"(?s)(?=.*fs\.(?:unlinkSync|rmSync))(?=.*[`'\"][A-Za-z]:[\\\\/])", "Deleting absolute-path files is blocked."),
98+
(r"(?s)(?=.*fs\.rmdirSync)(?=.*[`'\"][A-Za-z]:[\\\\/])", "Removing absolute-path directories is blocked."),
8099
(r"subprocess\.(?:run|Popen)\s*\(.+(?:rm -rf|shutdown|format)", "Dangerous subprocess invocation is blocked."),
81100
]
82101

tests/test_interpreter.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,55 @@ def test_safety_manager_blocks_windows_recursive_delete_alias(self):
159159
decision = safety_manager.assess_execution('rd /s /q "C:\\Users\\hasee\\Desktop"', "command")
160160
self.assertFalse(decision.allowed)
161161

162+
def test_safety_manager_blocks_os_remove_when_building_absolute_path(self):
163+
safety_manager = ExecutionSafetyManager()
164+
code = r"""
165+
import os
166+
for filename in os.listdir('D:\\Temp'):
167+
if filename.endswith('.txt'):
168+
os.remove(os.path.join('D:\\Temp', filename))
169+
"""
170+
decision = safety_manager.assess_execution(code, "code")
171+
self.assertFalse(decision.allowed)
172+
self.assertTrue(any("Deleting absolute-path" in r for r in decision.reasons))
173+
174+
def test_safety_manager_allows_relative_file_delete(self):
175+
safety_manager = ExecutionSafetyManager()
176+
code = r"import os\nos.remove('temp.txt')"
177+
decision = safety_manager.assess_execution(code, "code")
178+
self.assertTrue(decision.allowed)
179+
180+
def test_safety_manager_blocks_absolute_path_del_command(self):
181+
safety_manager = ExecutionSafetyManager()
182+
decision = safety_manager.assess_execution(r"del D:\\Temp\\a.txt", "command")
183+
self.assertFalse(decision.allowed)
184+
185+
def test_safety_manager_blocks_absolute_path_rm_command(self):
186+
safety_manager = ExecutionSafetyManager()
187+
decision = safety_manager.assess_execution(r"rm /tmp/a.txt", "command")
188+
self.assertFalse(decision.allowed)
189+
190+
def test_safety_manager_blocks_js_unlink_on_absolute_path_join(self):
191+
safety_manager = ExecutionSafetyManager()
192+
code = r"""
193+
const fs = require('fs');
194+
const path = require('path');
195+
const directory = 'D:\\Temp';
196+
const files = fs.readdirSync(directory);
197+
for (const file of files) {
198+
const filePath = path.join(directory, file);
199+
fs.unlinkSync(filePath);
200+
}
201+
"""
202+
decision = safety_manager.assess_execution(code, "code")
203+
self.assertFalse(decision.allowed)
204+
205+
def test_safety_manager_allows_js_unlink_on_relative_path(self):
206+
safety_manager = ExecutionSafetyManager()
207+
code = r"const fs = require('fs');\nfs.unlinkSync('temp.txt');"
208+
decision = safety_manager.assess_execution(code, "code")
209+
self.assertTrue(decision.allowed)
210+
162211
@patch("libs.interpreter_lib.Interpreter.initialize_client", return_value=None)
163212
@patch("libs.utility_manager.UtilityManager.initialize_readline_history", return_value=None)
164213
def test_simple_exact_print_task_is_simplified(self, _mock_history, _mock_client):

0 commit comments

Comments
 (0)