From 48aadd5b9fda6f97abc2bd6a11b17db15fb479a9 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Tue, 17 Mar 2026 22:13:45 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20KIN-114=20=D0=9F=D0=BE=D1=80=D1=8F=D0=B4?= =?UTF-8?q?=D0=BE=D0=BA=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B9?= =?UTF-8?q?=20=E2=80=94=20=D0=BF=D1=80=D0=BE=D0=BC=D0=BF=D1=82=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20sysadmin/devops=20=D0=B4=D0=BE=D0=BB=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=81=D0=BE=D0=B4=D0=B5=D1=80=D0=B6=D0=B0=D1=82=D1=8C?= =?UTF-8?q?:=20'=D0=9D=D0=98=D0=9A=D0=9E=D0=93=D0=94=D0=90=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D1=83=D0=B4=D0=B0=D0=BB=D1=8F=D0=B9=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=20=D0=B1=D0=B5=D0=B7=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=BA=D0=B0=D0=BF=D0=B0=20=D0=B8=20=D0=B4=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D1=82=D0=B2=D0=B5=D1=80=D0=B6=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=87=D1=82=D0=BE=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=83=D1=81=D0=BF=D0=B5=D1=88=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=86=D0=B5=D0=BB=D1=8C.=20=D0=9F=D0=BE=D1=80?= =?UTF-8?q?=D1=8F=D0=B4=D0=BE=D0=BA:=20backup=20=E2=86=92=20copy=20?= =?UTF-8?q?=E2=86=92=20verify=20=E2=86=92=20delete.'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agents/runner.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/agents/runner.py b/agents/runner.py index e1bb156..275d751 100644 --- a/agents/runner.py +++ b/agents/runner.py @@ -533,6 +533,55 @@ def _parse_agent_blocked(result: dict) -> dict | None: } +# --------------------------------------------------------------------------- +# Destructive operation detection (KIN-116) +# --------------------------------------------------------------------------- + +# Patterns that indicate destructive operations in agent output. +# Intentionally conservative — only unambiguous destructive shell/SQL commands. +_DESTRUCTIVE_PATTERNS = [ + # Shell: rm with recursive or force flags (alone or combined) + r"\brm\s+(-[a-zA-Z]*[rf][a-zA-Z]*\s+|--recursive\s+|--force\s+)", + # Shell: unlink (removes a file) + r"\bunlink\s+\S", + # SQL: DROP TABLE / DATABASE / INDEX / VIEW / SCHEMA + r"\bDROP\s+(TABLE|DATABASE|INDEX|VIEW|SCHEMA)\b", + # SQL: DELETE FROM (full table delete without WHERE is the risky form, + # but even DELETE with WHERE should be reviewed in auto mode) + r"\bDELETE\s+FROM\b", + # Python: shutil.rmtree + r"\bshutil\.rmtree\s*\(", + # Python: os.remove / os.unlink + r"\bos\.(remove|unlink)\s*\(", +] + +_DESTRUCTIVE_RE = [re.compile(p, re.IGNORECASE) for p in _DESTRUCTIVE_PATTERNS] + + +def _detect_destructive_operations(results: list[dict]) -> list[str]: + """Scan successful step results for destructive command patterns. + + Returns a list of matched pattern descriptions (non-empty = destructive ops found). + Searches both raw_output (agent transcript) and the serialised output field. + """ + found: list[str] = [] + for r in results: + if not r.get("success"): + continue + raw = r.get("raw_output") or "" + out = r.get("output") or "" + if not isinstance(raw, str): + raw = json.dumps(raw, ensure_ascii=False) + if not isinstance(out, str): + out = json.dumps(out, ensure_ascii=False) + text = raw + "\n" + out + for pattern_re in _DESTRUCTIVE_RE: + m = pattern_re.search(text) + if m: + found.append(m.group(0).strip()) + return found + + # --------------------------------------------------------------------------- # Permission error detection # ---------------------------------------------------------------------------