fix(shell): block shell operators when blocked list is active#3045
fix(shell): block shell operators when blocked list is active#3045failsafesecurity wants to merge 1 commit into
Conversation
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
|
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. |
|
@failsafesecurity, thanks for the contribution! Can you please re-instate the PR template? |
|
Hi @failsafesecurity, thanks for the contribution! Before we can review or merge this PR, you need to sign our Contributor License Agreement — the Once the CLA is signed, the check turns green, the ⏳ 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
left a comment
There was a problem hiding this comment.
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 underblocked=["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.
|
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 |
Summary
The
ShellAdapter'sblockedparameter only matches the head command prefix. Shell operators (;,|,&&,||,$(...), backticks) bypass the blocklist. For example,blocked=["rm"]does not blockecho x; rm -rf /.The
allowedmode already blocks shell operators viacontains_shell_operator(), but theblocked-only mode does not. This PR applies the same check when ablockedlist is active.Security Impact
Medium — Users who configure
blockedwithoutallowedmay believe dangerous commands are prevented. A prompt-injected agent can bypass the blocklist using shell operators.Changes
contains_shell_operator()check in_filter()whenself._blocked is not Noneallowed-list behaviorTesting
References
blockedis not a security boundary — this fix makes it harder to bypass