agentscope-ai / agentscope-ai/agentscope

[Bug]: indirect `rm` spellings (/bin/rm, \rm, env rm, FOO=1 rm) bypass the dangerous-removal check

Đang mở
#2,500 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Python
Star
31.5k
Fork
3.5k
Merge trung bình
1 ngày 23 giờ
Pull request đã merge (30 ngày)
95

Mô tả

## Prerequisites

- [x] I have searched the existing issues and discussions, and this is not a duplicate.
- [x] This is a bug, not a usage question.

## Background / Description

`Bash.check_permissions()` step 5 calls `_check_dangerous_removal_path()` in
`src/agentscope/tool/_builtin/_bash.py`. Its bypass-immune ASK is the last line of defence for a
command that would delete a critical system path, and it is explicitly documented as one that
"cannot be auto-allowed by permission rules".

That helper decides whether a subcommand is a removal command by comparing the first
whitespace-separated token literally:

```python
subcmd_tokens = subcmd.strip().split()
base = subcmd_tokens[0]
if base not in ("rm", "rmdir"):
continue
```

Bash runs several other spellings as the very same command, and none of them match that comparison:

- an absolute or relative path — `/bin/rm`, `/usr/bin/rm`
- a backslash that suppresses alias expansion — `\rm`
- a leading environment assignment — `FOO=1 rm`
- a wrapper that execs its argument — `env rm`, `command rm`, `env -i rm`

Writing the flags separately (`-r -f` instead of `-rf`) additionally keeps the command clear of the
`"rm -rf"` substring in `DANGEROUS_COMMANDS`, so step 2 does not catch it either. The whole
bypass-immune chain is therefore skipped, and `check_permissions()` returns `PASSTHROUGH` with
`bypass_immune=False` — so a user-configured Bash allow rule auto-approves the command, and the
decision can also be silenced, unlike the `rm -r -f /` form.

## Steps to reproduce

```python
import asyncio

from agentscope.permission import PermissionContext, PermissionMode
from agentscope.tool._builtin._bash import Bash

CASES = [
"rm -rf /",
"rm -r -f /",
"rmdir /",
"/bin/rm -r -f /",
"/usr/bin/rm -r -f /etc",
"\\rm -r -f /",
"env rm -r -f /",
"env -i rm -r -f /",
"command rm -r -f /",
"FOO=1 rm -r -f /",
"sudo /bin/rm -r -f /",
"/bin/rm -r -f ~",
]

async def main() -> None:
tool = Bash()
context = PermissionContext(mode=PermissionMode.DEFAULT)
for command in CASES:
decision = await tool.check_permissions({"command": command}, context)
print(f"{command!r:<26} {decision.behavior.value:<12} bypass_immune={decision.bypass_immune}")

asyncio.run(main())
```

## Result on current `main` (`10eaaac`)

| command | behavior | bypass_immune |
|---|---|---|
| `rm -rf /` | ask | True |
| `rm -r -f /` | ask | True |
| `rmdir /` | ask | True |
| `/bin/rm -r -f /` | **passthrough** | **False** |
| `/usr/bin/rm -r -f /etc` | **passthrough** | **False** |
| `\rm -r -f /` | **passthrough** | **False** |
| `env rm -r -f /` | **passthrough** | **False** |
| `env -i rm -r -f /` | **passthrough** | **False** |
| `command rm -r -f /` | **passthrough** | **False** |
| `FOO=1 rm -r -f /` | **passthrough** | **False** |
| `sudo /bin/rm -r -f /` | **passthrough** | **False** |
| `/bin/rm -r -f ~` | **passthrough** | **False** |

The first three rows are the intended behaviour. Every `passthrough` row is a command that bash
executes identically to its flagged counterpart.

Note that `sudo rm -rf /` is still caught, but only by the `"sudo rm"` and `"rm -rf"` substring
patterns in step 2 — `sudo /bin/rm -r -f /` matches neither.

## Expected behavior

`_check_dangerous_removal_path()` should resolve the command name before comparing it, so that every
spelling bash treats as `rm` / `rmdir` reaches `_is_dangerous_removal_path()` and receives the same
bypass-immune ASK.

## Related

Same area as #2470, but a different function and a different defect: #2470 is about separator
handling in the parser's `is_read_only_command()`, while this one is the command-name comparison in
`_bash.py`'s removal check. The two do not overlap.

## Note

I would like to claim this one — I have a fix plus regression tests ready locally and will open a PR
referencing this issue.

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

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

Đánh giá

Issue này chưa được đánh giá.

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.