feat: add shell tool#1107
Conversation
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Tool PR ChecklistUse this checklist when adding or modifying tools in Protocol Compliance
Integration
|
planetf1
left a comment
There was a problem hiding this comment.
Overall the design is solid — subprocess.run(shell=False) as the foundation, shlex.split() for tokenisation, and working-dir restriction are the right primitives. Two confirmed bugs block merge; a third issue means the _bash_patterns.py test suite gives false assurance about what's actually blocked at runtime.
| if cmd not in SAFE_WRAPPER_COMMANDS or arg_cmd not in ( | ||
| "bash", | ||
| "sh", | ||
| "zsh", |
There was a problem hiding this comment.
env bash -c <payload> bypasses the denylist
The code-execution check (lines 176–182) only fires when argv[0] is the interpreter. When argv[0] is env, that path is never reached — and lines 225–231 explicitly allow shells as nested arguments inside safe wrappers, so -c is never re-checked.
_is_dangerous_command(["env", "bash", "-c", "id"]) # (False, '')
_is_dangerous_command(["timeout", "60", "bash", "-c", "id"]) # (False, '')Fix: re-check for -c/-e on the nested interpreter, or restrict the shell allowlist here to script-file invocations only (no -c/-e in the remaining argv).
There was a problem hiding this comment.
additional check is added. The tests are added.
| with self._storage_lock: | ||
| results = self._violations[:] | ||
|
|
||
| for violation in results: |
There was a problem hiding this comment.
Classic iterate-and-remove bug — filter results will be incomplete
results.remove() inside for violation in results causes Python's iterator to skip the element immediately after each removal. Filters will silently return the wrong set.
if session_id:
results = [v for v in results if v.session_id == session_id]
if pattern:
results = [v for v in results if v.pattern == pattern]
if category:
results = [v for v in results if v.category == category]
if severity:
results = [v for v in results if v.severity == severity]| Returns: | ||
| A tuple of (has_dangerous_paths, reason_message). | ||
| """ | ||
| write_commands = {"rm", "touch", "cp", "mv", "mkdir", "mkfifo", "mknod", "tee"} |
There was a problem hiding this comment.
write_commands is missing several filesystem-write commands
chmod, chown, ln, and dd aren't in this set, so the dangerous-path checks never fire for them. A few things that currently slip through:
chmod 777 /etc/passwdln -sf /etc/passwd /allowed/path/link(symlink escape out of the allowed fence)dd if=/dev/urandom of=/boot/vmlinuz
Same set is duplicated on line 415 — both need updating.
There was a problem hiding this comment.
Additional write commands are added. Add "ln" command handling.
| ] | ||
|
|
||
|
|
||
| def check_all_patterns( |
There was a problem hiding this comment.
This framework isn't connected to the execution path
shell.py only imports record_bash_violation from _bash_audit — check_all_patterns() is never called from bash_executor(). The tests in test_bash_guardrails.py all pass, but none of it affects whether a command is actually blocked at runtime.
If this is intended as the extensible pattern layer, wire check_all_patterns(argv) into _is_dangerous_command(). If it's documentation of the threat model, the disconnect from the execution path should be made explicit.
There was a problem hiding this comment.
wire check_all_patterns is wired into the execution path by updating _is_dangerous_command to use the pattern framework.
|
|
||
|
|
||
| def example_3_with_working_dir() -> None: | ||
| """Example 3: Restrict write validation and execution cwd to a directory.""" |
There was a problem hiding this comment.
Two functions are both labelled "Example 3" — example_3_llm_with_forced_tool_use (line 85) already has that number. This one should be Example 4, with the subsequent examples renumbered.
Signed-off-by: Akihiko Kuroda <akihikokuroda2020@gmail.com>
Pull Request
Issue
Part of #1024
Description
This PR doesn't include issues mentioned in #1087
Testing
Attribution
Adding a new component, requirement, sampling strategy, or tool?
If your PR adds or modifies one of the types below, check the matching box. A checklist of type-specific review items will be posted as a comment.
NOTE: Please ensure you have an issue that has been acknowledged by a core contributor and routed you to open a pull request against this repository. Otherwise, please open an issue before continuing with this pull request.