Skip to content

fix(shell): block shell operators when blocked list is active#3045

Closed
failsafesecurity wants to merge 1 commit into
ag2ai:mainfrom
failsafesecurity:fix/shell-blocked-filter-shell-operators
Closed

fix(shell): block shell operators when blocked list is active#3045
failsafesecurity wants to merge 1 commit into
ag2ai:mainfrom
failsafesecurity:fix/shell-blocked-filter-shell-operators

Conversation

@failsafesecurity

Copy link
Copy Markdown

Summary

The ShellAdapter's blocked parameter only matches the head command prefix. Shell operators (;, |, &&, ||, $(...), backticks) bypass the blocklist. For example, blocked=["rm"] does not block echo x; rm -rf /.

The allowed mode already blocks shell operators via contains_shell_operator(), but the blocked-only mode does not. This PR applies the same check when a blocked list is active.

Security Impact

Medium — Users who configure blocked without allowed may believe dangerous commands are prevented. A prompt-injected agent can bypass the blocklist using shell operators.

Changes

  • Apply contains_shell_operator() check in _filter() when self._blocked is not None
  • Reject commands containing shell operators with a clear error message
  • Consistent with existing allowed-list behavior

Testing

# Before: blocked=["rm"] does not block "echo x; rm -rf /"
# After:  blocked=["rm"] blocks "echo x; rm -rf /" (shell operators rejected)

References

  • AGNT-004: Blocked command filter bypass via shell operators
  • The docstring already warns that blocked is not a security boundary — this fix makes it harder to bypass

The blocked command filter only matches the head command prefix, so
shell operators (; | && || $(...) `...) can bypass it. For example,
blocked=['rm'] does not block 'echo x; rm -rf /'.

Apply contains_shell_operator() check when a blocked list is set,
consistent with the existing allowed-list behaviour. This prevents
the most common bypass vector without changing the default
behaviour (no filtering).

Refs: AGNT-004
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Joshua Medvinsky seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@marklysze

Copy link
Copy Markdown
Collaborator

@failsafesecurity, thanks for the contribution! Can you please re-instate the PR template?

@Lancetnik Lancetnik added type:bug Existing behavior is broken or doesn't match the docs security status:needs-review PR is ready and waiting for maintainer review status:needs-cla Gate: CLA not signed; not reviewed until signed area:tools Tool system, builtin tools, subagents (ag2/tools) and removed status:needs-review PR is ready and waiting for maintainer review labels Jul 4, 2026
@Lancetnik

Copy link
Copy Markdown
Member

Hi @failsafesecurity, thanks for the contribution!

Before we can review or merge this PR, you need to sign our Contributor License Agreement — the license/cla check on this PR has the signing link. This applies to draft PRs as well: we don't want you to invest work into a change we can't accept.

Once the CLA is signed, the check turns green, the status:needs-cla label is removed, and the PR proceeds through the normal flow.

⏳ If the CLA isn't signed within 3 days, this PR will be marked stale and closed 7 days after that. Signing later and reopening is always an option.

@genisis0x genisis0x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct catch — a head-prefix blocklist genuinely doesn't stop echo x; rm -rf /, so rejecting operators while a blocklist is active does close that bypass.

One thing worth making explicit: this now rejects every shell operator whenever any blocked list is set, so a caller with blocked=["rm"] also loses benign chains like echo a && echo b. That's a real behavior change for existing blocklist users. It's defensible — it matches the restricted/allowed-list precedent you're pointing at — but it'd be good to call it out in the changelog and pin it with a test:

  • echo x; rm -rf / is now blocked under blocked=["rm"]
  • a plain blocked head (rm ...) still blocks
  • (optionally) a benign operator command is now rejected, documenting the tradeoff

The alternative — splitting on operators and re-checking each segment against the blocklist — preserves benign operators but is easy to get wrong around quoting and nested $(...), so wholesale rejection is the safer default. Just worth stating that's the deliberate choice rather than an incidental one.

@genisis0x genisis0x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please disregard this one — it duplicates a review I already left on this PR.

@Lancetnik Lancetnik added status:stale Gated item with no author activity (grace period depends on the gate); closed 7 days later and removed status:needs-cla Gate: CLA not signed; not reviewed until signed labels Jul 14, 2026
@vvlrff

vvlrff commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Closing: the CLA hasn't been signed for over two weeks, so this can't be merged regardless.

On the substance, this doesn't catch a real problem: the behavior you describe is intentional and already documented — the ShellAdapter docstring explicitly states that blocked is best-effort, only matches the head command's prefix, and is not a security boundary (use allowed, readonly, or an isolated container for that). The proposed change is also breaking: it would reject all pipes, redirects and chaining (ls | grep foo, echo x > file) for every user of blocked, which defeats the purpose of a soft blocklist.

@vvlrff vvlrff closed this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tools Tool system, builtin tools, subagents (ag2/tools) security status:stale Gated item with no author activity (grace period depends on the gate); closed 7 days later type:bug Existing behavior is broken or doesn't match the docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants