Skip to content

BashTool whitelist validation skips standalone and compound commands #277

Description

@FireCollector

What version of tRPC-Agent-Python are you using?

Current main at commit 1153e7f8455f867634ac4babf0be5717152711bf.

What operating system and Python version are you using?

  • Windows 11
  • CPython 3.14.6

The affected parser uses shlex, and the underlying logic error is platform-independent.

What did you do?

I inspected BashTool._is_command_safe() and called that predicate directly with a custom whitelist. This was a static check only: none of the sample strings were passed to a shell or executed.

tool = BashTool(whitelist_commands=["echo"])

tool._is_command_safe("blocked_cmd", tool.cwd)
tool._is_command_safe("echo ok; blocked_cmd", tool.cwd)
tool._is_command_safe("echo ok && blocked_cmd", tool.cwd)
tool._is_command_safe("echo ok || blocked_cmd", tool.cwd)
tool._is_command_safe("echo ok\nblocked_cmd", tool.cwd)
tool._is_command_safe("echo ok | blocked_cmd", tool.cwd)

All six calls currently return True.

What did you expect to see?

A command should be considered safe only when every executable command segment is present in the configured whitelist.

The standalone non-whitelisted command and every compound command containing blocked_cmd should return False. A pipeline containing only whitelisted commands should continue to return True.

What did you see instead?

The whitelist is skipped for standalone commands and for the final command in a pipeline. Shell separators other than a single pipe are not treated as command boundaries.

The parser appends current_command[0] to base_commands only when it encounters |, but it never appends the final current_command after the loop. Consequently:

  • a standalone command leaves base_commands empty;
  • the final pipeline command is not checked;
  • ;, &&, ||, background execution, and newlines are not parsed as command boundaries;
  • command substitution also needs an explicit fail-closed policy.

Because _run_async_impl() uses create_subprocess_shell() after this check, the behavior can bypass the documented custom whitelist and the default outside-workdir command restriction when the optional Tool Safety Guard is disabled.

Suggested direction

  • Always validate the final parsed command segment.
  • Recognize the shell control operators supported by the tool, including pipelines and compound command separators.
  • Reject or explicitly inspect command/process substitution rather than treating it as a normal argument.
  • Fail closed when parsing cannot prove that every executable command is allowlisted.
  • Add predicate-only regression tests; the test strings do not need to be executed.

Useful cases include:

  • one allowed standalone command;
  • one denied standalone command;
  • an all-allowed pipeline;
  • a pipeline whose final command is denied;
  • ;, &&, ||, newline, and background separators;
  • command substitution.

I would like to work on a focused fix and tests if this behavior and scope are accepted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions