Skip to content

bug: prevent tmux history paths from corrupting shell commands - #94

Merged
ALX99 merged 3 commits into
masterfrom
bug/tmux-history-shell-quote
Jul 14, 2026
Merged

bug: prevent tmux history paths from corrupting shell commands#94
ALX99 merged 3 commits into
masterfrom
bug/tmux-history-shell-quote

Conversation

@ALX99

@ALX99 ALX99 commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Bug

.local/bin/tmux-history embedded its temporary capture-file path directly inside a single-quoted shell command. A valid TMPDIR containing an apostrophe broke the generated command, so Neovim received a corrupted path and the real terminal-history file was left on disk.

Severity and impact

This is a correctness and reliability bug with a cleanup/privacy consequence. Apostrophes are valid in Unix paths. In that environment the command opens the wrong file while terminal history remains in the temporary directory.

Evidence

Relevant code: .local/bin/tmux-history.

The parent implementation interpolated the path into a shell string shaped like:

nvim '...' '...' '$file'; rm -f '$file'

That is not valid escaping when $file itself contains '. Because tmux new-window evaluates the supplied command through a shell, the apostrophe terminates the word and changes argument boundaries.

Observed with a temporary directory named tmp'quote:

nvim received corrupted history path: /tmp/.../tmpquote/tmux-history....log; rm -f /tmp/.../tmpquote/tmux-history....log

The actual capture under tmp'quote was left behind.

Reproduce

Use temporary, uncommitted tmux and nvim stubs:

  • tmux display-message prints 0.
  • tmux capture-pane prints sample history.
  • tmux new-window executes its command argument with bash -c.
  • nvim records its final argument.

Then run:

sandbox=$(mktemp -d)
mkdir -p "$sandbox/bin" "$sandbox/tmp'quote"
# Place the stubs described above in $sandbox/bin.
TMPDIR="$sandbox/tmp'quote" PATH="$sandbox/bin:$PATH" \
  .local/bin/tmux-history

Actual result before the fix: the recorded Neovim path loses the literal apostrophe and absorbs command text; the genuine capture file remains.

Expected result: Neovim receives the exact mktemp path, including the apostrophe, and that capture file is removed afterward.

Root cause

The implementation treated surrounding values with literal single quotes as general shell escaping. A value containing a single quote invalidates that quoting when the complete command is reparsed by the shell.

Fix

Build the window command with Bash printf %q for every dynamic argument:

printf -v window_command 'nvim %q %q %q; rm -f -- %q' \
  "$nvim_position" '+nnoremap <buffer> q :quit<CR>' "$file" "$file"

This retains the existing tmux-shell execution model while preserving each value as one literal shell argument.

Validation

The temporary harness was run before the committed test artifact was removed:

  • Fixed implementation: harness passed.
  • Parent implementation: exited 1 and reported the corrupted path shown above.
  • bash -n .local/bin/tmux-history passed.

GitHub comparison against master now shows only .local/bin/tmux-history changed (3 additions, 1 deletion). No test or reproduction artifact is included in the PR.

Scope

Pane capture, history trimming, Neovim position/mapping, window naming, temporary-file naming, dependencies, and unrelated scripts are intentionally unchanged. No tests, fixtures, snapshots, repro scripts, logs, or evidence-only files are added or modified.

@ALX99
ALX99 merged commit 07ae42e into master Jul 14, 2026
4 checks passed
@ALX99
ALX99 deleted the bug/tmux-history-shell-quote branch July 14, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant