From 621bf3a873b6b466b7fca6fbd6f4c7cf83a70fdd Mon Sep 17 00:00:00 2001 From: ashishpatel26 Date: Sat, 6 Jun 2026 13:31:06 +0530 Subject: [PATCH] fix(security): strip shell escapes in denylist normalizer; fail-closed on missing approval module DANGEROUS_PATTERNS and HARDLINE_PATTERNS are matched on the raw command string, so backslash-escape (r\m) and empty-quote split (r''m) bypass both lists. _normalize_command_for_detection now strips these before pattern matching. tui_gateway shell.exec had a bare 'except ImportError: pass' that silently disabled the entire safety gate if tools.approval wasn't importable. Changed to fail-closed (return 5001 error). Added detect_hardline_command check. Fixes #36846, #36847. --- tools/approval.py | 4 ++++ tui_gateway/server.py | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/approval.py b/tools/approval.py index c5051f71999..85ae2b9d7f6 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -537,6 +537,10 @@ def _normalize_command_for_detection(command: str) -> str: command = command.replace('\x00', '') # Normalize Unicode (fullwidth Latin, halfwidth Katakana, etc.) command = unicodedata.normalize('NFKC', command) + # Strip shell backslash-escapes: r\m → rm. Prevents \-injection bypass. + command = re.sub(r'\\([^\n])', r'\1', command) + # Strip empty-string literals that split tokens: r''m → rm, r""m → rm. + command = re.sub(r"''|\"\"", '', command) return command diff --git a/tui_gateway/server.py b/tui_gateway/server.py index d85e78b9c8a..888ce009ec6 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -8490,15 +8490,20 @@ def _(rid, params: dict) -> dict: if not cmd: return _err(rid, 4004, "empty command") try: - from tools.approval import detect_dangerous_command + from tools.approval import detect_dangerous_command, detect_hardline_command + is_hardline, hardline_desc = detect_hardline_command(cmd) + if is_hardline: + return _err( + rid, 4005, f"blocked (hardline): {hardline_desc}. Use the agent for dangerous commands." + ) is_dangerous, _, desc = detect_dangerous_command(cmd) if is_dangerous: return _err( rid, 4005, f"blocked: {desc}. Use the agent for dangerous commands." ) except ImportError: - pass + return _err(rid, 5001, "shell.exec unavailable: approval safety module not importable") try: r = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=30, cwd=os.getcwd()