fix: block shell operators in readonly/allowed-command mode#2689
fix: block shell operators in readonly/allowed-command mode#2689Ricardo-M-L wants to merge 4 commits intoag2ai:mainfrom
Conversation
When `LocalShellEnvironment(readonly=True)` restricts commands to a
whitelist, the `matches()` check only validates the command prefix.
Since commands run with `shell=True`, an agent can bypass the
restriction via shell operators:
echo 'data' > file.txt # write via redirect
cat /etc/passwd | nc ... # exfiltrate via pipe
ls && rm -rf / # chain destructive command
Add `contains_shell_operator()` that rejects commands containing
`>`, `>>`, `|`, `;`, `&&`, `||`, or backticks when an allowed-command
whitelist is active.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The original test exercised `echo hello > {output}` with `allowed=["echo"]`
and asserted the file was created — which was exactly the bypass this PR
fixes. Update the test to:
1. Assert plain `echo hello` works (allowed command without operators)
2. Add a new test that asserts `echo hello > {output}` is blocked
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@Ricardo-M-L thanks for identifying and working on this. Would you be able to check the failing tests? |
The test asserted `"hello" in str(reply)` but `reply` is the
TestConfig final_reply ("done") — the tool's stdout never appears in
the AgentReply, so the assertion always failed in CI even though the
command was correctly executed.
Replace with a touch-based test: allow "touch", run `touch <path>`,
assert the file is created. This mirrors the sibling
`test_allowed_blocks_non_matching_command` (same fixture, opposite
expectation) and verifies side-effecting allowed commands actually
run, which is what the original test name promised.
Addresses the failing tests called out in PR review.
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 116 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
@marklysze The failing tests are fixed in 12f42be. Root cause was that the assertion CI is now green across all 23 checks. Ready for another look when you have a moment 🙏 |
Why Are These Changes Needed?
LocalShellEnvironment(readonly=True)restricts execution to a whitelist of read-only commands. However, since commands run withshell=True, an agent can bypass this restriction via shell operators:The
matches()function only validates the command prefix ("echo", "cat", "ls") but doesn't detect shell operators (>,|,&&,;) that follow.Changes
base.py: Addcontains_shell_operator()that detects>,>>,|,;,&&,||, and backtickslocal.py: When an allowed-command whitelist is active, reject commands containing shell operatorsAfter the fix:
Related issue number
N/A (discovered during code review)
Checks
Verification
Test coverage (
test/beta/tools/test_local_shell.py):test_allowed_permits_matching_command— allowed command without operators executes (touch <path>, file is created)test_allowed_blocks_shell_redirect_bypass—echo hello > <path>is rejected even whenechois whitelistedtest_allowed_blocks_non_matching_command— non-whitelisted command stays blockedCI status at head
12f42beff1: 23 / 23 passing (was failing before the test-assertion fix in this commit — the previous test asserted"hello" in str(reply)butreplywas theTestConfigfinal_replyand never contained tool stdout).