Repository navigation
bug(tools): tmux self-pane injection guard (#5041) is bypassed by wrapper options, bundled -c, and interpreter one-liners #6563
Description
Description
The guard added for #5039 (packages/coding-agent/src/tools/tmux-self-injection-guard.ts, called from packages/coding-agent/src/tools/bash.ts:1441) refuses tmux send-keys / paste-buffer / send-prefix aimed at the agent's own pane. On current dev it does not recognize several common command shapes, so the same self-pane write gets through.
#5039 already describes this guard as mitigation, not a boundary, and lists sh -c / xargs indirection as residual paths. This issue records which shapes the shipped guard misses today, the two places in the lexer that cause most of them, and the cases that have to stay allowed if it gets tightened.
Steps to Reproduce
Checked against dev at 15846086be01cf34184b9063215d0229dcc9a027 by calling checkTmuxSelfInjection(command, { env, cwd }) directly with:
TMUX=/tmp/tmux-501/default,1,0TMUX_PANE=%47- every command targeting
-t %47(the agent's own pane)
No real tmux session was driven.
| command | result on dev |
|---|---|
tmux send-keys -t %47 x |
blocked (baseline) |
env -u FOO tmux send-keys -t %47 x |
allowed |
sudo -u root tmux send-keys -t %47 x |
allowed |
sudo -E tmux send-keys -t %47 x |
allowed |
timeout 5 tmux send-keys -t %47 x |
allowed |
nice tmux send-keys -t %47 x |
allowed |
xargs tmux send-keys -t %47 <<< x |
allowed |
bash -ce 'tmux send-keys -t %47 x' |
allowed |
bash -c -e 'tmux send-keys -t %47 x' |
allowed |
python3 -c "import os; os.system('tmux send-keys -t %47 x')" |
allowed |
node -e "require('child_process').execSync('tmux send-keys -t %47 x')" |
allowed |
t=tmux; $t send-keys -t %47 x |
allowed |
Root causes for the first two groups:
- Wrapper options (
tokenize,tmux-self-injection-guard.ts:120-127). Afterenv/sudo, the first word starting with-clears command position, and it is not restored for the rest of that command segment. So neither a flag-only option (sudo -E tmux) nor the real command after an argument-taking option (env -u FOO tmux,sudo -u root tmux) is ever seen astmux.timeout,nice,xargs,stdbuf,setsidare not inCOMMAND_WRAPPERSat all. - Bundled shell options (
collectShellPayloads,:199). The payload is only scanned when the word is exactly-c.bash -ce '…'doesn't match, and inbash -c -e '…'the word right after-cis-e, so the check fails and the real payload is never scanned.
The last three rows (interpreter one-liners, variable indirection) can't be closed by string analysis in this guard. The eval / python tools also run code that can call tmux without going through bash.ts, so that limit applies anyway.
Expected Behavior
Common wrapper and shell-option forms that still end up executing tmux send-keys against the agent's own pane should be refused the same way the bare form is, and the limits that remain (interpreters, variable indirection, the eval tool) should be documented next to the guard.
Suggested hardening, kept small:
- Model wrapper arguments with a per-wrapper table of the options that take an argument. For example
env:-u NAME,-C DIR,-S STRING.sudo:-u,-g,-h,-p,-C,-D,-r,-t,-U,-T. Alsotimeout DURATION,nice [-n N],stdbuf -o MODE,setsid. Flag-only options keep the next word in command position. - Treat
command -v/command -Vas a lookup, not an exec. - In
collectShellPayloads, accept a bundled option that containsc(e.g.-ce,-ec) and keep scanning past other flags, but only before the first operand. POSIX shells stop option parsing there. - Add a short "not covered" note to the guard and docs: interpreter one-liners, variable or
$(...)indirection, theeval/ python tools.
Cases that must stay allowed (the base allows them today, and a looser parser tends to start blocking them):
| command | why it must stay allowed |
|---|---|
env -u tmux send-keys -t %47 x |
tmux is the variable being unset. The command is send-keys |
sudo -u tmux send-keys -t %47 x |
tmux is a user name |
command -v tmux send-keys |
lookup only, nothing runs |
bash safe.sh -ce 'tmux …' |
-ce comes after the script operand, so it is a script argument |
tmux send-keys -t %99 "echo '; tmux send-keys -t %47'" |
types text into a different pane. Quoted text sent to another pane must not be re-parsed as a command |
Happy to send a PR along these lines (wrapper table + bundled -c + tests for both tables above) if that direction works for you.
Environment
- Area: Tool execution
- Platform: macOS (guard logic is platform-independent)
- gjc version: dev
15846086(coding-agent 0.18.8) - Bun: 1.4.2
Error Output
n/a (bypassed commands are not refused, so no error is produced)
Additional context
- Related: bug(tools): agent bash can write into its own tmux pane, and the injected text is recorded as a
userturn #5039, fix(tools): block tmux self-pane injection #5041 - Interpreter one-liners and the
eval/ python tools are listed for completeness only. Closing them would need a different mechanism than this guard (see options 4 and 5 in bug(tools): agent bash can write into its own tmux pane, and the injected text is recorded as auserturn #5039).
Activity
probepark commentedon Oct 10, 2026
Triage: C (needs a maintainer decision on direction) · type: bug · priority: P2
Evidence
All of the evidence below comes from the issue author in #6563. This triage did not investigate the code, reproduce the issue or run any tests.
checkTmuxSelfInjectiondirectly ondevat15846086be01cf34184b9063215d0229dcc9a027, withTMUX_PANE=%47and every command targeting-t %47. They did not drive a real tmux session. In their results, the baretmux send-keysis blocked, but 11 other forms are allowed. These includeenv -u FOO tmux …,sudo -u root tmux …,sudo -E tmux …,timeout,nice,xargs,bash -ce '…',bash -c -e '…', python/node one-liners andt=tmux; $t ….tokenize(tmux-self-injection-guard.ts:120-127), the first-option afterenv/sudoclears command position and never restores it. Also,timeout,nice,xargs,stdbufandsetsidare missing fromCOMMAND_WRAPPERS.collectShellPayloads(:199) only scans the payload when the word is exactly-c.eval/python tools never go throughbash.ts.userturn #5039 already calls this guard a mitigation, not a security boundary, and listssh -c/xargsas known gaps. That is why this is P2 and not higher. This is a hardening gap in a best-effort guard, not a broken security boundary.Decision needed: which direction?
env,sudo,timeout,nice,stdbuf,setsid,xargs).command -v/-Vas a lookup, not a command run.-coptions (-ce,-ec) only up to the first operand.$t/$(...)indirection and theeval/python tools.userturn #5039, options 4 and 5.Recommendation: option 1. It fixes the two causes behind most of the gaps with a small, testable change. The must-stay-allowed table guards against the over-blocking risk of a looser parser. Interpreter one-liners and indirection stay out of scope for this guard.
Next action
A maintainer picks one of the options above. If it's option 1, @ the author can open the PR. It should cover the wrapper table, bundled
-chandling and the not-covered note. It should include both tables from the issue as test cases and link #5039 and #5041. Merge and approval stay with the maintainers.Yeachan-Heo commentedon Oct 10, 2026
This issue has been fixed in PR #6564. The tmux self-injection guard now properly handles:
Changes:
Tests: All 33 tests in tmux-self-injection-guard.test.ts pass, including 19 new tests.
—
[repo owner's gaebal-gajae (clawdbot) 🦞]
fix(tmux-self-injection-guard): handle wrapper options and bundled sh…
chore: remove TLS environment work that belongs to PR #6552
fix(tmux-self-injection-guard): handle wrapper options and bundled sh…
chore: remove TLS environment work that belongs to PR #6552
chore(tmux-guard): trim unrelated PR changes
fix(tmux-self-injection-guard): handle wrapper options and bundled sh…
chore: remove TLS environment work that belongs to PR #6552
chore(tmux-guard): trim unrelated PR changes