fix(s03): match Windows del as a command word

This commit is contained in:
mameikagou
2026-08-26 00:18:53 +08:00
parent 04486201fc
commit 44e33d0ec3
55 changed files with 866 additions and 331 deletions

View File

@@ -57,6 +57,15 @@ def check_deny_list(command: str) -> str | None:
**ゲート 2**:ルールマッチング — 「いつユーザーに聞くべきか」を記述する。各ルールはツールとチェック条件を指定する。 **ゲート 2**:ルールマッチング — 「いつユーザーに聞くべきか」を記述する。各ルールはツールとチェック条件を指定する。
```python ```python
import re
DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
PERMISSION_RULES = [ PERMISSION_RULES = [
{ {
"tools": ["read_file", "write_file", "edit_file"], "tools": ["read_file", "write_file", "edit_file"],
@@ -65,7 +74,9 @@ PERMISSION_RULES = [
}, },
{ {
"tools": ["bash"], "tools": ["bash"],
"check": lambda args: any(kw in args.get("command", "") for kw in ["rm ", "> /etc/", "chmod 777"]), "check": lambda args: contains_destructive_command(args.get("command", "")) or any(
kw in args.get("command", "") for kw in ["> /etc/", "chmod 777"]
),
"message": "Potentially destructive command", "message": "Potentially destructive command",
}, },
] ]
@@ -141,6 +152,7 @@ python s03_permission/code.py
2. `Delete the file test.txt`bash + rm でゲート 2 が発動) 2. `Delete the file test.txt`bash + rm でゲート 2 が発動)
3. `What files are in the current directory?`(読み取り専用、すべて通過) 3. `What files are in the current directory?`(読み取り専用、すべて通過)
4. `Try to write a file to /etc/something`(作業ディレクトリ外への書き込みでゲート 2 が発動) 4. `Try to write a file to /etc/something`(作業ディレクトリ外への書き込みでゲート 2 が発動)
5. Windows では `del test.txt``DEL test.txt` がゲート 2 を発動し、`model``delimiter``echo del test.txt` は発動しない。
観察のポイント:どの操作がそのまま通過するか? どれに確認が必要か? どれが即座に拒否されるか? 観察のポイント:どの操作がそのまま通過するか? どれに確認が必要か? どれが即座に拒否されるか?

View File

@@ -57,6 +57,15 @@ def check_deny_list(command: str) -> str | None:
**Gate 2**: Rule matching — describes "when to ask the user." Each rule specifies a tool and a check condition. **Gate 2**: Rule matching — describes "when to ask the user." Each rule specifies a tool and a check condition.
```python ```python
import re
DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
PERMISSION_RULES = [ PERMISSION_RULES = [
{ {
"tools": ["read_file", "write_file", "edit_file"], "tools": ["read_file", "write_file", "edit_file"],
@@ -65,7 +74,9 @@ PERMISSION_RULES = [
}, },
{ {
"tools": ["bash"], "tools": ["bash"],
"check": lambda args: any(kw in args.get("command", "") for kw in ["rm ", "> /etc/", "chmod 777"]), "check": lambda args: contains_destructive_command(args.get("command", "")) or any(
kw in args.get("command", "") for kw in ["> /etc/", "chmod 777"]
),
"message": "Potentially destructive command", "message": "Potentially destructive command",
}, },
] ]
@@ -141,6 +152,7 @@ Try these prompts:
2. `Delete the file test.txt` (bash + rm triggers Gate 2) 2. `Delete the file test.txt` (bash + rm triggers Gate 2)
3. `What files are in the current directory?` (read-only, all pass) 3. `What files are in the current directory?` (read-only, all pass)
4. `Try to write a file to /etc/something` (writing outside workspace triggers Gate 2) 4. `Try to write a file to /etc/something` (writing outside workspace triggers Gate 2)
5. On Windows, `del test.txt` and `DEL test.txt` trigger Gate 2, while `model`, `delimiter`, and `echo del test.txt` do not.
What to watch for: Which operations pass through? Which need your confirmation? Which are denied outright? What to watch for: Which operations pass through? Which need your confirmation? Which are denied outright?

View File

@@ -57,6 +57,15 @@ def check_deny_list(command: str) -> str | None:
**闸门 2**负责规则匹配,用来描述"什么时候需要问用户"。每条规则指定工具和检查条件。 **闸门 2**负责规则匹配,用来描述"什么时候需要问用户"。每条规则指定工具和检查条件。
```python ```python
import re
DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
PERMISSION_RULES = [ PERMISSION_RULES = [
{ {
"tools": ["read_file", "write_file", "edit_file"], "tools": ["read_file", "write_file", "edit_file"],
@@ -65,7 +74,9 @@ PERMISSION_RULES = [
}, },
{ {
"tools": ["bash"], "tools": ["bash"],
"check": lambda args: any(kw in args.get("command", "") for kw in ["rm ", "> /etc/", "chmod 777"]), "check": lambda args: contains_destructive_command(args.get("command", "")) or any(
kw in args.get("command", "") for kw in ["> /etc/", "chmod 777"]
),
"message": "Potentially destructive command", "message": "Potentially destructive command",
}, },
] ]
@@ -141,6 +152,7 @@ python s03_permission/code.py
2. `Delete the file test.txt`bash + rm 会触发闸门 2 2. `Delete the file test.txt`bash + rm 会触发闸门 2
3. `What files are in the current directory?`(只读,全部通过) 3. `What files are in the current directory?`(只读,全部通过)
4. `Try to write a file to /etc/something`(写工作区外,触发闸门 2 4. `Try to write a file to /etc/something`(写工作区外,触发闸门 2
5. 在 Windows 上,`del test.txt``DEL test.txt` 会触发闸门 2`model``delimiter``echo del test.txt` 不会。
观察重点:哪些操作直接通过?哪些需要你确认?哪些被直接拒绝? 观察重点:哪些操作直接通过?哪些需要你确认?哪些被直接拒绝?

View File

@@ -32,6 +32,7 @@ Builds on s02 (multi-tool). Usage:
""" """
import os import os
import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -152,12 +153,22 @@ def check_deny_list(command: str) -> str | None:
# Gate 2: Rule matching - context-dependent checks # Gate 2: Rule matching - context-dependent checks
DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
PERMISSION_RULES = [ PERMISSION_RULES = [
{"tools": ["read_file", "write_file", "edit_file"], {"tools": ["read_file", "write_file", "edit_file"],
"check": lambda args: not (WORKDIR / args.get("path", "")).resolve().is_relative_to(WORKDIR), "check": lambda args: not (WORKDIR / args.get("path", "")).resolve().is_relative_to(WORKDIR),
"message": "Writing outside workspace"}, "message": "Writing outside workspace"},
{"tools": ["bash"], {"tools": ["bash"],
"check": lambda args: any(kw in args.get("command", "") for kw in ["rm ", "> /etc/", "chmod 777"]), "check": lambda args: contains_destructive_command(args.get("command", "")) or
any(kw in args.get("command", "") for kw in ["> /etc/", "chmod 777"]),
"message": "Potentially destructive command"}, "message": "Potentially destructive command"},
] ]

View File

@@ -102,12 +102,26 @@ agent_loop(history)
**PreToolUse / PostToolUse**、ツール実行の前後のフック。s03 の権限チェックロジックは PreToolUse フックに包まれ、さらにログフックと大出力リマインダーが追加される: **PreToolUse / PostToolUse**、ツール実行の前後のフック。s03 の権限チェックロジックは PreToolUse フックに包まれ、さらにログフックと大出力リマインダーが追加される:
```python ```python
import re
DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
# PreToolUse: 権限チェックs03 のロジック、ループからフックに移動) # PreToolUse: 権限チェックs03 のロジック、ループからフックに移動)
def permission_hook(block): def permission_hook(block):
if block.name == "bash": if block.name == "bash":
command = block.input.get("command", "")
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in block.input.get("command", ""): if pattern in command:
return "Permission denied by deny list" return "Permission denied by deny list"
if contains_destructive_command(command):
return "Potentially destructive command"
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):
path = block.input.get("path", "") path = block.input.get("path", "")
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR): if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
@@ -130,6 +144,8 @@ register_hook("PreToolUse", log_hook)
register_hook("PostToolUse", large_output_hook) register_hook("PostToolUse", large_output_hook)
``` ```
継承された shell rule は大文字小文字を区別せず、command の先頭または shell separator の直後にある完全な `rm`/`del` command word だけを検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
**Stop** はループが終了する直前に発火する。以下の hook は終了時の統計を出力する: **Stop** はループが終了する直前に発火する。以下の hook は終了時の統計を出力する:
```python ```python

View File

@@ -102,12 +102,26 @@ agent_loop(history)
**PreToolUse / PostToolUse**, hooks before and after tool execution. s03's permission check logic is now wrapped as a PreToolUse hook, plus a logging hook and a large-output reminder: **PreToolUse / PostToolUse**, hooks before and after tool execution. s03's permission check logic is now wrapped as a PreToolUse hook, plus a logging hook and a large-output reminder:
```python ```python
import re
DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
# PreToolUse: permission check (s03 logic, moved from loop to hook) # PreToolUse: permission check (s03 logic, moved from loop to hook)
def permission_hook(block): def permission_hook(block):
if block.name == "bash": if block.name == "bash":
command = block.input.get("command", "")
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in block.input.get("command", ""): if pattern in command:
return "Permission denied by deny list" return "Permission denied by deny list"
if contains_destructive_command(command):
return "Potentially destructive command"
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):
path = block.input.get("path", "") path = block.input.get("path", "")
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR): if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
@@ -130,6 +144,8 @@ register_hook("PreToolUse", log_hook)
register_hook("PostToolUse", large_output_hook) register_hook("PostToolUse", large_output_hook)
``` ```
The inherited shell rule is case-insensitive and matches a complete `rm` or `del` command word only at the start of a command or after a shell separator. It does not match `model`, `delimiter`, or `echo del test.txt`.
**Stop** triggers when the loop is about to exit. The following hook prints a cleanup summary: **Stop** triggers when the loop is about to exit. The following hook prints a cleanup summary:
```python ```python

View File

@@ -102,12 +102,26 @@ agent_loop(history)
**PreToolUse / PostToolUse**,工具执行前后的 hook。s03 的权限检查逻辑现在包装成 PreToolUse hook再加一个日志 hook 和一个大输出提醒: **PreToolUse / PostToolUse**,工具执行前后的 hook。s03 的权限检查逻辑现在包装成 PreToolUse hook再加一个日志 hook 和一个大输出提醒:
```python ```python
import re
DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
# PreToolUse: 权限检查s03 的逻辑,从循环移到 hook # PreToolUse: 权限检查s03 的逻辑,从循环移到 hook
def permission_hook(block): def permission_hook(block):
if block.name == "bash": if block.name == "bash":
command = block.input.get("command", "")
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in block.input.get("command", ""): if pattern in command:
return "Permission denied by deny list" return "Permission denied by deny list"
if contains_destructive_command(command):
return "Potentially destructive command"
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):
path = block.input.get("path", "") path = block.input.get("path", "")
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR): if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
@@ -130,6 +144,8 @@ register_hook("PreToolUse", log_hook)
register_hook("PostToolUse", large_output_hook) register_hook("PostToolUse", large_output_hook)
``` ```
沿用的 shell 规则不区分大小写,只在命令开头或 shell 分隔符之后识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被识别为危险命令。
**Stop** 在循环即将退出时触发。以下 hook 打印收尾统计: **Stop** 在循环即将退出时触发。以下 hook 打印收尾统计:
```python ```python

View File

@@ -21,6 +21,7 @@ Hooks run callbacks at fixed points in the agent loop:
""" """
import os import os
import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -139,22 +140,32 @@ def trigger_hooks(event: str, *args):
# s03 permission check logic, now wrapped as a hook # s03 permission check logic, now wrapped as a hook
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
"""PreToolUse: s03 check_permission() logic moved here.""" """PreToolUse: s03 check_permission() logic moved here."""
if block.name == "bash": if block.name == "bash":
command = block.input.get("command", "")
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in block.input.get("command", ""): if pattern in command:
print(f"\n\033[31m[blocked] '{pattern}'\033[0m") print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
return "Permission denied by deny list" return "Permission denied by deny list"
for kw in DESTRUCTIVE: if contains_destructive_command(command) or any(
if kw in block.input.get("command", ""): kw in command for kw in DESTRUCTIVE
print(f"\n\033[33m[permission] Potentially destructive command\033[0m") ):
print(f" Tool: {block.name}({block.input})") print(f"\n\033[33m[permission] Potentially destructive command\033[0m")
choice = input(" Allow? [y/N] ").strip().lower() print(f" Tool: {block.name}({block.input})")
if choice not in ("y", "yes"): choice = input(" Allow? [y/N] ").strip().lower()
return "Permission denied by user" if choice not in ("y", "yes"):
return "Permission denied by user"
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):
path = block.input.get("path", "") path = block.input.get("path", "")
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR): if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):

View File

@@ -122,6 +122,10 @@ Agent がタスクを受け取った後の典型的な流れ:まず `todo_writ
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみよう ## 試してみよう
```sh ```sh

View File

@@ -122,6 +122,10 @@ Typical flow when the Agent receives a task: first call `todo_write` to list all
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -122,6 +122,10 @@ Agent 收到任务后的典型流程:先调 `todo_write` 列出所有步骤(
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -25,6 +25,7 @@ without an update, the harness adds a reminder alongside the tool results.
import ast import ast
import json import json
import os import os
import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -218,7 +219,15 @@ def trigger_hooks(event: str, *args):
return None return None
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
"""PreToolUse: s03 permission logic, registered as an s04 hook.""" """PreToolUse: s03 permission logic, registered as an s04 hook."""
@@ -228,13 +237,14 @@ def permission_hook(block):
if pattern in command: if pattern in command:
print(f"\n\033[31m[blocked] '{pattern}'\033[0m") print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
return "Permission denied by deny list" return "Permission denied by deny list"
for keyword in DESTRUCTIVE: if contains_destructive_command(command) or any(
if keyword in command: keyword in command for keyword in DESTRUCTIVE
print(f"\n\033[33m[permission] Potentially destructive command\033[0m") ):
print(f" Tool: {block.name}({block.input})") print(f"\n\033[33m[permission] Potentially destructive command\033[0m")
choice = input(" Allow? [y/N] ").strip().lower() print(f" Tool: {block.name}({block.input})")
if choice not in ("y", "yes"): choice = input(" Allow? [y/N] ").strip().lower()
return "Permission denied by user" if choice not in ("y", "yes"):
return "Permission denied by user"
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):
path = block.input.get("path", "") path = block.input.get("path", "")
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR): if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):

View File

@@ -88,6 +88,10 @@ TOOL_HANDLERS = {**BASE_HANDLERS, "task": run_subagent}
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみよう ## 試してみよう
```sh ```sh

View File

@@ -88,6 +88,10 @@ The parent dispatches `task` through the same handler map as its other tools. Th
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -88,6 +88,10 @@ TOOL_HANDLERS = {**BASE_HANDLERS, "task": run_subagent}
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -19,6 +19,7 @@ The subagent has no task tool, so it cannot delegate again.
""" """
import os import os
import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -154,7 +155,14 @@ def trigger_hooks(event: str, *args):
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
@@ -165,13 +173,14 @@ def permission_hook(block):
if pattern in command: if pattern in command:
print(f"\n\033[31m[blocked] '{pattern}'\033[0m") print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
return "Permission denied by deny list" return "Permission denied by deny list"
for keyword in DESTRUCTIVE: if contains_destructive_command(command) or any(
if keyword in command: keyword in command for keyword in DESTRUCTIVE
print("\n\033[33m[permission] Potentially destructive command\033[0m") ):
print(f" Tool: {block.name}({block.input})") print("\n\033[33m[permission] Potentially destructive command\033[0m")
choice = input(" Allow? [y/N] ").strip().lower() print(f" Tool: {block.name}({block.input})")
if choice not in ("y", "yes"): choice = input(" Allow? [y/N] ").strip().lower()
return "Permission denied by user" if choice not in ("y", "yes"):
return "Permission denied by user"
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):
path = block.input.get("path", "") path = block.input.get("path", "")

View File

@@ -116,6 +116,10 @@ def load(self, name: str) -> str:
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみよう ## 試してみよう
```sh ```sh

View File

@@ -116,6 +116,10 @@ def load(self, name: str) -> str:
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -116,6 +116,10 @@ def load(self, name: str) -> str:
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -20,6 +20,7 @@ The model loads the full SKILL.md only when it calls load_skill.
""" """
import os import os
import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -241,7 +242,14 @@ def trigger_hooks(event: str, *args):
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
@@ -252,13 +260,14 @@ def permission_hook(block):
if pattern in command: if pattern in command:
print(f"\n\033[31m[blocked] '{pattern}'\033[0m") print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
return "Permission denied by deny list" return "Permission denied by deny list"
for keyword in DESTRUCTIVE: if contains_destructive_command(command) or any(
if keyword in command: keyword in command for keyword in DESTRUCTIVE
print("\n\033[33m[permission] Potentially destructive command\033[0m") ):
print(f" Tool: {block.name}({block.input})") print("\n\033[33m[permission] Potentially destructive command\033[0m")
choice = input(" Allow? [y/N] ").strip().lower() print(f" Tool: {block.name}({block.input})")
if choice not in ("y", "yes"): choice = input(" Allow? [y/N] ").strip().lower()
return "Permission denied by user" if choice not in ("y", "yes"):
return "Permission denied by user"
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):
path = block.input.get("path", "") path = block.input.get("path", "")

View File

@@ -296,6 +296,10 @@ if compact_requested:
> **s09 との境界:** s08 は現在のセッションにある有限のコンテキストを管理し、再取得できる詳細を圧縮できます。s09 は、圧縮後や次のセッションにも残す情報を保存します。 > **s09 との境界:** s08 は現在のセッションにある有限のコンテキストを管理し、再取得できる詳細を圧縮できます。s09 は、圧縮後や次のセッションにも残す情報を保存します。
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみる ## 試してみる
```bash ```bash

View File

@@ -296,6 +296,10 @@ This leaves no orphaned tool result. It also preserves the record of a file writ
> **Boundary with s09:** s08 manages the limited context of the current session and may discard recoverable details. s09 stores information that must survive compaction and future sessions. > **Boundary with s09:** s08 manages the limited context of the current session and may discard recoverable details. s09 stores information that must survive compaction and future sessions.
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```bash ```bash

View File

@@ -296,6 +296,10 @@ if compact_requested:
> **与 s09 的边界:** s08 管理当前会话的有限上下文压缩时允许舍弃可恢复的细节s09 保存需要跨压缩、跨会话继续存在的信息。 > **与 s09 的边界:** s08 管理当前会话的有限上下文压缩时允许舍弃可恢复的细节s09 保存需要跨压缩、跨会话继续存在的信息。
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```bash ```bash

View File

@@ -178,7 +178,14 @@ def trigger_hooks(event: str, *args):
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
@@ -187,7 +194,9 @@ def permission_hook(block):
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in command: if pattern in command:
return f"Permission denied by deny list: {pattern}" return f"Permission denied by deny list: {pattern}"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
print("\n\033[33m[permission] Potentially destructive command\033[0m") print("\n\033[33m[permission] Potentially destructive command\033[0m")
print(f" Tool: {block.name}({block.input})") print(f" Tool: {block.name}({block.input})")
if input(" Allow? [y/N] ").strip().lower() not in ("y", "yes"): if input(" Allow? [y/N] ").strip().lower() not in ("y", "yes"):

View File

@@ -170,6 +170,10 @@ except Exception:
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみる ## 試してみる
```sh ```sh

View File

@@ -170,6 +170,10 @@ The course uses a simple count threshold. A real application must also choose a
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -170,6 +170,10 @@ except Exception:
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -634,7 +634,15 @@ def trigger_hooks(event: str, *args):
return None return None
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
if block.name == "bash": if block.name == "bash":
@@ -642,7 +650,9 @@ def permission_hook(block):
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in command: if pattern in command:
return f"Permission denied by deny list: {pattern}" return f"Permission denied by deny list: {pattern}"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
print("\n\033[33m[permission] Potentially destructive command\033[0m") print("\n\033[33m[permission] Potentially destructive command\033[0m")
print(f" Tool: {block.name}({block.input})") print(f" Tool: {block.name}({block.input})")
if input(" Allow? [y/N] ").strip().lower() not in ("y", "yes"): if input(" Allow? [y/N] ").strip().lower() not in ("y", "yes"):

View File

@@ -198,6 +198,10 @@ complete_task(tests.id) # ✓ Completed
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみる ## 試してみる
```sh ```sh

View File

@@ -198,6 +198,10 @@ Each `create_task` writes a JSON file; `update_task`, `claim_task`, and `complet
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -198,6 +198,10 @@ complete_task(tests.id) # ✓ Completed
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -444,7 +444,14 @@ def trigger_hooks(event: str, *args):
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
@@ -454,7 +461,9 @@ def permission_hook(block):
if pattern in command: if pattern in command:
print(f"\n\033[31m[blocked] '{pattern}'\033[0m") print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
return "Permission denied by deny list" return "Permission denied by deny list"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
print("\n\033[33m[permission] Potentially destructive command\033[0m") print("\n\033[33m[permission] Potentially destructive command\033[0m")
print(f" Tool: {block.name}({block.input})") print(f" Tool: {block.name}({block.input})")
choice = input(" Allow? [y/N] ").strip().lower() choice = input(" Allow? [y/N] ").strip().lower()

View File

@@ -151,6 +151,10 @@ npm install がバックグラウンドで実行されている間、Agent Loop
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみる ## 試してみる
```sh ```sh

View File

@@ -151,6 +151,10 @@ While npm install ran in the background, the Agent Loop continued with read_file
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -151,6 +151,10 @@ npm install 在后台运行时Agent Loop 继续执行了 read_file。
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -14,6 +14,7 @@ s11_background_tasks.py - Background Tasks
import atexit import atexit
import glob import glob
import os import os
import re
import signal import signal
import subprocess import subprocess
import threading import threading
@@ -225,7 +226,14 @@ def trigger_hooks(event: str, *args):
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def permission_hook(block): def permission_hook(block):
@@ -235,7 +243,9 @@ def permission_hook(block):
if pattern in command: if pattern in command:
print(f"\n\033[31m[blocked] '{pattern}'\033[0m") print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
return "Permission denied by deny list" return "Permission denied by deny list"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
print("\n\033[33m[permission] Potentially destructive command\033[0m") print("\n\033[33m[permission] Potentially destructive command\033[0m")
print(f" Tool: {block.name}({block.input})") print(f" Tool: {block.name}({block.input})")
choice = input(" Allow? [y/N] ").strip().lower() choice = input(" Allow? [y/N] ").strip().lower()

View File

@@ -127,6 +127,10 @@ Agent が閉じている間も実行する必要がある場合は、crontab、s
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみる ## 試してみる
```sh ```sh

View File

@@ -127,6 +127,10 @@ Use crontab, a systemd timer, or an external scheduler when jobs must run while
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -127,6 +127,10 @@ for job in fired:
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -16,6 +16,7 @@ s12_cron_scheduler.py - Cron Scheduler
import glob import glob
import json import json
import os import os
import re
import secrets import secrets
import subprocess import subprocess
import threading import threading
@@ -173,7 +174,14 @@ def trigger_hooks(event: str, *args):
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def request_permission(block, reason: str) -> str | None: def request_permission(block, reason: str) -> str | None:
@@ -195,7 +203,9 @@ def permission_hook(block):
if pattern in command: if pattern in command:
print(f"\n\033[31m[blocked] '{pattern}'\033[0m") print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
return "Permission denied by deny list" return "Permission denied by deny list"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
return request_permission(block, "Potentially destructive command") return request_permission(block, "Potentially destructive command")
if block.name in ("read_file", "write_file", "edit_file"): if block.name in ("read_file", "write_file", "edit_file"):

View File

@@ -418,6 +418,10 @@ Lead認証タスクの結果を受け取りました。残りの作業を調
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみる ## 試してみる
```sh ```sh

View File

@@ -418,6 +418,10 @@ The terminal exposes the user request, Lead's proposal, task state, claims, sele
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It ## Try It
```sh ```sh

View File

@@ -415,6 +415,10 @@ Lead我已收到认证任务的结果接下来继续协调其余工作。
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -1663,7 +1663,14 @@ TOOL_HANDLERS = {
HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []} HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []}
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def register_hook(event: str, callback): def register_hook(event: str, callback):
@@ -1686,7 +1693,9 @@ def check_permission(block, prompt_user: bool = True) -> str | None:
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in command: if pattern in command:
return f"Permission denied by deny list: {pattern}" return f"Permission denied by deny list: {pattern}"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
if not prompt_user: if not prompt_user:
return "Permission required: ask Lead to run this command." return "Permission required: ask Lead to run this command."
print(f"\n[permission] {block.name}({block.input})") print(f"\n[permission] {block.name}({block.input})")

View File

@@ -169,6 +169,10 @@ lesson script を終了せず、model は次の turn で argument を修正で
--- ---
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## 試してみる ## 試してみる
```sh ```sh

View File

@@ -169,6 +169,10 @@ This chapter does not carry Task, Background, Cron, Team, or Worktree. They join
--- ---
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Try It Out ## Try It Out
```sh ```sh

View File

@@ -169,6 +169,10 @@ MCP error: TypeError: <lambda>() missing 1 required argument: 'query'
--- ---
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 试一下 ## 试一下
```sh ```sh

View File

@@ -369,7 +369,14 @@ def assemble_system_prompt() -> str:
HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []} HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []}
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
def register_hook(event: str, callback): def register_hook(event: str, callback):
@@ -390,7 +397,9 @@ def permission_hook(block):
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in command: if pattern in command:
return f"Permission denied by deny list: {pattern}" return f"Permission denied by deny list: {pattern}"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
print(f"\n[permission] {block.name}({block.input})") print(f"\n[permission] {block.name}({block.input})")
if input("Allow? [y/N] ").strip().lower() not in {"y", "yes"}: if input("Allow? [y/N] ").strip().lower() not in {"y", "yes"}:
return "Permission denied by user" return "Permission denied by user"

View File

@@ -222,6 +222,10 @@ command line から直接 Goal を設定することもできます。
python s17_goal_loop/code.py "/goal python -m pytest が exit code 0 で終了する" python s17_goal_loop/code.py "/goal python -m pytest が exit code 0 で終了する"
``` ```
## 継承する権限ルール
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator`;``&&``||``|``&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model``delimiter``echo del test.txt` は危険な command として扱わない。
## s16 との関係 ## s16 との関係
s16 は「複数の仕事をどう実行するか」を扱いました。どの step を並列化し、結果をどう検証し、中断後にどう resume するかを決めます。 s16 は「複数の仕事をどう実行するか」を扱いました。どの step を並列化し、結果をどう検証し、中断後にどう resume するかを決めます。

View File

@@ -222,6 +222,10 @@ You can also set a Goal directly from the command line:
python s17_goal_loop/code.py "/goal python -m pytest exits with code 0" python s17_goal_loop/code.py "/goal python -m pytest exits with code 0"
``` ```
## Inherited permission rule
This chapter carries forward the permission hook from s04. It recognizes `rm` and `del` case-insensitively only as complete command words at the start of a command or after a shell separator (`;`, `&&`, `||`, `|`, `&`, parentheses, or a newline). It does not treat `model`, `delimiter`, or `echo del test.txt` as destructive.
## Relationship to s16 ## Relationship to s16
s16 answers how a batch of work should run: which steps are concurrent, how results are verified, and how an interrupted run resumes. s16 answers how a batch of work should run: which steps are concurrent, how results are verified, and how an interrupted run resumes.

View File

@@ -222,6 +222,10 @@ python s17_goal_loop/code.py
python s17_goal_loop/code.py "/goal python -m pytest 退出码为 0" python s17_goal_loop/code.py "/goal python -m pytest 退出码为 0"
``` ```
## 继承的权限规则
本章沿用 s04 的权限 hook只在命令开头或 shell 分隔符(`;``&&``||``|``&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model``delimiter``echo del test.txt` 不会被当成危险命令。
## 与 s16 的关系 ## 与 s16 的关系
s16 解决“一批工作怎样执行”:哪些步骤并行,结果怎样验证,失败后怎样恢复。 s16 解决“一批工作怎样执行”:哪些步骤并行,结果怎样验证,失败后怎样恢复。

View File

@@ -31,6 +31,7 @@ import asyncio
import glob import glob
import json import json
import os import os
import re
import subprocess import subprocess
import sys import sys
import time import time
@@ -45,7 +46,14 @@ DEFAULT_STOP_HOOK_BLOCK_CAP = 8
MAX_GOAL_LENGTH = 4000 MAX_GOAL_LENGTH = 4000
CLEAR_ALIASES = {"clear", "stop", "off", "reset", "none", "cancel"} CLEAR_ALIASES = {"clear", "stop", "off", "reset", "none", "cancel"}
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="] DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
DESTRUCTIVE = ["rm ", "> /etc/", "chmod 777"] DESTRUCTIVE_COMMAND_WORD = re.compile(
r"(?i)(?:^|[;&|()\n])\s*(?:rm|del)(?=\s|$|[;&|()])"
)
DESTRUCTIVE = ["> /etc/", "chmod 777"]
def contains_destructive_command(command: str) -> bool:
return bool(DESTRUCTIVE_COMMAND_WORD.search(command))
class GoalError(Exception): class GoalError(Exception):
@@ -598,7 +606,9 @@ class AgentSession:
for pattern in DENY_LIST: for pattern in DENY_LIST:
if pattern in command: if pattern in command:
return f"Permission denied by deny list: {pattern}" return f"Permission denied by deny list: {pattern}"
if any(keyword in command for keyword in DESTRUCTIVE): if contains_destructive_command(command) or any(
keyword in command for keyword in DESTRUCTIVE
):
print(f"\n[permission] {name}({arguments})") print(f"\n[permission] {name}({arguments})")
if input("Allow? [y/N] ").strip().lower() not in {"y", "yes"}: if input("Allow? [y/N] ").strip().lower() not in {"y", "yes"}:
return "Permission denied by user" return "Permission denied by user"

View File

@@ -0,0 +1,126 @@
import importlib.util
import os
import sys
import time
import types
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
PERMISSION_LESSONS = tuple(
ROOT / chapter / "code.py"
for chapter in (
"s03_permission",
"s04_hooks",
"s05_todo_write",
"s06_subagent",
"s07_skill_loading",
"s08_context_compact",
"s09_memory",
"s10_task_system",
"s11_background_tasks",
"s12_cron_scheduler",
"s13_agent_teams",
"s14_mcp_plugin",
"s17_goal_loop",
)
)
def load_lesson(workdir: Path, lesson_path: Path):
fake_anthropic = types.ModuleType("anthropic")
fake_dotenv = types.ModuleType("dotenv")
class FakeAnthropic:
def __init__(self, *args, **kwargs):
self.messages = types.SimpleNamespace(create=None)
fake_anthropic.Anthropic = FakeAnthropic
fake_dotenv.load_dotenv = lambda override=True: None
previous_modules = {
"anthropic": sys.modules.get("anthropic"),
"dotenv": sys.modules.get("dotenv"),
}
previous_cwd = Path.cwd()
previous_model = os.environ.get("MODEL_ID")
module_name = f"permission_words_{lesson_path.parent.name}_{time.time_ns()}"
spec = importlib.util.spec_from_file_location(module_name, lesson_path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules["anthropic"] = fake_anthropic
sys.modules["dotenv"] = fake_dotenv
sys.modules[module_name] = module
try:
os.chdir(workdir)
os.environ["MODEL_ID"] = "test-model"
spec.loader.exec_module(module)
return module
finally:
os.chdir(previous_cwd)
if previous_model is None:
os.environ.pop("MODEL_ID", None)
else:
os.environ["MODEL_ID"] = previous_model
for name, previous in previous_modules.items():
if previous is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = previous
sys.modules.pop(module_name, None)
def permission_result(lesson, block):
if hasattr(lesson, "check_rules"):
return lesson.check_rules(block.name, block.input)
if hasattr(lesson, "permission_hook"):
return lesson.permission_hook(block)
goal = lesson.GoalController(evaluator=None)
session = lesson.AgentSession(
client=None,
model="test-model",
goal=goal,
workdir=Path.cwd(),
)
return session._permission_hook(block)
COMMAND_CASES = (
("rm file.txt", True),
("DEL file.txt", True),
("echo ready; rm file.txt", True),
("echo ready && del file.txt", True),
("echo ready || RM file.txt", True),
("echo ready | del file.txt", True),
("echo ready & rm file.txt", True),
("(del file.txt)", True),
("rm; echo ready", True),
("model list", False),
("delimiter file.txt", False),
("echo del file.txt", False),
("echo; delimiter file.txt", False),
("not-rm file.txt", False),
)
@pytest.mark.parametrize(
"lesson_path", PERMISSION_LESSONS, ids=lambda path: path.parent.name
)
@pytest.mark.parametrize("command, expected", COMMAND_CASES)
def test_permission_command_words_cover_position_case_and_boundaries(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
lesson_path: Path,
command: str,
expected: bool,
) -> None:
lesson = load_lesson(tmp_path, lesson_path)
monkeypatch.setattr("builtins.input", lambda _prompt: "n")
block = types.SimpleNamespace(name="bash", input={"command": command})
result = permission_result(lesson, block)
assert bool(result) is expected

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long