agentscope-ai / agentscope-ai/agentscope

[Bug]: `tee` is classified read-only, so its write is auto-ALLOWed while the equivalent redirection is not

Đang mở Phù hợp với người mới
#2,605 2 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Python
Star
31.6k
Fork
3.5k
Merge trung bình
1 ngày 16 giờ
Pull request đã merge (30 ngày)
103

Mô tả

- [x] I have searched the existing [issues](https://github.com/agentscope-ai/agentscope/issues) and [discussions](https://github.com/agentscope-ai/agentscope/discussions), and this is not a duplicate.
- [x] This is a bug, not a usage question. (For questions, please use [Discussions](https://github.com/agentscope-ai/agentscope/discussions/new?category=general) instead.)

### Background / Description

`tee` writes the file it is given, but `BashCommandParser` classifies it as read-only, so commands that write through `tee` are auto-ALLOWed while the equivalent redirection is refused the read-only label.

`_is_single_command_read_only()` ends with a membership test against `SAFE_COMMANDS`, and `tee` is in that set:

```python
# src/agentscope/tool/_builtin/_bash_parser.py:25-36
SAFE_COMMANDS: Set[str] = {
"echo", "cat", "ls", "pwd", "cd", "true", "false", "printf", "grep",
"tee", # <-- writes files
}

# src/agentscope/tool/_builtin/_bash_parser.py:233-235
# Check if base command is in safe commands
if base_cmd in SAFE_COMMANDS:
return True
```

The same module refuses the read-only label for redirection *precisely because* it writes:

```python
# src/agentscope/tool/_builtin/_bash_parser.py:175-177
# Check for output redirections - these are NOT read-only
if ">" in cmd:
return False
```

So two spellings of the same write get different verdicts. Measured against `main`:

| command | `is_read_only_command()` | `await Bash.check_read_only()` |
| --- | --- | --- |
| `echo x > /tmp/out.txt` | `False` | `False` |
| `echo x \| tee /tmp/out.txt` | **`True`** | **`True`** |
| `echo x >> /tmp/out.txt` | `False` | `False` |
| `echo x \| tee -a /tmp/out.txt` | **`True`** | **`True`** |
| `cat /etc/hosts > /tmp/out.txt` | `False` | `False` |
| `cat /etc/hosts \| tee /tmp/out.txt` | **`True`** | **`True`** |

**Consequence.** `Bash.check_permissions()` auto-ALLOWs whatever `check_read_only()` accepts, in every mode, before the dangerous-command / dangerous-path checks are reached. Verified with a `DENY` rule covering the target path:

```python
deny = PermissionRule(tool_name="Bash", rule_content="Bash(/tmp/**)",
behavior=PermissionBehavior.DENY, source="probe")
ctx = PermissionContext(mode=PermissionMode.DEFAULT, rules=[deny])

echo x > /tmp/out.txt -> PASSTHROUGH (rule is consulted)
echo x | tee /tmp/out.txt -> ALLOW (rule is never consulted)
echo x >> /tmp/out.txt -> PASSTHROUGH
echo x | tee -a /tmp/out.txt -> ALLOW
```

To be precise about severity: `tee` cannot execute arbitrary commands, so this is **not** command execution and not comparable to the separator bypass in #2470. It is a write performed without the rule/decision path that the equivalent redirection goes through, and a deny rule scoped to the target path does not reach it. I am reporting it as an inconsistency in the read-only classifier rather than as RCE.

The module already treats "a read-only-looking command that actually mutates" as a real concern — it has dedicated handling for `find` for exactly this reason:

```python
# src/agentscope/tool/_builtin/_bash_parser.py:214-215
if self._is_mutating_find_command(cmd):
return False
```

`tee` is the same shape of problem and has no equivalent guard.

### Error Messages

No exception is raised — that is the issue. The observable difference is the permission verdict:

```
echo x > /tmp/out.txt -> PermissionBehavior.PASSTHROUGH
echo x | tee /tmp/out.txt -> PermissionBehavior.ALLOW
```

### Steps to Reproduce

```python
import asyncio
from agentscope.permission import (PermissionBehavior, PermissionContext,
PermissionMode, PermissionRule)
from agentscope.tool._builtin._bash import Bash
from agentscope.tool._builtin._bash_parser import BashCommandParser

p = BashCommandParser()
print(p.is_read_only_command("echo x > /tmp/out.txt")) # False
print(p.is_read_only_command("echo x | tee /tmp/out.txt")) # True

async def main():
tool = Bash()
print(await tool.check_read_only({"command": "echo x > /tmp/out.txt"})) # False
print(await tool.check_read_only({"command": "echo x | tee /tmp/out.txt"})) # True

deny = PermissionRule(tool_name="Bash", rule_content="Bash(/tmp/**)",
behavior=PermissionBehavior.DENY, source="probe")
ctx = PermissionContext(mode=PermissionMode.DEFAULT, rules=[deny])
for c in ("echo x > /tmp/out.txt", "echo x | tee /tmp/out.txt"):
d = await tool.check_permissions({"command": c}, ctx)
print(d.behavior, c)

asyncio.run(main())
```

### Proposed fix (happy to open a PR)

Remove `tee` from `SAFE_COMMANDS`. That set is used for two things — the read-only verdict here, and command-prefix extraction at `_bash_parser.py:617` — so if the prefix-extraction use genuinely needs `tee`, the safer shape is to keep `SAFE_COMMANDS` for prefix extraction and decide read-only from `READ_ONLY_COMMANDS` alone, which already does not contain `tee`:

```python
# _is_single_command_read_only(): drop the SAFE_COMMANDS fallback, or
# exclude writing commands from it explicitly
WRITING_COMMANDS = {"tee"}
if base_cmd in SAFE_COMMANDS and base_cmd not in WRITING_COMMANDS:
return True
```

I have not implemented anything yet — per CONTRIBUTING I would like your steer on the shape first, since it touches the classifier that #2470 and #2500 are also about, and I do not want to collide with #2471.

### Environment

- AgentScope Version: 2.0.8
- Python Version: 3.13.15
- OS: Windows 11

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

Start in src/agentscope/tool/_builtin/_bash_parser.py, reading SAFE_COMMANDS, READ_ONLY_COMMANDS, _is_single_command_read_only(), and the prefix-extraction use near line 617. Run the listed tee and redirection reproductions, then verify that tee writes are no longer classified read-only, deny rules reach the permission decision, and prefix extraction remains correct.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
cli, security
Loại issue
Lỗi
Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
74/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.