mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-21 12:53:37 +08:00
fix(s03): match Windows del as a command word
This commit is contained in:
@@ -57,6 +57,15 @@ def check_deny_list(command: str) -> str | None:
|
||||
**ゲート 2**:ルールマッチング — 「いつユーザーに聞くべきか」を記述する。各ルールはツールとチェック条件を指定する。
|
||||
|
||||
```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 = [
|
||||
{
|
||||
"tools": ["read_file", "write_file", "edit_file"],
|
||||
@@ -65,7 +74,9 @@ PERMISSION_RULES = [
|
||||
},
|
||||
{
|
||||
"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",
|
||||
},
|
||||
]
|
||||
@@ -141,6 +152,7 @@ python s03_permission/code.py
|
||||
2. `Delete the file test.txt`(bash + rm でゲート 2 が発動)
|
||||
3. `What files are in the current directory?`(読み取り専用、すべて通過)
|
||||
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` は発動しない。
|
||||
|
||||
観察のポイント:どの操作がそのまま通過するか? どれに確認が必要か? どれが即座に拒否されるか?
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
```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 = [
|
||||
{
|
||||
"tools": ["read_file", "write_file", "edit_file"],
|
||||
@@ -65,7 +74,9 @@ PERMISSION_RULES = [
|
||||
},
|
||||
{
|
||||
"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",
|
||||
},
|
||||
]
|
||||
@@ -141,6 +152,7 @@ Try these prompts:
|
||||
2. `Delete the file test.txt` (bash + rm triggers Gate 2)
|
||||
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)
|
||||
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?
|
||||
|
||||
|
||||
@@ -57,6 +57,15 @@ def check_deny_list(command: str) -> str | None:
|
||||
**闸门 2**负责规则匹配,用来描述"什么时候需要问用户"。每条规则指定工具和检查条件。
|
||||
|
||||
```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 = [
|
||||
{
|
||||
"tools": ["read_file", "write_file", "edit_file"],
|
||||
@@ -65,7 +74,9 @@ PERMISSION_RULES = [
|
||||
},
|
||||
{
|
||||
"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",
|
||||
},
|
||||
]
|
||||
@@ -141,6 +152,7 @@ python s03_permission/code.py
|
||||
2. `Delete the file test.txt`(bash + rm 会触发闸门 2)
|
||||
3. `What files are in the current directory?`(只读,全部通过)
|
||||
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` 不会。
|
||||
|
||||
观察重点:哪些操作直接通过?哪些需要你确认?哪些被直接拒绝?
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ Builds on s02 (multi-tool). Usage:
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -152,12 +153,22 @@ def check_deny_list(command: str) -> str | None:
|
||||
|
||||
|
||||
# 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 = [
|
||||
{"tools": ["read_file", "write_file", "edit_file"],
|
||||
"check": lambda args: not (WORKDIR / args.get("path", "")).resolve().is_relative_to(WORKDIR),
|
||||
"message": "Writing outside workspace"},
|
||||
{"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"},
|
||||
]
|
||||
|
||||
|
||||
@@ -102,12 +102,26 @@ agent_loop(history)
|
||||
**PreToolUse / PostToolUse**、ツール実行の前後のフック。s03 の権限チェックロジックは PreToolUse フックに包まれ、さらにログフックと大出力リマインダーが追加される:
|
||||
|
||||
```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 のロジック、ループからフックに移動)
|
||||
def permission_hook(block):
|
||||
if block.name == "bash":
|
||||
command = block.input.get("command", "")
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in block.input.get("command", ""):
|
||||
if pattern in command:
|
||||
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"):
|
||||
path = block.input.get("path", "")
|
||||
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
|
||||
@@ -130,6 +144,8 @@ register_hook("PreToolUse", log_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 は終了時の統計を出力する:
|
||||
|
||||
```python
|
||||
|
||||
@@ -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:
|
||||
|
||||
```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)
|
||||
def permission_hook(block):
|
||||
if block.name == "bash":
|
||||
command = block.input.get("command", "")
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in block.input.get("command", ""):
|
||||
if pattern in command:
|
||||
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"):
|
||||
path = block.input.get("path", "")
|
||||
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
|
||||
@@ -130,6 +144,8 @@ register_hook("PreToolUse", log_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:
|
||||
|
||||
```python
|
||||
|
||||
@@ -102,12 +102,26 @@ agent_loop(history)
|
||||
**PreToolUse / PostToolUse**,工具执行前后的 hook。s03 的权限检查逻辑现在包装成 PreToolUse hook,再加一个日志 hook 和一个大输出提醒:
|
||||
|
||||
```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)
|
||||
def permission_hook(block):
|
||||
if block.name == "bash":
|
||||
command = block.input.get("command", "")
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in block.input.get("command", ""):
|
||||
if pattern in command:
|
||||
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"):
|
||||
path = block.input.get("path", "")
|
||||
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
|
||||
@@ -130,6 +144,8 @@ register_hook("PreToolUse", log_hook)
|
||||
register_hook("PostToolUse", large_output_hook)
|
||||
```
|
||||
|
||||
沿用的 shell 规则不区分大小写,只在命令开头或 shell 分隔符之后识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被识别为危险命令。
|
||||
|
||||
**Stop** 在循环即将退出时触发。以下 hook 打印收尾统计:
|
||||
|
||||
```python
|
||||
|
||||
@@ -21,6 +21,7 @@ Hooks run callbacks at fixed points in the agent loop:
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -139,22 +140,32 @@ def trigger_hooks(event: str, *args):
|
||||
|
||||
# s03 permission check logic, now wrapped as a hook
|
||||
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):
|
||||
"""PreToolUse: s03 check_permission() logic moved here."""
|
||||
if block.name == "bash":
|
||||
command = block.input.get("command", "")
|
||||
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")
|
||||
return "Permission denied by deny list"
|
||||
for kw in DESTRUCTIVE:
|
||||
if kw in block.input.get("command", ""):
|
||||
print(f"\n\033[33m[permission] Potentially destructive command\033[0m")
|
||||
print(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
if contains_destructive_command(command) or any(
|
||||
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})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
if block.name in ("read_file", "write_file", "edit_file"):
|
||||
path = block.input.get("path", "")
|
||||
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -122,6 +122,10 @@ Agent 收到任务后的典型流程:先调 `todo_write` 列出所有步骤(
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -25,6 +25,7 @@ without an update, the harness adds a reminder alongside the tool results.
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -218,7 +219,15 @@ def trigger_hooks(event: str, *args):
|
||||
return None
|
||||
|
||||
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):
|
||||
"""PreToolUse: s03 permission logic, registered as an s04 hook."""
|
||||
@@ -228,13 +237,14 @@ def permission_hook(block):
|
||||
if pattern in command:
|
||||
print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
|
||||
return "Permission denied by deny list"
|
||||
for keyword in DESTRUCTIVE:
|
||||
if keyword in command:
|
||||
print(f"\n\033[33m[permission] Potentially destructive command\033[0m")
|
||||
print(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
if contains_destructive_command(command) or any(
|
||||
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})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
if block.name in ("read_file", "write_file", "edit_file"):
|
||||
path = block.input.get("path", "")
|
||||
if not (WORKDIR / path).resolve().is_relative_to(WORKDIR):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -88,6 +88,10 @@ TOOL_HANDLERS = {**BASE_HANDLERS, "task": run_subagent}
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -19,6 +19,7 @@ The subagent has no task tool, so it cannot delegate again.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -154,7 +155,14 @@ def trigger_hooks(event: str, *args):
|
||||
|
||||
|
||||
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):
|
||||
@@ -165,13 +173,14 @@ def permission_hook(block):
|
||||
if pattern in command:
|
||||
print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
|
||||
return "Permission denied by deny list"
|
||||
for keyword in DESTRUCTIVE:
|
||||
if keyword in command:
|
||||
print("\n\033[33m[permission] Potentially destructive command\033[0m")
|
||||
print(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
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(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
|
||||
if block.name in ("read_file", "write_file", "edit_file"):
|
||||
path = block.input.get("path", "")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -116,6 +116,10 @@ def load(self, name: str) -> str:
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -20,6 +20,7 @@ The model loads the full SKILL.md only when it calls load_skill.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -241,7 +242,14 @@ def trigger_hooks(event: str, *args):
|
||||
|
||||
|
||||
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):
|
||||
@@ -252,13 +260,14 @@ def permission_hook(block):
|
||||
if pattern in command:
|
||||
print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
|
||||
return "Permission denied by deny list"
|
||||
for keyword in DESTRUCTIVE:
|
||||
if keyword in command:
|
||||
print("\n\033[33m[permission] Potentially destructive command\033[0m")
|
||||
print(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
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(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
if choice not in ("y", "yes"):
|
||||
return "Permission denied by user"
|
||||
|
||||
if block.name in ("read_file", "write_file", "edit_file"):
|
||||
path = block.input.get("path", "")
|
||||
|
||||
@@ -296,6 +296,10 @@ if compact_requested:
|
||||
> **s09 との境界:** s08 は現在のセッションにある有限のコンテキストを管理し、再取得できる詳細を圧縮できます。s09 は、圧縮後や次のセッションにも残す情報を保存します。
|
||||
|
||||
|
||||
## 継承する権限ルール
|
||||
|
||||
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator(`;`、`&&`、`||`、`|`、`&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model`、`delimiter`、`echo del test.txt` は危険な command として扱わない。
|
||||
|
||||
## 試してみる
|
||||
|
||||
```bash
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
```bash
|
||||
|
||||
@@ -296,6 +296,10 @@ if compact_requested:
|
||||
> **与 s09 的边界:** s08 管理当前会话的有限上下文,压缩时允许舍弃可恢复的细节;s09 保存需要跨压缩、跨会话继续存在的信息。
|
||||
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```bash
|
||||
|
||||
@@ -178,7 +178,14 @@ def trigger_hooks(event: str, *args):
|
||||
|
||||
|
||||
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):
|
||||
@@ -187,7 +194,9 @@ def permission_hook(block):
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in command:
|
||||
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(f" Tool: {block.name}({block.input})")
|
||||
if input(" Allow? [y/N] ").strip().lower() not in ("y", "yes"):
|
||||
|
||||
@@ -170,6 +170,10 @@ except Exception:
|
||||
|
||||
---
|
||||
|
||||
## 継承する権限ルール
|
||||
|
||||
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator(`;`、`&&`、`||`、`|`、`&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model`、`delimiter`、`echo del test.txt` は危険な command として扱わない。
|
||||
|
||||
## 試してみる
|
||||
|
||||
```sh
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -170,6 +170,10 @@ except Exception:
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -634,7 +634,15 @@ def trigger_hooks(event: str, *args):
|
||||
return None
|
||||
|
||||
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):
|
||||
if block.name == "bash":
|
||||
@@ -642,7 +650,9 @@ def permission_hook(block):
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in command:
|
||||
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(f" Tool: {block.name}({block.input})")
|
||||
if input(" Allow? [y/N] ").strip().lower() not in ("y", "yes"):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -198,6 +198,10 @@ complete_task(tests.id) # ✓ Completed
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -444,7 +444,14 @@ def trigger_hooks(event: str, *args):
|
||||
|
||||
|
||||
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):
|
||||
@@ -454,7 +461,9 @@ def permission_hook(block):
|
||||
if pattern in command:
|
||||
print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
|
||||
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(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -151,6 +151,10 @@ npm install 在后台运行时,Agent Loop 继续执行了 read_file。
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -14,6 +14,7 @@ s11_background_tasks.py - Background Tasks
|
||||
import atexit
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import threading
|
||||
@@ -225,7 +226,14 @@ def trigger_hooks(event: str, *args):
|
||||
|
||||
|
||||
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):
|
||||
@@ -235,7 +243,9 @@ def permission_hook(block):
|
||||
if pattern in command:
|
||||
print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
|
||||
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(f" Tool: {block.name}({block.input})")
|
||||
choice = input(" Allow? [y/N] ").strip().lower()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -127,6 +127,10 @@ for job in fired:
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -16,6 +16,7 @@ s12_cron_scheduler.py - Cron Scheduler
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import subprocess
|
||||
import threading
|
||||
@@ -173,7 +174,14 @@ def trigger_hooks(event: str, *args):
|
||||
|
||||
|
||||
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:
|
||||
@@ -195,7 +203,9 @@ def permission_hook(block):
|
||||
if pattern in command:
|
||||
print(f"\n\033[31m[blocked] '{pattern}'\033[0m")
|
||||
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")
|
||||
|
||||
if block.name in ("read_file", "write_file", "edit_file"):
|
||||
|
||||
@@ -418,6 +418,10 @@ Lead:認証タスクの結果を受け取りました。残りの作業を調
|
||||
|
||||
---
|
||||
|
||||
## 継承する権限ルール
|
||||
|
||||
この章は s04 の permission hook を引き継ぐ。command の先頭または shell separator(`;`、`&&`、`||`、`|`、`&`、括弧、改行)の直後にある完全な `rm`/`del` command word だけを大文字小文字を区別せず検出する。`model`、`delimiter`、`echo del test.txt` は危険な command として扱わない。
|
||||
|
||||
## 試してみる
|
||||
|
||||
```sh
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -415,6 +415,10 @@ Lead:我已收到认证任务的结果,接下来继续协调其余工作。
|
||||
|
||||
---
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
|
||||
@@ -1663,7 +1663,14 @@ TOOL_HANDLERS = {
|
||||
|
||||
HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []}
|
||||
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):
|
||||
@@ -1686,7 +1693,9 @@ def check_permission(block, prompt_user: bool = True) -> str | None:
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in command:
|
||||
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:
|
||||
return "Permission required: ask Lead to run this command."
|
||||
print(f"\n[permission] {block.name}({block.input})")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```sh
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -369,7 +369,14 @@ def assemble_system_prompt() -> str:
|
||||
|
||||
HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []}
|
||||
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):
|
||||
@@ -390,7 +397,9 @@ def permission_hook(block):
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in command:
|
||||
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})")
|
||||
if input("Allow? [y/N] ").strip().lower() not in {"y", "yes"}:
|
||||
return "Permission denied by user"
|
||||
|
||||
@@ -222,6 +222,10 @@ command line から直接 Goal を設定することもできます。
|
||||
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 は「複数の仕事をどう実行するか」を扱いました。どの step を並列化し、結果をどう検証し、中断後にどう resume するかを決めます。
|
||||
|
||||
@@ -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"
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
s16 answers how a batch of work should run: which steps are concurrent, how results are verified, and how an interrupted run resumes.
|
||||
|
||||
@@ -222,6 +222,10 @@ python s17_goal_loop/code.py
|
||||
python s17_goal_loop/code.py "/goal python -m pytest 退出码为 0"
|
||||
```
|
||||
|
||||
## 继承的权限规则
|
||||
|
||||
本章沿用 s04 的权限 hook:只在命令开头或 shell 分隔符(`;`、`&&`、`||`、`|`、`&`、括号或换行)之后,按大小写不敏感方式识别完整的 `rm`/`del` 命令词。`model`、`delimiter` 和 `echo del test.txt` 不会被当成危险命令。
|
||||
|
||||
## 与 s16 的关系
|
||||
|
||||
s16 解决“一批工作怎样执行”:哪些步骤并行,结果怎样验证,失败后怎样恢复。
|
||||
|
||||
@@ -31,6 +31,7 @@ import asyncio
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@@ -45,7 +46,14 @@ DEFAULT_STOP_HOOK_BLOCK_CAP = 8
|
||||
MAX_GOAL_LENGTH = 4000
|
||||
CLEAR_ALIASES = {"clear", "stop", "off", "reset", "none", "cancel"}
|
||||
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):
|
||||
@@ -598,7 +606,9 @@ class AgentSession:
|
||||
for pattern in DENY_LIST:
|
||||
if pattern in command:
|
||||
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})")
|
||||
if input("Allow? [y/N] ").strip().lower() not in {"y", "yes"}:
|
||||
return "Permission denied by user"
|
||||
|
||||
126
tests/test_permission_command_words.py
Normal file
126
tests/test_permission_command_words.py
Normal 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
Reference in New Issue
Block a user