Skip to content

bug(tools): tmux self-pane injection guard (#5041) is bypassed by wrapper options, bundled -c, and interpreter one-liners #6563

Open

Description

@Oreochococukie

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,0
  • TMUX_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:

  1. Wrapper options (tokenize, tmux-self-injection-guard.ts:120-127). After env / 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 as tmux. timeout, nice, xargs, stdbuf, setsid are not in COMMAND_WRAPPERS at all.
  2. Bundled shell options (collectShellPayloads, :199). The payload is only scanned when the word is exactly -c. bash -ce '…' doesn't match, and in bash -c -e '…' the word right after -c is -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. Also timeout DURATION, nice [-n N], stdbuf -o MODE, setsid. Flag-only options keep the next word in command position.
  • Treat command -v / command -V as a lookup, not an exec.
  • In collectShellPayloads, accept a bundled option that contains c (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, the eval / 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

Activity

  1. probepark commented on Oct 10, 2026

    @probepark
    Collaborator

    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.

    • The author says they called checkTmuxSelfInjection directly on dev at 15846086be01cf34184b9063215d0229dcc9a027, with TMUX_PANE=%47 and every command targeting -t %47. They did not drive a real tmux session. In their results, the bare tmux send-keys is blocked, but 11 other forms are allowed. These include env -u FOO tmux …, sudo -u root tmux …, sudo -E tmux …, timeout, nice, xargs, bash -ce '…', bash -c -e '…', python/node one-liners and t=tmux; $t ….
    • The author traces the first two groups to two causes:
      1. In tokenize (tmux-self-injection-guard.ts:120-127), the first - option after env/sudo clears command position and never restores it. Also, timeout, nice, xargs, stdbuf and setsid are missing from COMMAND_WRAPPERS.
      2. collectShellPayloads (:199) only scans the payload when the word is exactly -c.
    • The author agrees that interpreter one-liners and variable indirection can't be closed by string analysis in this guard. They also note that the eval/python tools never go through bash.ts.
    • Context: bug(tools): agent bash can write into its own tmux pane, and the injected text is recorded as a user turn #5039 already calls this guard a mitigation, not a security boundary, and lists sh -c/xargs as 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?

    1. Small hardening PR plus a docs note (recommended). This is the author's proposal:
      • Add a table of options that take an argument for each wrapper (env, sudo, timeout, nice, stdbuf, setsid, xargs).
      • Treat command -v/-V as a lookup, not a command run.
      • Scan bundled -c options (-ce, -ec) only up to the first operand.
      • Add a "not covered" note covering interpreters, $t/$(...) indirection and the eval/python tools.
      • Test with both tables from the issue, including the five cases that must stay allowed.
    2. Docs-only. Leave the guard as it is and write down all of the reported bypass forms as known gaps next to the guard and in the docs.
    3. Defer to a different mechanism. Leave the string guard alone and track the broader fix under bug(tools): agent bash can write into its own tmux pane, and the injected text is recorded as a user turn #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 -c handling 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.

  2. Yeachan-Heo commented on Oct 10, 2026

    @Yeachan-Heo
    Owner

    This issue has been fixed in PR #6564. The tmux self-injection guard now properly handles:

    Changes:

    • ✅ Wrapper commands with option tables (env, sudo, timeout, nice, xargs, stdbuf, setsid)
    • ✅ Bundled shell options (-ce, -ec) and separate shell options (-c -e)
    • ✅ Command lookups (command -v/V treated as non-execution)
    • ✅ All must-stay-allowed cases from the issue continue to work

    Tests: All 33 tests in tmux-self-injection-guard.test.ts pass, including 19 new tests.

    —
    [repo owner's gaebal-gajae (clawdbot) 🦞]

  3. added
    bugSomething isn't working
    P2Medium priority
    on Oct 10, 2026
  4. added 8 commits that reference this issue on Oct 10, 2026
    8d28032
    3aebf1c
    74ffc72
    bd5fc0c
    d40d0fd
    d3a6d03
    95431b9
    b749032
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium prioritybugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions