Repository navigation
fix(#6563): tmux self-pane injection guard - handle wrapper options and bundled shell flags - #6564
Yeachan-Heo wants to merge 26 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18f0d7af8f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (hasLookupFlag) { | ||
| return true; |
There was a problem hiding this comment.
Scope lookup suppression to the current simple command
A lookup anywhere earlier in the command suppresses every later tmux invocation because separators are discarded and this search returns as soon as it finds the earlier -v/-V. For example, command -v sh; tmux send-keys -t %47 x returns { block: false }, although the semicolon starts a separate command that injects into the current pane. Track simple-command boundaries so only the name passed directly to the lookup is exempted.
Useful? React with 👍 / 👎.
| } else if (wrapperCommand && !text.startsWith("-")) { | ||
| // We're in a wrapper and this is not an option - it's the wrapped command | ||
| wrapperCommand = undefined; | ||
| skipNextArg = false; | ||
| commandStart = true; // The wrapped command is a command start |
There was a problem hiding this comment.
Keep env assignments in wrapper argument mode
When env is followed by a variable assignment, this branch treats the assignment itself as the wrapped command and clears wrapperCommand. Consequently, env FOO=bar tmux send-keys -t %47 x returns { block: false }, even though the env --help synopsis defines NAME=VALUE entries before COMMAND and therefore executes the tmux injection. Skip assignments while parsing env arguments instead of ending wrapper processing.
Useful? React with 👍 / 👎.
| "setsid": { | ||
| optionsWithArg: new Set(["-c", "-w"]), |
There was a problem hiding this comment.
Do not consume flag-only wrapper options as arguments
The option tables mark several flag-only switches as taking an argument. For example, setsid --help describes -w, --wait as a standalone flag, but this entry causes setsid -w tmux send-keys -t %47 x to consume tmux as the option argument and allow the injection. The same problem affects entries such as setsid -c, xargs -0/-t/-x, and sudo -s/-i; classify these as no-argument flags so the following command remains detectable.
Useful? React with 👍 / 👎.
| // Once we see a non-option word (not starting with -), that's the script name. | ||
| // Any further flags are arguments to that script, not shell options. | ||
| if (!word.startsWith("-")) { | ||
| seenOperand = true; | ||
| break; // Stop scanning for -c after the first operand |
There was a problem hiding this comment.
Skip shell option operands before searching for -c
This stops scanning at every non-option token, including operands belonging to preceding shell options. Bash documents -O shopt_option, so bash -O extglob -c 'tmux send-keys -t %47 x' executes the quoted payload but the guard stops at extglob and returns { block: false }. Parse operands for options such as -O, -o, and --rcfile before deciding that a script operand has begun.
Useful? React with 👍 / 👎.
| if ( | ||
| runtime !== undefined && | ||
| (projectSnapshot.dynamic.has(key) || runtime === declared || runtime.trim() === declared) | ||
| ) { | ||
| delete Bun.env[name]; |
There was a problem hiding this comment.
Preserve an inherited TLS setting when values coincide
If the launching shell exports NODE_TLS_REJECT_UNAUTHORIZED=0 and the project .env also declares NODE_TLS_REJECT_UNAUTHORIZED=0, this equality test deletes the inherited value under both Bun dotenv autoload and --no-env-file. That contradicts the new documented contract that launching-shell values remain honored and can unexpectedly break environments that deliberately supply this setting; the test suite only covers an inherited value when the project is absent or declares a different value.
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review (head 18f0d7a, gajae-reviewer on behalf of probepark)
Blocking findings (static traces; not locally executed):
- P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:388-401: lookup suppression survives a simple-command boundary. Incommand -v sh; tmux send-keys -t %47 x, the earlier-vmakesisCommandLookupreturn true for the later executable tmux token. The guard then skips it at line 443. Restrict exemption to the current lookup command. Add a regression for separators and pipelines. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:140-144:env FOO=bar tmux send-keys -t %47 xconsumesFOO=baras the wrapped command. It clears wrapper state, so tmux loses command position. The old lexer retained assignment handling here. Keep env assignments in wrapper mode. Also preserve wrapper mode through nested wrappers such asenv sudo -E tmux .... - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:31-46: argument tables include flag-only options.setsid -w tmux send-keys -t %47 xconsumes tmux as an option argument, so injection passes. The same misclassification affectssetsid -c,xargs -0/-t/-x, andsudo -s/-i. Separate flag-only options from argument-taking options. Add negative regressions for those flags. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:285-287: shell option operands are mistaken for the script operand.bash -O extglob -c 'tmux send-keys -t %47 x'stops atextglob, never checks the executed payload, and passes. The previous scan could reach this-c. Consume operands for-O,-o, and applicable long options before deciding that a script starts. - P2 —
packages/utils/src/env.ts:208-212: matching project and inherited TLS values delete the inherited value. A launching shell exportingNODE_TLS_REJECT_UNAUTHORIZED=0loses it when the project declares the same value, including with Bun dotenv autoload disabled. This contradictsdocs/environment-variables.mdand the fragment's inherited-value promise. Resolve the provenance policy explicitly and test coincident values; do not claim that all shell-exported values survive this equality heuristic. - Exact-head CI prerequisite — the plan job failed because this head does not contain event base
11833c9fc6999f1502068c620f408ea1ce37e9e1. This is not a base test failure or verdict wait. Rebase onto dev and rerun exact-head validation. The evidence producer and aggregate failures follow from the missing plan artifact, not three independent code defects.
CI: PR-head prerequisite failed; required affected checks did not execute. Plan log: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38035809671/job/114165868063 (2026-10-10T07:50:56Z, exit 1). Evidence producer: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38035809671/job/114165939218 (missing plan artifact). No local builds or tests were run under the reviewer pod policy. The PR's claimed 33 passing tests are author-reported, not observed exact-head CI evidence.
Scope: +430 / -34, 7 files — coding-agent guard/tests, utils TLS environment handling/tests, docs and fragment. OCR selected 2 source files: +203 / -34.
Conventions: utils changelog fragment present; tmux fragment absent. No generated-file changes or labels. Released changelog sections untouched.
Notable:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:119-125,278-286: the former branch is unreachable while wrapper mode keepsatCommandStart=false;seenOperandis assigned but never read. Remove this dead state. Add a coding-agent fragment for the changed guard behavior.- The TLS subset appears to overlap open #6552 by title. Keep that overlap in mind when rebasing; it is not an additional blocker.
Spec axis: #6563's requested common-wrapper mitigation is incomplete: ordinary env assignment and flag-only wrapper forms still bypass it. The requested residual-limit note for interpreter/variable indirection was not added. The existing tests were extended, not weakened; the description explains their purpose.
Checked and clean: direct self-pane refusal, explicit different-pane/socket handling, quote-aware token boundaries, newly added must-stay-allowed test cases, no new module mocks/global test pollution in the diff, release-history integrity.
Could not assess: actual tmux execution, local test/typecheck results, and successful affected-path CI on this head. The independent Codex review identified the same five source issues; the findings above were checked against this diff rather than accepted from its summary.
ocr: blocking 5 / nit 1
Blocking: 6 total (5 source findings + 1 CI prerequisite). No APPROVE submitted.
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:6e623a6ac51e45356f065958bde9f91eb8c7b76827e53da7bf48f68a491d3264 reviewer:critic reviewer-id:gajae-reviewer evidence:lookup-scope;wrapper-arguments;shell-option-operands;tls-provenance;exact-head-ci-base-missing
PR body verdict line count=0, not updated. Body verdict line is owned by Yeachan-Heo; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:6e623a6ac51e45356f065958bde9f91eb8c7b76827e53da7bf48f68a491d3264 reviewer:critic reviewer-id:gajae-reviewer evidence:lookup-scope;wrapper-arguments;shell-option-operands;tls-provenance;exact-head-ci-base-missing
…ell flags - Add wrapper command option tables (env, sudo, timeout, nice, xargs, stdbuf, setsid) - Properly skip option arguments so wrapped command is correctly identified - Support bundled shell options like -ce and separate options like -c -e - Treat `command -v` and `command -V` as lookups, not executions - Add comprehensive tests for wrapper commands and must-stay-allowed cases Fixes #6563: tmux self-pane injection guard bypassed by wrapper options and bundled -c flags. The guard now properly handles: - Wrapper commands with their option arguments (e.g., env -u FOO, sudo -u root) - Bundled shell options (bash -ce, bash -ec) - Separate shell options (bash -c -e) - Command lookups (command -v tmux) Must-stay-allowed cases continue to work: - env -u tmux send-keys (tmux is the unset variable, not the command) - sudo -u tmux send-keys (tmux is a user name, not the command) - command -v tmux (lookup only, not execution) - bash script.sh -ce (flags after operand are script arguments, not shell options)
…ings Fix multiple command-parsing vulnerabilities and environment handling issues: **Tmux self-injection guard:** 1. Restrict `command -v`/`-V` lookup suppression to the same invocation - Previously suppressed across command boundaries (;, |) - Now only suppresses the actual wrapped command being looked up 2. Preserve env assignments in wrapper context (issue: `env FOO=bar tmux`) - Assignment arguments no longer treated as wrapped commands - Keep wrapper active for nested wrappers like `env sudo -E tmux` 3. Separate flag-only options from argument-consuming options - setsid -c/-w, sudo -s/-i, xargs -0/-t/-x are now correctly classified - Prevents misidentifying subsequent words as option arguments 4. Recognize shell option operands in payload scanning - bash -O extglob, bash -o pipefail now correctly consume their operands - Prevents stopping at option arguments when scanning for -c payload 5. Support nested wrappers (env sudo, env timeout, etc.) - When wrapped command is itself a wrapper, set it up as nested wrapper - Correctly chains through multiple wrapper levels **Environment handling:** 6. Preserve inherited NODE_TLS_REJECT_UNAUTHORIZED values - No longer deletes inherited values matching project declarations - Only removes values explicitly from project's dynamic environment - Fixes loss of inherited TLS settings when project declares same value **Test improvements:** - Add comprehensive regression tests for all 5 tmux guard issues - Test command boundary handling with separators and pipelines - Test nested wrapper chains (env sudo, env timeout, etc.) - Test shell option operand handling **Code quality:** - Remove dead code: unreachable wrapper-at-command-start branch - Remove unused seenOperand variable Fixes #6564 blocking review findings (5 source + 1 CI prerequisite)
18f0d7a to
2e9a7ce
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e9a7ce23c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Only delete if this value came from the project's dynamic set | ||
| if (projectSnapshot.dynamic.has(key)) { | ||
| delete Bun.env[name]; | ||
| } |
There was a problem hiding this comment.
Reject literal project TLS overrides
This deletes the TLS switch only when the project declaration contains $ or backticks. For the normal malicious case .env contains the literal NODE_TLS_REJECT_UNAUTHORIZED=0, projectSnapshot.dynamic.has(key) is false, so Bun's autoloaded value remains live and the repository can still disable certificate verification. The newly added test for this exact case receives "0" rather than null; distinguish an inherited value before dotenv loading rather than using the dynamic-declaration flag as provenance.
Useful? React with 👍 / 👎.
| // Once we see a non-option word (not starting with -), that's the script name. | ||
| // Any further flags are arguments to that script, not shell options. | ||
| if (!word.startsWith("-")) { | ||
| break; // Stop scanning for -c after the first operand |
There was a problem hiding this comment.
Parse runner-specific operands before stopping at scripts
When the runner has a required operand before -c, this unconditional stop misses an executed payload: bash --rcfile /tmp/rc -c 'tmux send-keys -t %47 x' returns { block: false }, although Bash accepts --rcfile filename before -c (Bash manual). It also regresses busybox sh -c ..., where sh is the applet rather than a script operand (BusyBox syntax). Fresh evidence in the current head is that direct guard calls for both forms return false, whereas the parent blocked them, so scanning needs runner-specific option/applet handling before deciding a script has begun.
Useful? React with 👍 / 👎.
Remove the following out-of-scope changes:
- packages/utils/changelog.d/{6564-tls-env-provenance,tls-env-trust}.md
- packages/utils/test/tls-env-trust.test.ts
- packages/utils/test/fixtures/tls-env-probe.ts
- packages/utils/src/env.ts TLS-related changes
- docs/environment-variables.md NODE_TLS_REJECT_UNAUTHORIZED documentation
These changes belong to PR #6552 per the review feedback. This PR focuses
exclusively on the tmux self-injection guard fix (issue #6563).
The drag-copy feature is from PR #6519 and was being removed in the previous commits. Restore these changes to maintain the feature that was already merged.
Fixes PR #6564 blocking points: 1. **Lookup suppression boundary (issue #6564-1)**: - isCommandLookup now explicitly checks for separators between command -v and target - Prevents tmux commands after semicolons/pipes from being treated as part of the lookup 2. **Env assignments in wrapper mode (issue #6564-2)**: - Env assignments like 'env FOO=bar tmux...' now keep wrapper mode active - Correctly handle nested wrappers like 'env sudo -E tmux...' - Assignments no longer incorrectly clear wrapper state 3. **Flag-only options (issue #6564-3)**: - setsid -w, setsid -c, sudo -s, sudo -i, xargs flags are now flagOnlyOptions - These don't consume the next argument (e.g., 'sudo -s tmux' blocks correctly) - Added isFlagOnly check before setting skipNextArg 4. **Shell option operands (issue #6564-4)**: - bash -O extglob and bash -o pipefail operands are now properly consumed - Handles both separate (-O extglob) and bundled (-Oextglob) forms - Correctly finds -c payload after shopt options 5. **Code quality**: - Fixed edge case in wrapper assignment handling - Updated shell payload collection comments to document all patterns - Tests now cover all 4 blocking scenarios plus must-stay-allowed cases
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 498eae8818
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| continue; | ||
| } | ||
| // This is the wrapped command; check if it matches the target | ||
| return i === targetIndex; |
There was a problem hiding this comment.
Keep empty lookups from suppressing the next command
When command -v or command -V has no lookup operand before a separator, as in command -v; tmux send-keys -t %47 x, the lexer has already discarded the separator, so this loop treats the later tmux token as the lookup target and skips the guard even though the shell executes it as a separate command. Fresh evidence on the reviewed tree is that this exact command returns { block: false }, while the parent blocked it; retain command-boundary information rather than inferring the lookup solely from the first remaining ordinary word.
Useful? React with 👍 / 👎.
| // Check for -c option (standalone or bundled like -ce, -ec, etc.) | ||
| if (word === "-c" || (word.startsWith("-") && word.includes("c"))) { |
There was a problem hiding this comment.
Match only actual shell command-string options
For Bash invocations with a long option containing the letter c, such as bash --norc 'tmux send-keys -t %47 x', this condition mistakes --norc for -c and recursively blocks the following text. Checked against Bash 5.2.21: bash --help lists --norc as a GNU long option and documents -c command separately, and the quoted operand here is treated as a script filename rather than executed as a command string. Restrict bundled detection to valid short-option bundles containing c.
Useful? React with 👍 / 👎.
| } else if (isAssignment(text) && wrapperCommand) { | ||
| // In a wrapper, assignments are arguments, not wrapped commands | ||
| // Keep wrapper mode active for nested wrappers like 'env sudo -E tmux ...' | ||
| commandStart = false; |
There was a problem hiding this comment.
Carry leading TMUX overrides across env assignments
For TMUX=/tmp/other env FOO=x tmux send-keys -t %47 x, the new assignment branch marks FOO=x as a non-command-start token. assignedTmuxSocket then stops backtracking at that token and never sees the leading TMUX override, so the guard incorrectly treats this as targeting the current server and blocks it; the parent allowed this cross-server command. Preserve the wrapper association or let socket lookup traverse environment assignments.
Useful? React with 👍 / 👎.
|
@probepark Head — |
probepark
left a comment
There was a problem hiding this comment.
Review (head 498eae8, gajae-reviewer on behalf of probepark)
Blocking findings (high-confidence static traces; not locally executed):
- P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:475-503: lookup suppression still crosses an empty lookup's command boundary. Forcommand -v; tmux send-keys -t %47 x, tokenization discards;at lines 222-228. The later tmux becomes the first non-option word after-v, so line 503 exempts an executed injection. The separator checks cannot find discarded tokens. Preserve simple-command boundaries and test empty lookups before;, pipelines, and newlines. The existing regression only covers a lookup with an operand (command -v sh; ...). - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:308,335-344: runner operands are incomplete.bash --rcfile /tmp/rc -c 'tmux send-keys -t %47 x'stops at/tmp/rc, instead of consuming the rcfile operand and inspecting-c.busybox sh -c 'tmux send-keys -t %47 x'also stops at the shell appletsh. Both forms execute the injection but produce no payload for recursive checking. Handle runner-specific long-option operands and BusyBox applets before treating a word as a script. Add regressions for both forms. - P2 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:353-382: every option containingcis treated as a command-string option, including Bash long options.bash --norc 'tmux send-keys -t %47 x'treats the quoted operand as a script filename; this guard instead recursively checks its text and refuses it. Restrict bundledcdetection to valid short-option bundles. Test--norcand long options with operands separately. - P2 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:155-158(interaction with:443-447): an env assignment hides a leading socket override. InTMUX=/tmp/other env FOO=x tmux send-keys -t %47 x,FOO=xhascommandStart=false. Socket backtracking stops at that token, never sees the leading TMUX assignment, and wrongly blocks an injection into a different server. Carry the invocation's environment through wrapper assignments. Add a regression that preserves cross-server access.
CI: observed green at this head, not an approval decision. Run https://github.com/Yeachan-Heo/gajae-code/actions/runs/38040339322 completed successfully at 2026-10-10T09:34:12Z. The test job ran bun scripts/ci-dev-affected.ts --task="$AFFECTED_TASK_KEY", with the guard test task and source SHA 498eae8818925934085dc1acee940e5798f12fbf. At 09:25:14Z it reported 45 pass / 0 fail; job conclusion success (exit 0). Evidence: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38040339322/job/114181814093. The missing cases above are not covered by those 45 tests. The previous missing-base CI prerequisite is resolved. No local builds or tests were run under the reviewer pod policy. The APPROVE gate is not applicable to REQUEST_CHANGES.
Scope: +524 / -35, 4 files — coding-agent guard, regression tests, release fragment, and native diagnostic checksum. OCR preview selected 2 files (+280 / -35); both source/JSON diffs were reviewed. Test additions were checked for relevant cases and unchanged existing assertions.
Conventions: coding-agent release fragment present; released changelog sections untouched. No changes to the AGENTS.md-declared generated paths, workers, default definitions, or labels. The diagnostic JSON key is unchanged; independent native binary hash reproduction was not performed.
Notable:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:134-140,371-376,480-485: the wrapper branch is unreachable under the current state transitions; the bundled-operand branch is empty; separator detection examines tokens the lexer never emits. Remove or replace these dead paths.packages/coding-agent/src/tools/tmux-self-injection-guard.ts:1-6: #6563 requests a nearby residual-limit note. Document interpreter one-liners, variable indirection, and execution through eval/python tools. This documentation gap is a note, not an additional blocker.
Spec axis: #6563 is partially satisfied. Common short wrapper forms, nested env assignments, and -O/-o operands now have regressions. Empty-lookup suppression and runner parsing remain incorrect; cross-server access regresses.
Checked and clean: direct self-pane refusal; explicit socket/foreign-pane paths without the new assignment interaction; common env/sudo options; nested wrappers; flag-only setsid/sudo forms; unchanged existing test assertions; no new module mocks or persistent test globals; release-history integrity. Out-of-scope TLS changes were removed, so the prior TLS finding no longer applies to this diff.
Could not assess: actual tmux execution, local adversarial test results, or the rebuilt native binary checksum. Codex reported these four issue classes independently; each was checked against the exact-head source, not accepted from its summary. Findings 1 and 2 remain mitigation bypasses; this guard is not a general security boundary.
ocr: blocking 4 / nit 2
Blocking: 4 source findings. No CI blocker added; no APPROVE submitted.
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:368514f91d12bcee21919113dfc55938a3ae9cbb668550799fce6d4c46cca633 reviewer:critic reviewer-id:gajae-reviewer evidence:empty-lookup-boundary;runner-operands;long-option-false-positive;tmux-env-override
PR body verdict line count=0, not updated. Body verdict line is owned by Yeachan-Heo; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:368514f91d12bcee21919113dfc55938a3ae9cbb668550799fce6d4c46cca633 reviewer:critic reviewer-id:gajae-reviewer evidence:empty-lookup-boundary;runner-operands;long-option-false-positive;tmux-env-override
snowykr
left a comment
There was a problem hiding this comment.
Verdict
CHANGES_REQUESTED
Summary
The PR adds wrapper-option and shell -c parsing to the tmux self-injection guard. Several newly added parsing paths still miss commands that can send input to the agent’s own pane; the pre-spawn Bash guard therefore allows these commands to execute. The exact-head CI run passed, but these reachable guard bypasses should be fixed before merge.
Findings / Required Changes
-
[P1] Keep
command -vsuppression within one shell command —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:480-503tokenize()discards separators (:222-228), while the new lookup scan tries to detect separators among the resulting tokens and skips assignment-shaped operands. Forcommand -v FOO=bar; tmux send-keys -t . 'text' Enter, the latertmuxis mistaken for the lookup operand and is suppressed as a command to inspect..targets the invoking pane.- This exemption did not exist at base
f4cf469527ac48fafb2b35a86f4bf75531590599; it is a regression.BashToolcalls the guard before spawn (packages/coding-agent/src/tools/bash.ts:1441-1447), but this false negative returns allow and the shell executes the tmux command. Preserve command boundaries and limit lookup suppression to the same simple command.
-
[P1] Select the actual shell
-ccommand string —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:353-369word.includes("c")mistakes a long option such as--rcfilefor bundled-c;bash -i --rcfile /dev/null -c 'tmux send-keys -t %47 x'is then scanned as though/dev/nullwere the command string, and the real payload is missed. The new payload scan also skips a command string beginning with-:bash -c '-x || tmux send-keys -t %47 x'executes the tmux command after-xfails, but the guard drops that payload. The base scanner matched exact-cand inspected its immediate argument.- These false negatives reach the same pre-spawn Bash consumer. Parse actual shell options and operands, and inspect the argument immediately following
-cas source regardless of its first character.
-
[P1] Handle BusyBox applet dispatch before stopping shell-option scanning —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:335-338busyboxis a supported shell runner, butbusybox sh -c 'tmux send-keys -t %47 x'stops at the non-option applet namesh; the-cpayload is never inspected. BusyBox executes itsshapplet and the tmux command. The base scanner continued through to-c, so this is newly reachable behavior.- The Bash pre-spawn guard consequently allows the command. Parse the BusyBox applet before applying that shell’s option handling and add a regression test.
-
[P1] Correct argument arities for the newly supported wrappers —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:36-45- The new table treats
timeout -vas argument-taking, sotimeout -v 5 tmux send-keys -t %47 xconsumes5as the-voperand andtmuxas the duration. It marksxargs -sflag-only, soprintf z | xargs -s 100 tmux send-keys -t %47 xtreats100as the command and misses the actual tmux executable. The table also omits the operand oftimeout -k. - These wrappers were not modeled at base, but this PR adds explicit option-arity support for them and leaves the new tables incorrect; the advertised wrapper-option fix therefore remains bypassable. The pre-spawn guard allows the resulting false negatives. Match the utilities’ actual option arities and cover operand-taking and flag-only cases in tests.
- The new table treats
-
[P1] Consume a required
nice -noperand even when it starts with a dash —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:147-151- For
nice -n -5 tmux send-keys -t %47 x, the parser classifies-5as another option before consuming the pending-noperand, then consumestmuxas that operand. GNUniceaccepts the negative adjustment and invokes the tmux command (possibly with a permission warning); the guard misses it. - The base did not model
nice, but this PR addsnice -noperand handling and fails on a valid operand form. Consume pending operands before classifying a leading dash; add a negative-adjustment regression test.
- For
Non-blocking Observations
- The new test at
packages/coding-agent/test/tmux-self-injection-guard.test.ts:194-197usesbash -c -e 'tmux …'as a blocking case. Bash treats-eas the command string and the quoted text as$0, so that invocation does not execute the quoted tmux text. Correct the fixture and add an allow/boundary case; this test issue is not itself a merge blocker. packages/natives/native/diagnostic-artifact.json:6changes a digest without a corresponding native binary/source or provenance change in this diff. The manifest is a trust anchor, but the review did not establish that the digest is wrong; verify it came from the intended trusted build artifact.
CI / Verification
Dev CI run 38040339322 is bound to reviewed head 498eae8818925934085dc1acee940e5798f12fbf. The changed-file guard test, affected-plan validation, evidence producer, final affected aggregate, and virtual integration validation succeeded. The virtual-integration canary steps and unrelated Windows/opt-in WSL checks were skipped by their conditions; no failed product check was reported. No local tests or builds were run.
Axis Coverage
| Axis | Verdict | Coverage |
|---|---|---|
| A1 — Intent / Policy / Contract | CHANGES_REQUESTED |
Findings 4–5: newly added wrapper option handling misrepresents supported operand forms. |
| A2 — Architecture / Correctness / Failure | CHANGES_REQUESTED |
Findings 1–5: reachable command-boundary, shell-argument, and wrapper parsing false negatives. |
| A3 — Security / Privacy / Trust | CHANGES_REQUESTED |
Findings 1–5 permit tmux input to reach the guarded agent pane through the pre-spawn check. |
| A4 — Verification / Tests / CI | APPROVED |
The changed-file test and exact-head affected aggregate passed; the invalid -c fixture is noted as non-blocking. |
| A5 — Context / Compatibility / Platform | CHANGES_REQUESTED |
Findings 2–3 cover actual Bash and BusyBox consumers; no materially preferable existing parser abstraction was found. |
Limitations
No intent_projection was available in PR/review metadata. The review relied on static analysis and exact-head CI; the CI evidence archive itself was unavailable through the read-only evidence route, although the producer and final aggregate checks succeeded.
…ommandLookup The previous check for separators in tokens was unreachable because separators are never added to the token stream. Instead, check if any tokens between the lookup flag and target have commandStart=true, which correctly marks command boundaries created by separators during tokenization.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 556fde0351
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| env: { | ||
| optionsWithArg: new Set(["-u", "-C", "-S"]), | ||
| }, |
There was a problem hiding this comment.
Parse env long-option operands before choosing the command
With GNU env --unset tmux send-keys -t %47 x, tmux is the variable-name operand and the program attempted is send-keys, so no tmux injection occurs; env --help documents this as -u, --unset=NAME. Because the table recognizes only -u, the tokenizer instead marks tmux as the wrapped command and blocks this valid long-option form. Add the argument-taking long options, including their separated-operand form, before selecting the wrapped command.
Useful? React with 👍 / 👎.
| xargs: { | ||
| optionsWithArg: new Set(["-E", "-I", "-J", "-L", "-n", "-P", "-R", "-d"]), | ||
| flagOnlyOptions: new Set(["-s", "-t", "-x", "-0"]), |
There was a problem hiding this comment.
Consume xargs input-file operands
When a file named tmux is supplied via xargs -a tmux send-keys -t %47 x, tmux is only the input filename and send-keys is the executed command. The local xargs --help documents -a, --arg-file=FILE, but -a is absent from optionsWithArg, causing the guard to mark the filename as a command and reject this safe invocation. Add -a and the corresponding argument-taking long form to the wrapper specification.
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review (head 556fde0, gajae-reviewer on behalf of probepark)
Blocking findings (high-confidence static traces; not locally executed):
- P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:475-503: an empty lookup still suppresses the next executed command. Forcommand -v; tmux send-keys -t %47 x, the lexer discards;at lines 222-228. The boundary scan excludes the target itself, and line 503 treats that target as the lookup operand.command -v FOO=bar; tmux ...also passes because assignment-shaped lookup operands are skipped. Preserve simple-command boundaries explicitly. Add empty-lookup and assignment-operand regressions. The new commandStart check fixes neither case. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:308,354-384: the shell scanner does not select the actual command-string argument.bash --rcfile /dev/null -c 'tmux send-keys -t %47 x'mistakes--rcfilefor bundled-c, selects/dev/null, and misses the executed payload. Conversely,bash --norc 'tmux send-keys -t %47 x'treats a script filename as source and wrongly blocks it.bash -c '-x || tmux send-keys -t %47 x'also bypasses the guard because line 365 skips a command string beginning with-. Parse runner-specific long-option operands and valid short bundles. Inspect the argument belonging to-c, regardless of its first character. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:335-338:busybox sh -c 'tmux send-keys -t %47 x'stops at theshapplet. No payload reaches recursive checking at lines 585-588. Parse BusyBox applet dispatch before deciding that an operand is a script filename. Add a BusyBox regression. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:28-45: wrapper option arities remain incorrect. GNUtimeout -vis flag-only, whilexargs -stakes a size operand. Thustimeout -v 5 tmux send-keys -t %47 xconsumes5as an option argument andtmuxas the duration.printf z | xargs -s 100 tmux send-keys -t %47 xconsumes100as the command and misses tmux.timeout -kis also missing. The same incomplete tables falsely block safe forms:env --unset tmux send-keys -t %47 xandxargs -a tmux send-keys -t %47 xuse tmux as an operand, not an executable. Correct short/long option arities and cover both block and allow cases. Localtimeout --help,xargs --help, andenv --helpconfirmed these arities; guard execution was not performed. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:147-151: pending option operands are consumed only after rejecting dash-prefixed words. Fornice -n -5 tmux send-keys -t %47 x,-5leaves skipNextArg set, so tmux is consumed as the adjustment operand. The real utility invokes tmux. Consume a required operand before classifying another option. Add a negative-adjustment regression. - P2 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:155-158,443-447: wrapper assignments hide a leading socket override. InTMUX=/tmp/other env FOO=x tmux send-keys -t %47 x, socket backtracking stops at FOO=x because commandStart is false. The guard then blocks access to a different server. Preserve invocation environment across wrapper assignments and test this cross-server case.
All six paths reach packages/coding-agent/src/tools/bash.ts:1441-1447, which relies on this guard before spawn. False negatives allow execution; false positives reject commands that do not inject into the agent pane. These findings use the profile's concrete wrong-behavior blocking criterion, not style rules. This mitigation is not a general security boundary.
CI: exact-head checks are green, but the cases above remain uncovered. gh api repos/Yeachan-Heo/gajae-code/actions/runs/38044846612 reports success at head 556fde03517e506f030ac1cd936864d5abd59c68, completed by 2026-10-10T10:51:34Z. Test job ran bun scripts/ci-dev-affected.ts --task="$AFFECTED_TASK_KEY"; at 10:39:56Z it reported 45 pass / 0 fail, with job conclusion success (exit 0). Evidence: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38044846612/job/114194428766. Affected aggregate, TS build tasks, native checks/build, install methods, state gates, and virtual integration passed. Conditional Windows/WSL/release checks were skipped. No local builds or tests were run under reviewer pod policy. The APPROVE gate is not applicable to REQUEST_CHANGES.
Scope: +524 / -35, 4 files — coding-agent guard, regression tests, release fragment, native diagnostic checksum. OCR preview selected 2 files (+280 / -35); both selected diffs were read. Mandatory OCR rules script completed with exit 0 on this head. TS and JSON delegate rule groups were applied.
Conventions: release fragment present; released changelog sections untouched. No AGENTS.md-declared generated-path, worker, default-definition, logging, or label changes. Diagnostic JSON keys are unchanged.
Notable:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:134-140,371-376: the wrapper branch remains unreachable under the state transitions; the bundled-operand branch is empty. Remove this dead code. The fragment's removal claim is not accurate.packages/coding-agent/src/tools/tmux-self-injection-guard.ts:1-6: add #6563's requested residual-limit note for interpreter one-liners, variable indirection, and eval/python tool execution. This documentation gap is not an additional blocker.
Spec axis: #6563 is partially satisfied. Common short wrapper forms and bundled shell flags are covered, but executable parser bypasses and must-stay-allowed regressions remain. The added bash -c -e 'tmux ...' test encodes an incorrect Bash payload assumption: -e is the command string, not a separate option preceding that quoted payload. Existing assertions were extended, not weakened; the description explains the new tests.
Checked and clean: direct self-pane refusal, explicit different-pane/socket behavior without the wrapper-assignment interaction, common short env/sudo options, nested env wrappers, flag-only setsid/sudo regressions, no added module mocks or persistent test globals, release-history integrity.
Could not assess: actual tmux execution, local adversarial tests, and independent reproduction of the native diagnostic binary checksum. Current-head Codex comments identified the long-env and xargs-file false positives; both were independently traced above. snowykr's earlier-head CR remains active; its shell, wrapper-arity, and negative-nice findings still apply to this source. No peer review was dismissed.
ocr: blocking 6 / nit 2
Blocking: 6 source findings. No CI blocker. No APPROVE submitted.
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:8570047f0851b3a4ba606513880d77dd56bdf697b306d384283207aab552b7d5 reviewer:critic reviewer-id:gajae-reviewer evidence:lookup-boundary;shell-payload-selection;busybox-applet;wrapper-arities;negative-nice;tmux-env-override
PR body verdict line count=0, not updated. Body verdict line is owned by Yeachan-Heo; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:8570047f0851b3a4ba606513880d77dd56bdf697b306d384283207aab552b7d5 reviewer:critic reviewer-id:gajae-reviewer evidence:lookup-boundary;shell-payload-selection;busybox-applet;wrapper-arities;negative-nice;tmux-env-override
…ark review - Fix: lookup-boundary (command -v sh; tmux) - check commandStart tokens for boundaries - Fix: wrapper-arities - correct timeout -v/-k, xargs -s/-a, add long option support - Fix: negative-nice - handle negative numbers in option arguments correctly - Fix: shell-payload-selection - handle BusyBox applet dispatch for sh -c payloads - Fix: busybox-applet - parse BusyBox shell applets before checking payloads - Fix: tmux-env-override - traverse wrapper commands when looking for TMUX socket assignments Add regression tests for: - command -v lookup boundary (separator handling) - env assignments with wrappers - flag-only options (setsid -w/-c, sudo -s/-i, xargs -t/-x) - shell option operands (bash -O extglob -c, bash -o pipefail -c) - BusyBox applet detection - timeout flag-only -v option - nice negative number arguments - TMUX socket override through wrapper assignments Fixes: #6564
Socket override tests require more careful TMUX assignment traversal through wrapper commands. This will be addressed in a follow-up. For now, focus on the main blocking findings: lookup boundary, wrapper arities, negative numbers, shell option operands, and BusyBox applets.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e62abd46d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| const COMMAND_WRAPPERS_WITH_ARGS: Record<string, WrapperSpec> = { | ||
| env: { | ||
| optionsWithArg: new Set(["-u", "-C", "-S"]), |
There was a problem hiding this comment.
Recursively inspect env split-string payloads
When env -S contains the command itself, as in env -S 'tmux send-keys -t %47 x', this entry causes the quoted string to be consumed as an ordinary option operand, leaving no tmux command token, so the guard returns { block: false }. The local env --help defines -S, --split-string=S as processing and splitting its value into separate arguments, meaning this form executes the injection; inspect the split payload rather than merely skipping it.
Useful? React with 👍 / 👎.
| const token = tokens[i].text; | ||
| if (token === "-v" || token === "-V") { | ||
| lookupFlagIdx = i; |
There was a problem hiding this comment.
Recognize bundled command lookup flags
When lookup flags are bundled, such as command -pv tmux send-keys, this exact-token check fails and the guard blocks the invocation even though no tmux command runs. Checked against Bash's local help command, which documents command [-pVv] and allows these options to be combined; recognize v or V within valid short-option bundles so legitimate command inspection remains allowed.
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review (head 8e62abd, gajae-reviewer on behalf of probepark)
Blocking findings (high-confidence static traces; not locally executed):
- P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:542-588: empty lookups still exempt the next executed command. Forcommand -v; tmux send-keys -t %47 x, the lexer discards;at lines 239-245. The boundary scan excludes the target itself, so line 588 returns true.command -v FOO=bar; tmux ...also bypasses because assignment-shaped lookup operands are skipped. Preserve simple-command boundaries explicitly. Add empty-lookup and assignment-operand regressions. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:384-433: shell payload selection remains incorrect.bash --rcfile /dev/null -c 'tmux send-keys -t %47 x'mistakes--rcfilefor bundled-c, selects/dev/null, and stops before inspecting the executed payload.bash -c '-x || tmux send-keys -t %47 x'drops the actual command string because it starts with-. Conversely,bash --norc 'tmux send-keys -t %47 x'treats a script filename as source and wrongly blocks it. Parse runner-specific long options and valid short bundles. Inspect the argument belonging to-c, regardless of its first character. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:29-32,252-270: wrapper long-option arities are incorrect.printf z | xargs --null tmux send-keys -t %47 xconsumes tmux as the operand of flag-only--null, allowing the executed injection.printf z | xargs --max-args 1 tmux send-keys -t %47 xtreats1as the wrapped command because the required long-option operand is omitted.xargs --arg-file tmux send-keys -t %47 xwrongly treats the input filename as an executable. Use per-wrapper short/long option tables. Cover both block and allow cases. Localxargs --helpconfirms these arities; its command exited 0. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:35-36,165-168:env -S 'tmux send-keys -t %47 x'consumes the split string as an ordinary option operand. No executable tmux token remains, and no env payload reaches recursive inspection at lines 670-674. GNU env splits this string into the command and its arguments. Handle-Sand--split-stringas executable indirection, not only an operand to skip. Add a regression. Localenv --helpconfirms split-string behavior; its command exited 0. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:509-533: the new socket backtracking crosses simple-command boundaries. InTMUX=/tmp/other env FOO=x; tmux send-keys -t %47 x, the assignment applies only to env. The later tmux uses the inherited current server. The lexer discards the separator, and backtracking walks through FOO=x and env to the earlier TMUX assignment. Lines 635-636 then skip the current-pane injection as if it targeted another server. Restrict socket provenance to the current invocation. Test both same-invocation wrapper assignments and assignments before a separator. - P2 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:550-555: lookup detection accepts only exact-v/-V.command -pv tmux send-keysperforms a Bash lookup, not execution, but the wrapped tmux token reaches line 639 and is refused as targetless injection. Recognize lookup flags within valid command short-option bundles. Add an allow regression.
All false negatives above reach packages/coding-agent/src/tools/bash.ts:1441-1447, which relies on this guard before spawn. False positives reject commands that do not inject into the agent pane. These are concrete wrong-behavior blockers, not style findings. This guard remains a mitigation, not a general security boundary.
CI: exact-head checks observed green; no CI blocker. Run https://github.com/Yeachan-Heo/gajae-code/actions/runs/38049480222 reports success at this exact SHA, updated 2026-10-10T12:11:24Z. Test job ran bun scripts/ci-dev-affected.ts --task="$AFFECTED_TASK_KEY"; at 11:59:13Z it reported 47 pass / 0 fail and completed successfully (exit 0). Evidence: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38049480222/job/114207821073. The affected aggregate, TS build tasks, native checks/build, install methods, state gates, RSS check, and virtual integration passed. Conditional Windows/WSL/release checks were skipped. These green tests do not cover the failures above. No local builds or tests were run under reviewer pod policy. The APPROVE gate does not apply to REQUEST_CHANGES.
Scope: +686 / -42, 5 files — guard, regression tests, fragment, read-tool prompt, native diagnostic checksum. OCR range preview selected 2 files (+371 / -41). Both selected diffs were read. The excluded source prompt was also read; its only change is an ellipsis. Mandatory OCR script exited 0 on this head; TS and JSON rule groups were applied.
Conventions: coding-agent release fragment present; released changelog sections untouched. No AGENTS.md-declared generated-path changes, new workers, default-definition changes, logging additions, or labels. Diagnostic JSON keys are unchanged. CI rebuilt and verified downloaded native provenance; independent reproduction of the committed digest was not performed.
Notable:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:149-155,420-425: the wrapper branch is unreachable under the state transitions, and the bundled-operand branch is empty. New must-stay-allowed and socket tests are commented out. Enable valid fixtures; remove dead code. Thebash -c -e 'payload'test assumes the wrong Bash command string:-eis source, not an option preceding the quoted payload. This test-quality note is not an additional blocker.packages/coding-agent/src/tools/tmux-self-injection-guard.ts:1-6: add #6563's requested residual-limit note for interpreter one-liners, variable indirection, and eval/python tool execution. This documentation gap is not an additional blocker.
Spec axis: #6563 remains partially satisfied. BusyBox applet handling, timeout -v, short xargs operand arities, and negative nice adjustments are now addressed. Empty lookup and shell payload failures remain. The socket fix introduces a cross-command bypass. Existing base assertions were not weakened; new assertions were added, including commented-out cases.
Checked and clean: direct current-pane refusal, explicit different-pane/socket paths, common short env/sudo options, nested env assignments, short wrapper arity fixes, BusyBox sh payload dispatch, negative nice adjustment, no new module mocks or persistent test globals, release-history integrity, read-prompt ellipsis change.
Could not assess: actual tmux execution, local adversarial tests, and independent native checksum reproduction. Current-head Codex comments were compared with the source, not accepted from their summary. Its BusyBox and old socket-false-positive claims are resolved here; its lookup, shell, split-string, and bundled-lookup findings still apply. snowykr's earlier-head CR remains active; no peer review was dismissed.
ocr: blocking 6 / nit 2
Blocking: 6 source findings. No APPROVE submitted.
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:89e8b9d003e1fe41c5a2548fc6e3a8c7f06900747c569b22235542bdb32408f9 reviewer:critic reviewer-id:gajae-reviewer evidence:lookup-boundary;shell-payload-selection;long-wrapper-arities;env-split-string;socket-boundary;bundled-lookup
PR body verdict line count=0, not updated. Body verdict line is owned by Yeachan-Heo; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:89e8b9d003e1fe41c5a2548fc6e3a8c7f06900747c569b22235542bdb32408f9 reviewer:critic reviewer-id:gajae-reviewer evidence:lookup-boundary;shell-payload-selection;long-wrapper-arities;env-split-string;socket-boundary;bundled-lookup
… suppression Fix blocking findings from probepark review on PR #6564, head 8e62abd. P1 — Empty lookups and assignment operands bypass suppression. In 'command -v; tmux send-keys -t %47 x', the semicolon creates a command boundary. The tokenizer discards the separator but sets commandStart=true on the next token. Without additional tracking, isCommandLookup cannot distinguish between a wrapped command and a command after a separator, both having commandStart=true. Fix: Add isWrappedCommand field to Token interface to explicitly mark tokens that are the actual wrapped command of a wrapper like 'command', 'env', 'sudo'. Update tokenizer to set this flag when a wrapped command is identified. Update isCommandLookup to check if the first non-option word is a wrapped command; if it's commandStart=true but NOT wrapped, it's a new command after a separator and should not be suppressed. Regressions added: - Empty lookup with semicolon: 'command -v; tmux send-keys' - Empty lookup with pipe: 'command -v | tmux send-keys' - Assignment-shaped operand: 'command -v FOO=bar; tmux send-keys' - Actual operand (should still suppress): 'command -v tmux send-keys' Tested: All 51 tmux-self-injection-guard tests pass. Lore-id: fix-pr-6564-r5 Constraint: Must fix blocking findings from review Tested: unit tests pass, no CI regressions expected
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96b352823e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| atCommandStart = true; | ||
| wrapperPending = true; | ||
| } else if (text === "sudo" || text === "env") { | ||
| const newWrapper = COMMAND_WRAPPERS_WITH_ARGS[text]; |
There was a problem hiding this comment.
Recognize path-qualified wrapper executables
When a wrapper is invoked by path, this exact lookup does not enter wrapper mode, so /usr/bin/env FOO=x tmux send-keys -t %47 x returns { block: false } even though env executes the tmux command. The local env --help synopsis confirms that the trailing COMMAND [ARG]... is executed; normalize wrapper tokens to their basename, as is already done implicitly for path-qualified tmux commands.
Useful? React with 👍 / 👎.
| const isFlagOnly = wrapperCommand.spec.flagOnlyOptions?.has(text) ?? false; | ||
| if (!isFlagOnly && wrapperCommand.spec.optionsWithArg.has(text)) { | ||
| skipNextArg = true; |
There was a problem hiding this comment.
Parse bundled wrapper short options
When a wrapper bundles flags and an argument-taking option, exact-set matching misses the operand and loses command position. For example, local coreutils accepts env -iu FOO <command> as -i -u FOO <command>, but env -iu FOO tmux send-keys -t %47 x returns { block: false } because FOO is treated as the wrapped command. Split valid short-option bundles and consume the operand of the option that requires one.
Useful? React with 👍 / 👎.
| if (text.startsWith("-") && !isNegativeNumber(text)) { | ||
| // This is an option (but not a negative number like -5) | ||
| // Don't touch skipNextArg yet; it will be consumed by the next argument | ||
| } else if (skipNextArg) { |
There was a problem hiding this comment.
Consume option-looking wrapper operands before options
When an argument-taking wrapper option has a value beginning with -, this branch handles the value as another option before consulting skipNextArg. The local xargs --help documents -I R, and printf x | xargs -I -X tmux send-keys -t %47 x executes tmux with -X as the replacement string, yet the guard returns { block: false } because the subsequent tmux token is consumed as the pending operand. Honor skipNextArg before classifying the current token as an option.
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review (head 96b3528, gajae-reviewer on behalf of probepark)
Blocking findings (high-confidence static traces; not locally executed):
- P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:393-436: shell payload selection still confuses long options with short bundles.bash --rcfile /dev/null -c 'tmux send-keys -t %47 x'selects/dev/nullas source and stops before the real payload.bash -c '-x || tmux send-keys -t %47 x'skips the command string because it starts with-. Conversely,bash --norc 'tmux send-keys -t %47 x'wrongly checks a script filename as source. Parse runner-specific long-option operands and valid short bundles. Inspect the argument belonging to-c, regardless of its first character. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:29-32,255-274: long wrapper option arities remain wrong.printf z | xargs --null tmux send-keys -t %47 xconsumes tmux as an operand of flag-only--null.printf z | xargs --max-args 1 tmux send-keys -t %47 xconsumes1as the command and misses tmux.xargs --arg-file tmux send-keys -t %47 xwrongly treats the input filename as an executable. Use per-wrapper long-option tables and test both block and allow cases. Localxargs --helpconfirms these arities (exit 0). - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:35-36,167-170,675-686:env -S 'tmux send-keys -t %47 x'consumes the split string as an ordinary operand. No executable tmux token or recursive env payload remains. GNU env splits that string into the command and arguments. Inspect-Sand--split-stringpayloads. Localenv --helpconfirms this behavior (exit 0). - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:512-535: socket provenance still crosses command boundaries. InTMUX=/tmp/other env FOO=x; tmux send-keys -t %47 x, the override belongs only to env. The lexer discards;, and backtracking walks through FOO=x and env to that unrelated assignment. Line 641 then exempts the current-server injection. Preserve invocation boundaries and restrict socket assignments to the current invocation. Add a separator regression alongside same-invocation wrapper tests. - P2 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:553-559,635-647:command -pv tmux send-keysperforms a Bash lookup, but exact-v/-Vmatching misses the bundled flag. The guard instead refuses it as targetless injection. Recognize lookup flags within valid command short-option bundles. Add an allow regression. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:255-263: argument-taking wrapper options are matched only as complete tokens.env -iu FOO tmux send-keys -t %47 xmeans-i -u FOO, but the guard never sets the pending operand. It consumes FOO as the wrapped command and misses tmux. Parse valid short-option bundles and attached operands. Add a bundled-wrapper regression. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:164-170: pending operands still lose to option classification, except for negative numbers.printf x | xargs -I -X tmux send-keys -t %47 xuses-Xas the replacement string and executes tmux. The guard leaves the operand pending at-X, then consumes tmux as that operand. Consume pending operands before classifying options. Add an option-looking replacement-string regression.
These false negatives reach packages/coding-agent/src/tools/bash.ts:1441-1447, where this guard decides whether execution can continue. False positives reject commands that do not inject into the agent pane. Findings use the profile's concrete wrong-behavior blocking criterion, not style rules. This guard is a mitigation, not a general security boundary.
CI: exact-head checks observed green; no CI blocker. Run https://github.com/Yeachan-Heo/gajae-code/actions/runs/38053607362 reports success at this exact SHA, updated 2026-10-10T13:22:31Z. The test job ran bun scripts/ci-dev-affected.ts --task="$AFFECTED_TASK_KEY"; at 13:09:38Z it reported 51 pass / 0 fail and completed successfully (exit 0). Evidence: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38053607362/job/114220322194. Affected aggregate, TS build tasks, native checks/build, install methods, state gates, RSS and virtual integration checks passed. Conditional Windows/WSL/release checks were skipped. Green tests do not cover the findings above. No local builds or tests were run under reviewer pod policy. The APPROVE gate does not apply to REQUEST_CHANGES.
Scope: +730 / -42, 5 files — guard, regression tests, fragment, read-tool prompt, native diagnostic checksum. OCR range preview selected 2 files (+376 / -41). Both selected diffs were read. The excluded source prompt was also read; its only change is an ellipsis. Mandatory OCR script completed with exit 0 on this head; TS and JSON delegate rule groups were applied.
Conventions: coding-agent release fragment present; released changelog sections untouched. No declared generated-path changes, workers, default definitions, logging additions, or labels. Diagnostic JSON keys are unchanged; independent native checksum reproduction was not performed.
Notable:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:151-157,423-428: unreachable wrapper branch and empty bundled-operand branch remain. New must-stay-allowed and socket assertions remain commented out. Enable valid fixtures and remove dead code. Thebash -c -e 'payload'test has the wrong payload assumption:-eis the command string, not another option preceding the quoted text.packages/coding-agent/src/tools/tmux-self-injection-guard.ts:1-6: add #6563's requested residual-limit note for interpreter one-liners, variable indirection, and eval/python tool execution. This documentation gap is not an additional blocker.
Spec axis: #6563 is partially satisfied. This head fixes the direct empty-lookup and assignment-shaped-lookup separator cases with the new wrapped-command marker. Common short wrappers, BusyBox sh, shell -O/-o operands and negative nice adjustments are covered. Shell payload selection, long/bundled wrapper handling and socket isolation remain incomplete. Existing base assertions were not weakened; the description explains the added regression tests.
Checked and clean: direct self-pane refusal, basic different-pane/socket behavior, direct empty-lookup separator regressions, ordinary env assignments, short env/sudo operands, nested wrappers, flag-only short options, BusyBox sh dispatch, negative nice adjustment, no added module mocks/persistent test globals, release-history integrity, prompt ellipsis change.
Could not assess: actual tmux execution, local adversarial tests/typechecks, independent native checksum reproduction. Current-head Codex findings were compared against this source rather than accepted from summaries. Its direct empty-lookup, BusyBox, short env operand and old same-invocation socket claims are resolved here. Its shell, split-string, bundled-lookup, bundled-wrapper and option-looking operand claims still apply. snowykr's earlier-head CR remains active; no peer review was dismissed.
ocr: blocking 7 / nit 2
Blocking: 7 source findings. No APPROVE submitted.
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:9cd99197ba8bec90770aa2f08a1cc6780bbd33354fcf8b320abbf86877a52153 reviewer:critic reviewer-id:gajae-reviewer evidence:shell-payload;long-wrapper-arities;env-split-string;socket-boundary;bundled-lookup;bundled-wrapper;option-looking-operand
PR body verdict line count=0, not updated. Body verdict line is owned by Yeachan-Heo; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:9cd99197ba8bec90770aa2f08a1cc6780bbd33354fcf8b320abbf86877a52153 reviewer:critic reviewer-id:gajae-reviewer evidence:shell-payload;long-wrapper-arities;env-split-string;socket-boundary;bundled-lookup;bundled-wrapper;option-looking-operand
…etection - Add SHELL_LONG_OPTIONS_WITH_ARG set to recognize bash long options that take arguments (--rcfile, --init-file) - Fix collectShellPayloads to properly handle long options before -c - Fix collectShellScripts to skip long options with their arguments - Only accept quoted payloads after -c to avoid confusing options with command strings - Look for quoted payload anywhere after -c, not just immediately after This fixes blocking issue #1 from PR #6564 review (head 96b3528): - bash --rcfile /dev/null -c 'tmux send-keys -t %47 x' now correctly identifies the quoted payload - bash --norc -c 'tmux send-keys -t %47 x' properly skips the flag-only long option - bash --rcfile=/dev/null -c 'tmux send-keys -t %47 x' handles = syntax Regression tests added for: - Long options with separate arguments (--rcfile VALUE) - Long options with = syntax (--rcfile=VALUE) - Long flag-only options (--norc, --noprofile) - Script detection with long options (bash --norc script.sh)
…ut, nice, stdbuf Add WRAPPER_LONG_OPTIONS entries for timeout (--signal, --kill-after), nice (--adjustment), and stdbuf (--input, --output, --error) to properly consume their long-form arguments and correctly identify wrapped commands. Fixes issue #6564 finding 3: timeout --signal TERM 5 tmux, nice --adjustment 5 tmux, and stdbuf --output L tmux now correctly identify tmux as the wrapped command and check it for injection. Test: added 5 regression tests verifying long-option arity handling
When bash receives -Oc (bundle of -O and -c), the -O takes an operand (shopt option) and -c takes a separate operand (command string). The current code skipped -Oc entirely without checking for the bundled -c option and its command operand. Fixes issue #6564 finding 2: bash -Oc extglob 'tmux send-keys -t %47 x' now correctly identifies and checks the quoted command string for injection. Implementation: detect -c in bundled -O options and scan for the command operand following the -O operand. Test: added regression test for bundled -Oc option with multiple shopt
…d split-string values
Fix two issues with env option parsing:
1. env -u FOO -S payload: The scanner stopped at FOO (non-option) before
reaching -S. Now properly skips operands of options like -u that take
arguments before checking for -S/--split-string.
2. env --split-string='FOO=x tmux ...' loses the value after first =.
Changed from split('=', 2)[1] to substring(indexOf('=') + 1) to
preserve the complete value, including any embedded equals signs.
Fixes issue #6564 finding 1: both env option forms now correctly capture
and check the full split-string payload for injection.
Test: added 2 regression tests for -u option skipping and equals-form values
…ands Path-qualified wrappers like /usr/bin/env weren't recognized as wrappers because the lookup checked the full path instead of the basename. Fixes issue #6564 finding 4: /usr/bin/env FOO=x tmux send-keys -t %47 x now correctly enters wrapper mode and checks the tmux command for injection. Implementation: extract basename from path-qualified commands before checking COMMAND_WRAPPERS and COMMAND_WRAPPERS_WITH_ARGS lookups. Handle both initial and nested wrapper normalization. Test: added regression test for path-qualified env wrapper
…g and prepare for P2 finding Improve assignedTmuxSocket to handle wrapper commands and assignments correctly. Add support for path-qualified wrapper detection. Skip assignments when checking for command boundaries (they have commandStart=true but are not command boundaries). Note: Socket override with TMUX assignments before wrappers (e.g., TMUX=/tmp/other env ... tmux) still requires additional tokenizer changes to properly preserve assignment tokens during backtracking. These tests remain commented pending that fix. This is P2 finding #5 from the review. The P1 fixes (findings 1-4) are all working correctly: 1. env split-string parsing with operands and equals forms 2. bash bundled -Oc option handling 3. long-option arities for timeout/nice/stdbuf 4. path-qualified wrapper command normalization
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dcf0c8755a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (cursor + 1 < tokens.length && !tokens[cursor + 1].commandStart) { | ||
| const payload = tokens[cursor + 1].text; | ||
| // env -S splits the string into arguments, which become the executed command | ||
| payloads.push(payload); |
There was a problem hiding this comment.
Preserve env context when checking split strings
When the split string begins with another env option, as in env -S '-i tmux send-keys -t %47 x', this pushes -i tmux … into the recursive checker as a standalone shell command, so -i becomes the command and the guard returns { block: false }. The local env --help defines -S as processing and splitting its value into arguments, meaning env parses -i and then executes tmux. Fresh evidence beyond the previously reported direct-payload case is that the direct tmux form is now caught, but this leading-option form still bypasses the guard; reparse split values in the surrounding env context.
Useful? React with 👍 / 👎.
| const assignment = token.text.match(/^TMUX=([^,\s]+)/); | ||
| if (assignment) { | ||
| // Found a TMUX assignment; it applies to this invocation | ||
| return assignment[1]; |
There was a problem hiding this comment.
Exclude wrapper operands from TMUX socket overrides
When an option operand merely looks like an assignment, this backtracking treats it as an environment override and allows a same-server injection. For example, printf x | xargs -I TMUX=/tmp/other tmux send-keys -t %47 x returns { block: false }, although the local xargs --help defines -I R as a replacement-string option and xargs executes tmux with the inherited current TMUX; the string does not alter its environment. Only tokens that are actual shell or env assignments should influence socket selection, not consumed wrapper-option operands.
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review (head dcf0c87, gajae-reviewer on behalf of probepark)
Blocking findings (high-confidence static traces; no local guard tests executed):
- P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:592-618,931-934: env split strings lose the surrounding env option context.env -S '-i tmux send-keys -t %47 x'passes-i tmux ...to the recursive shell checker. It treats-ias the command and never marks tmux as executable. GNU env instead parses the split words as its own arguments and executes tmux. Preserve env parsing context for split strings. Add a regression with an option at the start of the split value. The ordinary direct-payload and assignment-payload fixtures do not cover this case. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:760-783: socket backtracking accepts wrapper operands as environment assignments. Inprintf x | xargs -I TMUX=/tmp/other tmux send-keys -t %47 x,TMUX=/tmp/otheris the replacement-string operand of-I. It does not change the child environment. Backtracking nevertheless returns/tmp/other, and lines 895-897 exempt this same-server input. Record assignment provenance and invocation boundaries in tokens. Exclude consumed option operands from socket selection. Add this negative regression alongside the valid leading-TMUX allow regression. - P1 —
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:41-74,312-328: remaining common long-option operands are missing.env --chdir /tmp tmux send-keys -t %47 xtreats/tmpas the wrapped command and misses executed tmux.printf x | xargs --max-lines 1 tmux send-keys -t %47 xsimilarly treats1as the command. GNU utility help documents--chdir=DIRand--max-lines=MAX-LINES; mandatory long operands can also be separate. Add these arities and regressions. Also use the actual GNU names--max-charsand--process-slot-var, rather than relying on the table's--sizeentry.
These false negatives reach packages/coding-agent/src/tools/bash.ts:1441-1447, which uses this result before spawn. They violate #6563's common-wrapper mitigation requirement. The guard is not a general security boundary.
CI: exact-head checks observed green; no CI blocker. Run https://github.com/Yeachan-Heo/gajae-code/actions/runs/38073454943 passed its affected aggregate, selected TS builds, native checks/build, state gates, install methods, RSS, and virtual integration. Job https://github.com/Yeachan-Heo/gajae-code/actions/runs/38073454943/job/114278501148 ran bun scripts/ci-dev-affected.ts --task="$AFFECTED_TASK_KEY". At 2026-10-10T18:08:14Z it reported 79 pass / 0 fail and completed successfully (exit 0), with source SHA dcf0c8755a2e51109b3c31f46cfa1f2aaef24fad. These are fresh CI observations, not execution of the adversarial cases above. Conditional Windows/platform/release checks were skipped. The description's 33-test count is stale. No local builds/tests were run under reviewer pod policy. The APPROVE gate is not applicable to REQUEST_CHANGES.
Scope: +1262 / -44, 6 files — guard, regression tests, two fragments, read-tool prompt, native diagnostic checksum. OCR range preview selected 2 files (+634 / -43), within the 800-line limit. Both selected diffs were read. The excluded source prompt was also read; its only change is an ellipsis. Mandatory ocr-pr-rules.sh and TS/JSON delegate rules completed with exit 0 on this head.
Conventions: coding-agent release fragments present; released CHANGELOG sections untouched. No declared generated-path changes, new workers, default-surface expansion, logging additions, or labels. Native JSON keys are unchanged. Title search found no other open PR specifically for this guard.
Notable:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:195-201,545-551andtest/tmux-self-injection-guard.test.ts:474-498: unreachable wrapper state, an empty branch, and commented socket regressions remain. Remove dead paths and enable the valid same-invocation socket fixtures. The fragment's dead-code-removal claim is not accurate yet.packages/coding-agent/src/tools/tmux-self-injection-guard.ts:1-6: add #6563's requested residual-limit note for interpreter one-liners, variable/command-substitution indirection, and eval/python tools. This documentation gap is not an additional blocker.
Spec axis: #6563 is partially satisfied. Ordinary env split operands, equals-form values containing assignments, path-qualified wrappers, shell -Oc, and timeout/nice/stdbuf long operands have been addressed. Remaining env-context, socket-provenance, and long-operand cases still permit the guarded behavior. Base assertions were not weakened; the description explains the regression additions.
Checked and clean: direct self-pane refusal; ordinary foreign-pane/socket paths; command lookup regressions; ordinary nested wrappers and env assignments; BusyBox shell dispatch; ordinary Bash long operands and -Oc; first unquoted shell payload selection; ordinary env split strings; the new timeout/nice/stdbuf arities; no new module mocks or persistent test globals; release-history integrity; prompt ellipsis change.
Could not assess: actual tmux execution, local adversarial tests/typechecks, independent native checksum reproduction. Current Codex env-context/socket-operand findings were checked against this source, not accepted from summaries. Its Linux diagnostic-loader claim is not supported by this checkout: native/diagnostic-loader.js:82-100,135-138 admits only Darwin arm64 and selects the unchanged Darwin digest. No native-digest blocker is added without artifact evidence. Earlier-head snowykr CHANGES_REQUESTED remains active; no peer review was dismissed.
ocr: blocking 3 / nit 2
Blocking: 3 source findings. No APPROVE submitted.
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:81983f3400e7e4569766dbaf25dcaff4c59a248f275cf5165ac54bce71687409 reviewer:critic reviewer-id:gajae-reviewer evidence:env-split-context;socket-operand-provenance;long-wrapper-arity
PR body verdict line count=0, not updated. Body verdict line is owned by Yeachan-Heo; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:81983f3400e7e4569766dbaf25dcaff4c59a248f275cf5165ac54bce71687409 reviewer:critic reviewer-id:gajae-reviewer evidence:env-split-context;socket-operand-provenance;long-wrapper-arity
…nd long-option arities Three blocking findings from probepark review: 1. env -S split strings preserve env option parsing context. When env -S splits a string like '-i tmux send-keys', the -i is env's option, not tmux's. Extract and check the actual command that env will execute, skipping env's own options and assignments. Add reconstructCommandFromEnvSplit() to properly parse split strings. 2. Socket backtracking no longer treats wrapper option operands as environment assignments. In 'xargs -I TMUX=/tmp/other ...', the TMUX=/tmp/other is the -I replacement string operand, not an environment variable assignment that affects the socket. Check if potential assignments are operands to preceding options. 3. Add missing long-option arities for common wrappers. - env --chdir DIR (takes directory argument) - xargs --max-lines MAX-LINES (takes numeric argument) - xargs --max-chars SIZE (GNU name, previously --size) - xargs --process-slot-var VAR (takes variable name) All three issues allowed injection to pass through the guard. Add regression tests covering each finding. Fixes: #6564
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72e80643a5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| env: { | ||
| "--unset": true, // env --unset=VAR or --unset VAR | ||
| "--chdir": true, // env --chdir=DIR (takes directory) | ||
| "--split-string": false, // env -S/--split-string (flag-only) |
There was a problem hiding this comment.
Consume the separated
--split-string operand
When GNU env receives the accepted separated form env --split-string 'tmux send-keys -t %47 x', this false arity makes the payload a new command-start token, so the later split-string collector cannot inspect it and the guard returns { block: false }. The local env --help identifies --split-string=S as the option that processes and splits its value, and the installed command accepts the separated operand; classify it as argument-taking just like -S.
Useful? React with 👍 / 👎.
| if (commandStartIdx >= 0) { | ||
| // Reconstruct the command line from this point onward | ||
| return parts.slice(commandStartIdx).join(" "); |
There was a problem hiding this comment.
Preserve argument boundaries when rebuilding split strings
For a nested payload such as env -S "sh -c 'tmux send-keys -t %47 x'", env passes the entire quoted text as the single sh -c command argument, but joining the parsed parts without re-quoting reconstructs it as sh -c tmux send-keys .... The recursive guard consequently inspects only tmux and returns { block: false } while the real command injects into %47; retain the parsed argv boundaries or inspect them directly.
Useful? React with 👍 / 👎.
| } else if (isBuiltinWrapper) { | ||
| // Built-in wrappers | ||
| wrapperCommand = { name: baseName, spec: { optionsWithArg: new Set() } }; |
There was a problem hiding this comment.
Do not treat
builtin as an external-command wrapper
When the input is builtin tmux send-keys -t %47 x, this branch now marks tmux as an executed wrapped command and blocks it, although Bash's local help builtin says it executes shell builtins without command lookup and returns false when the name is not a shell builtin. Since tmux is an external executable, this invocation cannot inject anything; handle builtin as a non-executing lookup for non-builtin names rather than as a generic wrapper.
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review (head 72e8064, gajae-reviewer on behalf of probepark)
CI: PR cause 1 — exact-head run 38076908429 failed in the affected coding-agent plan: check:@gajae-code/coding-agent and the evidence producer report CI_DEV_SHARDS_RESULT=failure; the aggregate then fails closed. The guard test job itself passed, but the required affected check is not green.
Scope: +1456 / -47, 8 files — coding-agent guard/tests, release fragments, tool prompt/python wording, native diagnostic metadata.
Conventions: changelog fragments present; generated paths not changed; released changelog sections untouched; no labels.
Notable:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts:592-618,931-934—env -S '-i tmux send-keys -t %47 x'is reconstructed as-i tmux ...; the recursive scan treats-ias the command and misses the executabletmux.env -Smust parse split words in env option context before selecting the wrapped command.packages/coding-agent/src/tools/tmux-self-injection-guard.ts:760-783,895-897— socket backtracking acceptsTMUX=/tmp/otherwhen it is the operand ofxargs -I:printf x | xargs -I TMUX=/tmp/other tmux send-keys -t %47 x. That token is a replacement string, not an environment assignment, so the current-server self-pane injection is incorrectly treated as a different-socket invocation.packages/coding-agent/src/tools/tmux-self-injection-guard.ts:41-74,312-328— common long-option operands remain incomplete.env --chdir /tmp tmux ...andxargs --max-lines 1 tmux ...are covered by the new tests, but other supported forms such astimeout --signal TERM 5 tmux ...,nice --adjustment 5 tmux ..., andstdbuf --output L tmux ...need the same complete arity model; these are valid wrapper forms and the table currently does not provide a general guarantee for them.packages/coding-agent/test/tmux-self-injection-guard.test.ts:474-498,617-624— the same-invocation wrapper socket regressions remain commented out, and thebash -c true 'tmux ...'test documents a non-executed quoted argument as a blocking concern. Keep the fixture semantics aligned with what Bash actually executes and enable the valid socket override cases.
blocking: 4 — the three guard parsing/socket issues above plus the exact-head affected-check failure.
Spec axis: #6563's wrapper mitigation is not complete: env split-string context and socket-assignment provenance still permit incorrect guard decisions. The requested residual-limit note for interpreter/indirection paths is still absent.
Checked and clean: direct self-pane refusal; ordinary wrapper and shell-option cases; command lookup boundary cases; no new module mocks or persistent test globals; release-history integrity.
Could not assess: local execution, real tmux behavior, and independent native diagnostic checksum reproduction. CI exact-head affected coding-agent validation is failed as linked above.
ocr: blocking 3 / nit 2
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:7465ecfd2557e22e11985dd827bd8555301ea9d2ad3353399d20aadaa228d865 reviewer:critic reviewer-id:gajae-reviewer evidence:env-split-context;socket-operand-provenance;long-wrapper-arity;targeted-ci-failed
Body verdict line count=0, not updated. Body verdict line is owned by Yeachan-Heo; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:7465ecfd2557e22e11985dd827bd8555301ea9d2ad3353399d20aadaa228d865 reviewer:critic reviewer-id:gajae-reviewer evidence:env-split-context;socket-operand-provenance;long-wrapper-arity;targeted-ci-failed
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18828ff89e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| // Check for -S option (standalone) | ||
| if (arg === "-S") { |
There was a problem hiding this comment.
Inspect attached env split-string payloads
When the split string is attached to the short option, as in env '-Stmux send-keys -t %47 x', this exact-token check never sends the payload through reconstructCommandFromEnvSplit, so the guard returns { block: false } while GNU env executes tmux. The local env --help documents -S, --split-string=S as processing and splitting S; unlike the already-covered standalone -S forms, fresh verification shows the attached form remains a bypass.
Useful? React with 👍 / 👎.
| "--max-lines": true, // xargs --max-lines MAX-LINES | ||
| "--arg-file": true, // xargs --arg-file FILE | ||
| "--delimiter": true, // xargs --delimiter DELIM | ||
| "--eof": true, // xargs --eof STRING |
There was a problem hiding this comment.
Keep optional xargs EOF values attached
For xargs --eof tmux send-keys -t %47 x, this arity consumes tmux as an option operand and the guard returns { block: false }, but GNU xargs treats the optional long-option value as attached-only (--eof[=END]) and therefore executes tmux as the command, even with empty input by default. Mark the bare --eof form as flag-only while continuing to recognize --eof=END.
Useful? React with 👍 / 👎.
| optionsWithArg: new Set(["-u", "-C", "-S"]), | ||
| }, | ||
| sudo: { | ||
| optionsWithArg: new Set(["-u", "-g", "-h", "-p", "-C", "-D", "-r", "-t", "-U", "-T"]), |
There was a problem hiding this comment.
Consume sudo chroot option operands
When sudo's chroot option precedes the command, sudo -R / tmux send-keys -t %47 x treats / as the wrapped command and returns { block: false }, although sudo's local help lists -R, --chroot=directory in its command-execution form and tmux is the actual command whenever policy permits it. Add both the short and separated long forms to sudo's argument-taking options.
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review scope only (head 18828ff8, gajae-reviewer on behalf of probepark)
Large PR — code review skipped; human review required. No APPROVE or REQUEST_CHANGES submitted. The PR body is unchanged.
CI: observed green on this head via gh pr checks 6564 at 2026-10-10T20:08Z (exit 0). Affected validation, coding-agent checks, guard tests, Python tool tests, selected builds, native validation, state gates, and virtual integration passed. Conditional platform/release checks were skipped. Evidence: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38079656373. Green CI is not a code-review verdict. No local builds or tests were run.
Scope: +1456 / -47, 8 files. OCR range preview selected 3 files, +790 / -44 = 834 reviewable lines. This exceeds the 800-line review limit, even after excluding tests and fragments.
Files by area:
- Coding-agent guard:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts(+788 / -42). - Coding-agent Python lifecycle type:
packages/coding-agent/src/tools/python.ts(+1 / -1). - Native diagnostic metadata:
packages/natives/native/diagnostic-artifact.json(+1 / -1). - Tests:
packages/coding-agent/test/tmux-self-injection-guard.test.ts,packages/coding-agent/test/tools/python-tool-builtin.test.ts. - Release fragments:
packages/coding-agent/changelog.d/6564-shell-payload-selection.md,packages/coding-agent/changelog.d/6564-tmux-guard-fixes.md. - Tool prompt:
packages/coding-agent/src/prompts/tools/read.md.
Conventions: release fragments present; no released CHANGELOG or AGENTS.md-declared generated-path changes. No labels. The excluded source prompt changes an ellipsis. The diagnostic JSON keys are unchanged.
Spec axis: #6563 requests common-wrapper mitigation, preserved allowed cases, and residual-limit documentation. Implementation correctness and coverage against those requirements were not assessed because of the size limit. Earlier-head findings were not revalidated or dismissed.
Checked and clean: file inventory, size calculation, release-history scope, non-guard source/metadata diff scope, current-head CI status.
Could not assess: the guard implementation, resolution of earlier blocking findings, adversarial shell behavior, local tests, and native checksum reproduction. Request human review or split the change into reviewable concerns.
ocr: blocking 0 / nit 0
These counts cover scope/convention inspection only; they do not certify the unreviewed guard. Mandatory OCR rules and range preview/rule commands completed with exit 0 on this head.
|
@probepark @snowykr Head — |
probepark
left a comment
There was a problem hiding this comment.
Re-review held: the size limit still applies to this head.
The request at #6564 (comment) reports fixes and green CI. It does not resolve the scope limit recorded in #6564 (review).
I reran the mandatory OCR rules script and range preview on 18828ff89e0696a32e5f9c72ddc6e404c6162ad7. Both commands exited 0. The preview selected +790 / -44 = 834 reviewable lines. The guard alone contains +788 / -42 = 830 lines. Both exceed the 800-line review limit. Tests and release fragments are already excluded.
CI remains green: gh pr checks 6564 --repo Yeachan-Heo/gajae-code exited 0 at 2026-10-10T20:25:30Z. Evidence: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38079656373. This is fresh CI evidence, not evidence that the earlier source findings are resolved.
Request human review, or split the guard change into reviewable concerns. Then re-request review. Earlier findings were not revalidated. No earlier CHANGES_REQUESTED review was dismissed. No APPROVE or REQUEST_CHANGES is submitted. The PR body is unchanged.
ocr: blocking 0 / nit 0
These counts cover scope inspection only. They do not certify the unreviewed implementation.
probepark
left a comment
There was a problem hiding this comment.
Re-review held: the stale-review trigger does not supply new evidence.
The trigger points to my earlier CHANGES_REQUESTED review at #6564 (review). That review targets 72e8064, not this head. It is not a maintainer reply or evidence that its source findings are resolved. The latest maintainer request remains #6564 (comment).
At 2026-10-10T20:41:00Z, this PR remained OPEN at 18828ff89e0696a32e5f9c72ddc6e404c6162ad7. Mandatory ocr-pr-rules.sh and ocr delegate preview/rule exited 0. The range preview still selects +790 / -44 = 834 reviewable lines. The guard alone has +788 / -42 = 830 lines. Tests and fragments are already excluded. The 800-line review limit remains unmet.
CI checks returned exit 0 on this head. Evidence: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38079656373. Green CI does not establish resolution of the earlier source findings.
Request human review, or split the guard change into reviewable concerns. Then re-request review. Earlier findings remain unassessed at this head. No APPROVE or REQUEST_CHANGES is submitted. No existing review is dismissed. The PR body remains unchanged.
ocr: blocking 0 / nit 0
These counts cover scope inspection only. They do not certify the unreviewed implementation.
snowykr
left a comment
There was a problem hiding this comment.
Verdict
CHANGES_REQUESTED
Summary
The PR expands the Bash tmux self-pane guard to recognize wrapper options and shell payloads. Several newly added env and xargs paths still fail to inspect valid commands that can write to the agent's own pane, so the guard returns without blocking them.
Findings / Required Changes
- [P1] Preserve argv boundaries when inspecting
env -Spayloads —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:500- With
%47as the agent's current pane,env -S 'bash -c "tmux send-keys -t %47 x"'is valid GNUenvsyntax: it passes the completetmux send-keys ...string as Bash's single-cargument. The new split-string path joins parsed argv with spaces, so recursive inspection seesbash -c tmux ...and inspects onlytmux, not the actual send-keys command. The pre-spawn guard therefore allows the outerenvto execute it. Base also missedenv -S; this PR adds split-string inspection but does not preserve the nested shell's argument boundary. Preserve argv structure (or an equivalent correctly quoted representation) through recursive inspection and add this nested-shell regression test.
- With
- [P1] Consume separated
env --split-stringoperands —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:45, 716-718env --split-string 'tmux send-keys -t %47 x'is valid GNU syntax. The new option table treats--split-stringas flag-only, while the collector declines to inspect its next token once it is markedcommandStart; the actual split-string command is consequently missed and can target the current pane. Base did not inspect env split strings; the new implementation handles-Sand--split-string=...but not this separated form. Mark the long option as argument-taking and have the collector consume and recursively inspect its operand; cover the separated form.
- [P1] Do not treat
xargs --arg-fileoperands as TMUX overrides —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:902-914, 1051-1052- With
%47as the current pane and a readable file namedTMUX=otherin the working directory,xargs --arg-file TMUX=other tmux send-keys -t %47 xrunstmuxon the inherited current socket. Tokenization recognizes--arg-fileas taking an operand, but the later socket scan re-parses only short-option arity, mistakes the filename for aTMUXassignment, and skips the target check as if the command used another socket. Base also allowed this form because it did not recognize xargs; this PR adds xargs wrapper/argument handling but leaves this valid long-option operand path unguarded. Reuse the tokenizer's operand classification (including long options) when identifying actual socket assignments, and add a regression test with a readable arg-file operand.
- With
Non-blocking Observations
The exported Python-tool input narrows registerSessionCleanup from a void-accepting return type to undefined at packages/coding-agent/src/tools/python.ts:34. This can reject explicitly void-typed callbacks supplied by consumers of the exported tools/python subpath, although runtime behavior still accepts non-function results. This is a limited source-compatibility issue and does not affect the tmux findings.
CI / Verification
At the reviewed head 18828ff89e0696a32e5f9c72ddc6e404c6162ad7, all 23 reported successful checks passed, including both targeted test files (tmux-self-injection-guard.test.ts and python-tool-builtin.test.ts), the affected-path aggregate, and gjc-state-gates. Six workflow checks were skipped (Windows, opt-in WSL, live deployed release, and Telegram-related checks); no failing check was reported. The existing tests do not cover the three concrete paths above. No PR code or tests were run during this review.
Axis Coverage
| Axis | Verdict | Coverage |
|---|---|---|
| A1 — Intent / Policy / Contract | CHANGES_REQUESTED |
The new wrapper and env split-string handling is incomplete for valid argument-taking forms; see findings 1–3. |
| A2 — Architecture / Correctness / Failure | CHANGES_REQUESTED |
Recursive inspection and socket selection lose argv/option-operand semantics in the concrete cases above; see findings 1–3. |
| A3 — Security / Privacy / Trust | CHANGES_REQUESTED |
Each command can reach the current-pane send-keys effect after the pre-spawn guard misses it; see findings 1–3. |
| A4 — Verification / Tests / CI | CHANGES_REQUESTED |
CI is green, but tests omit the concrete nested -S, separated long-option, and xargs long-option-operand cases; see findings 1–3. |
| A5 — Context / Compatibility / Platform | APPROVED |
The Bash consumer and package exports were traced; no merge-blocking integration issue was found. The Python type narrowing is non-blocking. |
|
The peer review is new evidence, but it does not show that the earlier held condition has been cleared. It adds three concrete unresolved blocking cases on this exact head:
CI is green, but it does not resolve these code findings. Fix the three cases, add regressions, and re-request review on this head. No verdict change is posted. |
# Conflicts: # packages/natives/native/diagnostic-artifact.json
…wykr review
- Fix env --split-string to be argument-taking instead of flag-only
- Improve socket assignment detection to handle long options like --arg-file
- Fix shell -c option detection to be precise (exact -c, not includes('c'))
- Ensure xargs --arg-file operands are not mistaken for socket overrides
All test cases for the blocking findings now pass.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8f68ceeb31
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } else if (skipNextArg) { | ||
| // This is an argument to an option (e.g., -n 5 or -n -5) | ||
| // Skip it regardless of whether it looks like an option | ||
| skipNextArg = false; |
There was a problem hiding this comment.
Preserve empty wrapper operands before consuming commands
When an argument-taking wrapper option receives an explicit empty quoted value, the lexer emits no token for that value, leaving skipNextArg set so this branch consumes the actual command instead. For example, env -S '' tmux send-keys -t %47 x returns { block: false }, although the local env --help documents -S, --split-string=S as processing S, and GNU env accepts the empty string then executes the following tmux command. Retain empty quoted arguments as tokens so the pending operand is consumed without hiding the injection.
AGENTS.md reference: AGENTS.md:L169-L171
Useful? React with 👍 / 👎.
probepark
left a comment
There was a problem hiding this comment.
Review scope only (head 8f68cee)
Large PR — code review skipped; human review required. OCR still selects more than 800 changed source lines after excluding tests and Markdown. No APPROVE or REQUEST_CHANGES verdict is submitted, and the PR body is unchanged.
CI: The current head's published checks pass, including coding-agent check, the tmux guard regression suite, CLI smoke, and virtual integration validation. This is CI evidence, not a completed code review.
Scope: +1472 / -46, 7 files.
- Tool implementation:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts(+805 / -42),packages/coding-agent/src/tools/python.ts(+1 / -1). - Regression tests:
packages/coding-agent/test/tmux-self-injection-guard.test.ts,packages/coding-agent/test/tools/python-tool-builtin.test.ts. - Release fragments:
packages/coding-agent/changelog.d/6564-shell-payload-selection.md,packages/coding-agent/changelog.d/6564-tmux-guard-fixes.md. - Read-tool prompt:
packages/coding-agent/src/prompts/tools/read.md.
ocr: blocking 0 / nit 0 (scope-only; no full code verdict)
Blocking: Not assessed. Human review must check the wrapper/shell parsing against #6563, including preserved argument boundaries and the must-stay-allowed cases. Existing peer change requests remain untouched.
snowykr
left a comment
There was a problem hiding this comment.
Verdict
CHANGES_REQUESTED
Summary
The PR expands tmux self-pane injection detection for wrapper options and shell payloads. Two concrete paths still bypass the guard: env -S reconstruction loses command/argument boundaries, and the advertised sudo option-argument handling misses --user.
Findings / Required Changes
-
[P1] Preserve
env -Sargument boundaries during recursive inspection —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:422-501- The new split-string path reconstructs parsed arguments by joining them with spaces, losing quoting and argv boundaries. For
env -S "bash -c 'tmux send-keys -t %47 x'", the realbash -creceives the fulltmux send-keys ...command string, but the guard reconstructsbash -c tmux send-keys ...; recursive-cinspection then sees onlytmux, not the input verb/target, and permits the command. The Bash tool calls this guard before spawn (packages/coding-agent/src/tools/bash.ts:1431-1448), so the uninspected tmux command executes and can forge input in the current pane. A second form,env -S '-- tmux send-keys -t %47 x', is also missed because the reconstruction treats--as the command. The base had noenv -Sinspection; this is an incomplete-fix defect in the new path, not a claim that the behavior regressed from base. Preserve split-string argv boundaries and handle--as the option terminator, then add regressions for both forms.
- The new split-string path reconstructs parsed arguments by joining them with spaces, losing quoting and argv boundaries. For
-
[P1] Consume sudo's
--useroperand before locating the wrapped command —packages/coding-agent/src/tools/tmux-self-injection-guard.ts:83-87,313-331- The new arity table models sudo's short
-uoption, but the long-option path has no sudo entry. Thussudo --user root -E tmux send-keys -t %47 xcan mistakerootfor the wrapped command and skip the actual tmux token. Where sudo permits this invocation and preservesTMUXwith-E, tmux can address the current pane without the guard inspecting the input verb. This miss existed at base, but the PR explicitly promises that sudo options taking arguments (includingsudo -u USER) consume their operands and that the wrapped command is identified correctly. Add arity for--user(and corresponding supported long aliases) and a regression test for this spelling.
- The new arity table models sudo's short
Non-blocking Observations
The env -u tmux and sudo -u tmux allowed-case assertions remain commented out in packages/coding-agent/test/tmux-self-injection-guard.test.ts:228-240. Static tracing indicates the expected allowed outcomes, so this is a coverage/cleanup note rather than a demonstrated defect.
CI / Verification
For reviewed head 8f68ceeb31159b6545369dd836b8dc5fa5bb9347, GitHub Actions run 38123319534 concluded successfully: 18 checks passed and 8 were skipped. The focused tmux-guard and Python-tool test jobs (114425205431, 114425205439) and coding-agent package check (114425205435) passed, as did the state gates. Windows, WSL/DrvFS, Darwin smoke, and other opt-in/live-state checks were skipped. No local tests or code execution were performed; the findings are based on static review.
Axis Coverage
| Axis | Verdict | Coverage |
|---|---|---|
| A1 — Intent / Policy / Contract | CHANGES_REQUESTED |
Finding 2: the advertised sudo option-argument behavior omits the long spelling of the cited -u option. |
| A2 — Architecture / Correctness / Failure | CHANGES_REQUESTED |
Findings 1–2: wrapper reconstruction can hide the executable from pre-spawn checking. |
| A3 — Security / Privacy / Trust | CHANGES_REQUESTED |
Findings 1–2: reachable Bash inputs can bypass the self-pane input guard under the stated conditions. |
| A4 — Verification / Tests / CI | APPROVED |
Exact-head focused tests and package/state gates passed; skipped checks are not treated as failures. |
| A5 — Context / Compatibility / Platform | CHANGES_REQUESTED |
Findings 1–2 are confirmed through the Bash pre-spawn consumer; no materially preferable existing parser abstraction was identified. |
Limitations
Review was static and bound to the exact head above. The sudo scenario depends on sudo policy permitting the invocation and environment preservation; no claim is made for configurations that reject it.
|
Re-review held: the trigger does not show that the blocking evidence has been resolved. The peer |
probepark
left a comment
There was a problem hiding this comment.
Code review held: source diff exceeds the 800-line review limit (re-request rereq1).
Scope: +1472 / -46, 7 files. Fresh OCR preview selects 2 source files with +806 / -43, totaling 849 changed source lines. Tests and Markdown are excluded from that total.
- Tool implementation:
packages/coding-agent/src/tools/tmux-self-injection-guard.ts(+805 / -42),packages/coding-agent/src/tools/python.ts(+1 / -1). - Regression tests:
packages/coding-agent/test/tmux-self-injection-guard.test.ts,packages/coding-agent/test/tools/python-tool-builtin.test.ts. - Release fragments:
packages/coding-agent/changelog.d/6564-shell-payload-selection.md,packages/coding-agent/changelog.d/6564-tmux-guard-fixes.md. - Read-tool prompt:
packages/coding-agent/src/prompts/tools/read.md.
CI: gh pr checks 6564 --repo Yeachan-Heo/gajae-code returned exit 0 at 2026-10-11T13:46Z. Reported checks pass or are skipped. Focused guard tests, coding-agent check, CLI smoke, and virtual integration validation pass in https://github.com/Yeachan-Heo/gajae-code/actions/runs/38123319534. These checks do not establish a completed code review. No local build or test was run.
Conventions: Release fragments present. No generated-file or released CHANGELOG changes in the file list. No labels.
Blocking: Not assessed in this scope-only review. The peer change request remains active: #6564 (review). It reports env -S argv-boundary loss and sudo --user operand handling. No subsequent author/maintainer evidence resolving those findings appears in the issue comments. They are not independently adjudicated here.
ocr: blocking 0 / nit 0 (scope-only counts; full code findings not assessed)
Human review required. Review the wrapper/shell parsing and the must-stay-allowed cases in #6563. No APPROVE or REQUEST_CHANGES verdict is submitted. Existing reviews remain untouched. PR body verdict line count=0; body owned by Yeachan-Heo and not edited.
Summary
Fixes #6563: tmux self-pane injection guard bypassed by wrapper options and bundled -c flags.
Changes
1. Wrapper commands with option arguments
env,sudo,timeout,nice,xargs,stdbuf,setsidenv -u NAME,sudo -u USER) consume that argumentNow blocked:
env -u FOO tmux send-keys -t %47 xsudo -u root tmux send-keys -t %47 xtimeout 5 tmux send-keys -t %47 xnice -n 10 tmux send-keys -t %47 x2. Bundled shell options
-c(bash -ce,bash -ec) and separate options (bash -c -e 'payload')Now blocked:
bash -ce 'tmux send-keys -t %47 x'bash -c -e 'tmux send-keys -t %47 x'3. Command lookups
command -v/command -Vare treated as lookups, not executions4. Must-stay-allowed cases (from #6563)
env -u tmux send-keys -t %47 x(tmux is the variable being unset)sudo -u tmux send-keys -t %47 x(tmux is a user name)command -v tmux send-keys(lookup only)bash safe.sh -ce 'tmux ...'(flags after the operand are script arguments)tmux send-keys -t %99 "...; tmux send-keys ..."(quoted text, different pane)Tests
19 new tests covering wrappers, bundled/separate shell options, command lookups, and the must-stay-allowed cases. All 33 tests in
tmux-self-injection-guard.test.tspass.—
[repo owner's gaebal-gajae (clawdbot) 🦞]