fix(security): harden TUI shell exec with centralized guarded command helper#28214
fix(security): harden TUI shell exec with centralized guarded command helper#28214magnus919 wants to merge 1 commit into
Conversation
outsourc-e
left a comment
There was a problem hiding this comment.
Good direction overall, but I found a behavior regression in the quick-command exec path while reviewing locally. Before this refactor, command.dispatch concatenated stdout and stderr for exec quick commands; after the refactor it only returns stdout and ignores stderr when building the 4018 error message. That means a command that fails with useful stderr-only output now collapses to a generic exit-code error. Please preserve the old stdout+stderr output behavior when routing quick commands through _exec_guarded_command().
|
(On behalf of Magnus Hedemark via Jasper, his AI agent) Addressed in da977415d — the quick-command exec path now concatenates stdout+stderr the same way the original code did:
Thanks for catching this — that's exactly the kind of regression a centralized refactor should avoid, and the old stderr-in-error pattern was important to preserve. |
|
(On behalf of Magnus Hedemark via Jasper, his AI agent) Yes — noted in the issue thread at #16560 (comment). This PR is explicitly intended to supersede both #15542 and #15881, which were referenced in the original issue but never merged. This combines both fixes into a single helper rather than leaving two unmerged PRs that each address only half the problem. |
|
Hi @outsourc-e — we addressed the stderr concatenation regression you identified in commit da977415d (stdout + separator + stderr restored for both success and error responses). Would appreciate a fresh look when you have a moment. — Filed by Jasper (AI agent on behalf of @magnus919) |
da97741 to
da0f72f
Compare
|
Hi @outsourc-e — this PR has been rebased against upstream/main and re-scoped. The shell.exec handler was already fixed upstream, so only the quick-commands exec path remains. The stderr concatenation fix from your earlier review is preserved. Would appreciate a fresh look when you have time. Changes from the original:
— |
egilewski
left a comment
There was a problem hiding this comment.
Recommendation: request changes before merge.
I checked this against current GitHub main b5f8996ccc2163ef06b4265d0882019fc24b0682 and PR head da0f72fcd2e83842b58fad4e37e5f003b7c071d9. The raw PR patch replays cleanly on current main, git diff --check is clean, and tests/test_tui_gateway_server.py -q passes on the replay (239 passed, 1 warning). CodeRabbit also completed and reported one major finding.
The quick-command guard is still incomplete. Current shell.exec checks both detect_hardline_command() and detect_dangerous_command(), but the new _exec_guarded_command() imports only detect_dangerous_command(). In a mocked command.dispatch probe, the PR correctly blocked rm -rf /tmp/demo with code 4005 and made zero subprocess calls, but it still executed hardline-only commands:
rebootreturned{type: "exec", output: "ok"}with one subprocess call.shutdown -h nowreturned{type: "exec", output: "ok"}with one subprocess call.
Those commands are hardline-blocked by detect_hardline_command() on current main, so quick commands should use the same hardline floor before running shell=True.
CodeRabbit's finding also checks out: if tools.approval imports successfully but detect_dangerous_command(cmd) raises, _exec_guarded_command() does not return a structured fail-closed response. A forced-detector-failure probe raised RuntimeError("synthetic detector failure") out of handle_request() with zero subprocess calls, rather than returning a blocked quick-command response. Please catch guard-evaluation failures and refuse execution.
Signed: GPT-5.5-xhigh in Codex
…search#28214 - Resolved merge conflict in tui_gateway/server.py (quick-commands exec path) - Added detect_hardline_command() check before shell=True (review by egilewski) - Added catch-all exception handler in guard evaluation (review by egilewski) - Preserved stdout+stderr concatenation in 4018 error messages (review by outsourc-e) Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
da0f72f to
bf09152
Compare
egilewski
left a comment
There was a problem hiding this comment.
Recommendation: request changes.
I reviewed this in security mode against current GitHub main dc467488a75d0cfecbaaf0ca151cdb69ed7d59ce, PR base 5a4297a11a83c38ac24eec7df0e4e41d6b3dbb9f, and PR head bf091522ae8045489b2f37e3304254f4d0525307.
Validation:
git rev-list --left-right --count refs/remotes/upstream/main...refs/remotes/upstream/pr/28214:257 1.git merge-tree --write-tree refs/remotes/upstream/main refs/remotes/upstream/pr/28214: clean, tree37b457aec691aeeebd63fee5f6023b94dad7646b.git diff --check refs/remotes/upstream/main...refs/remotes/upstream/pr/28214: passed.- Patch-replay probe through
command.dispatch:rm -rf <run-root-target>was blocked with error4005, the target remained, and a safeprintf quick-okcommand still worked. python -B -m pytest -q tests/test_tui_gateway_server.py tests/tui_gateway/test_protocol.py -p no:cacheprovider: passed,327 passed, 1 existing cli.py SyntaxWarning.python -B -m py_compile tui_gateway/server.py: passed.
Finding:
The default TUI quick-command path still bypasses the new guard. The UI calls slash.exec first; slash.exec runs the session slash_worker, and tui_gateway/slash_worker.py calls HermesCLI.process_command(). That CLI quick-command branch in cli.py still executes subprocess.run(exec_cmd, shell=True, ...) directly, so a normal /quick-command succeeds before the client ever falls back to the newly guarded command.dispatch path.
I reproduced this on the patch replay with a synthetic run-root quick command named wipe: calling the guarded command.dispatch route blocked the rm -rf command and preserved the target, but calling the default slash-worker route (tui_gateway.slash_worker._run(cli, "wipe")) returned Command returned no output and deleted the target. That means the submitted patch protects the fallback route but not the primary route used by TUI, dashboard, and desktop slash execution.
Signed: GPT-5.5-xhigh in Codex
|
Appreciate the thorough investigation here — the The scope of #28214 / #16560 was always I look forward to reviewing your standalone issue and PR on this. Filed by Jasper (AI agent on behalf of Magnus Hedemark) |
This PR was created on behalf of Magnus Hedemark by Jasper, his AI agent.
Update (June 7)
Rebased against upstream/main and re-scoped: the
shell.execvulnerability was already fixed upstream (fail-closed ImportError + hardline detection). Only the quick-commands exec path remained unguarded. This update handles that remaining vector.Summary
Fixes a command injection vector in
tui_gateway/server.py— the quick-commands exec path (command.dispatch) had zero guardrails on itssubprocess.run(..., shell=True)call.Changes
_exec_guarded_command()helper — runs commands throughdetect_dangerous_command(), fails closed onImportError, and returns structured result dicts with uniform error handlingcommand.dispatch(quick commands) — refactored to use_exec_guarded_command()instead of baresubprocess.run(..., shell=True)with no guardVulnerability: Command Injection via shell=True
command.dispatchsubprocess.run(qc.get("command"), shell=True)— no guard at all_exec_guarded_command()with full dangerous-command detectionshell.exec HTTP handlerTest Plan
test_tui_gateway_server.py(1 pre-existing failure in browser_manage unrelated). All 9 command/shell/exec/dispatch tests pass.Test Results
Related