Fix shell injection in TUI quick_commands and /exec slash command - #33495
Fix shell injection in TUI quick_commands and /exec slash command#33495ErnestHysa wants to merge 1 commit into
Conversation
|
@alt-glitch — noted, thanks. I've reviewed the scope of #28214 (centralized guarded command helper) and this PR. The PRs complement each other: #28214 is the long-term centralized-helper refactor; #33495 is the targeted shell injection fix in |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for targeting a real historical shell.exec concern.
Problems
- The
/execportion is already superseded: currenttui_gateway/server.py:14401-14418checks hardline and dangerous commands, fails closed when approval support is unavailable, and disconnects stdin. This landed in621bf3a873b6b466b7fca6fbd6f4c7cf83a70fdd. - The quick-command injection premise does not hold on current main.
command.dispatchreceivesargattui_gateway/server.py:11850, but executes onlyqc.get("command", "")at line 11866; the request argument is not interpolated into that command. shlex.split(...), shell=Falsewould break documented operator shell snippets.website/docs/user-guide/configuration.md:1687configurescd ... && git pull && pip install -e ., which requires shell interpretation. Current quick commands also preserve sanitized environment,stdin=subprocess.DEVNULL, and output redaction attui_gateway/server.py:11860-11881.
Suggested changes
- Do not salvage the quick-command conversion as an injection fix; retain the operator-configured shell-snippet contract and current protections.
- Drop the superseded
/execchange.
This is an automated hermes-sweeper review.
| return _err( | ||
| rid, 4005, f"blocked: {desc}. Use the agent for dangerous commands." | ||
| ) | ||
| except ImportError: |
There was a problem hiding this comment.
This changes the documented quick-command contract from operator-authored shell snippets to a single argv invocation. For example, the current configuration guide uses cd ... && git pull && pip install -e .; shlex.split() would pass && literally and break it. The RPC arg is not interpolated into qc["command"], so it is not the claimed injection channel.
| try: | ||
| r = subprocess.run( | ||
| cmd, shell=True, capture_output=True, text=True, timeout=30, cwd=os.getcwd() | ||
| shlex.split(cmd), shell=False, capture_output=True, text=True, timeout=30, cwd=os.getcwd() |
There was a problem hiding this comment.
This portion is stale against current main: shell.exec now applies both detect_hardline_command and detect_dangerous_command, fails closed if approval support is unavailable, and uses stdin=subprocess.DEVNULL before its shell execution path.
Bug (Before)
Two RPC handlers in
tui_gateway/server.pypassed user-supplied command strings directly tosubprocess.run()withshell=True:quick_commands exec (line ~4957): The
command.dispatchmethod resolved a quick-command name and executed its configured command viasubprocess.run(cmd, shell=True)— allowing shell injection through malicious command arguments./exec slash command (line ~6984): The
shell.execmethod executedsubprocess.run(cmd, shell=True)after only adetect_dangerous_commandcheck — which only blocks a known blocklist, not arbitrary shell metacharacters.Fix (After)
Both paths now:
shlex.split()into a list of argumentsshell=Falsetosubprocess.run()— preventing shell interpretation of&,|,;,$, backticks, etc.detect_dangerous_commandcheck that was missingImpact
An attacker who could send RPC requests to the TUI gateway could execute arbitrary shell commands on the host system with the privileges of the hermes-agent process. This is now prevented.