Report a vulnerability through GitHub's private advisory form, not a public issue.
Worth knowing before deciding whether something is a vulnerability here.
Commands are never shell-parsed. Every argument crosses to tmux as its own
argv element through ProcessBuilder, so no shell interprets it and a
semicolon, quote or backslash in a pane title is inert. CommandRequestTest
pins this.
tmux's own command grammar is not inert. tmux ends a command at a semicolon
ending any argument, so an argument built from untrusted text can add a command.
ControlClient.isCommandGroup is the library's reading of that rule; see
docs/decisions/0009-command-groups-are-transport-agnostic.md. Treat text you
did not author as data: pass it as a single argument, and do not concatenate it
into one.
tmux expands formats before any shell runs. tmux expands #{...} and
#(...) in many argument positions, and #(...) runs a command. The expansion
happens before /bin/sh sees the string, so shell quoting does not contain it —
measured on tmux 3.7d, a #(...) inside single quotes ran through both
run-shell and pipe-pane. Every argument this library composes itself passes
through TmuxFormats.literal, which doubles #. The commands a caller
composes cannot be neutralized for them, because expansion there is sometimes
the point: Pane.pipeTo, Window.displayPopup, Shell.run,
Shell.capturing, Shell.choose and Options.setExpanded take
caller-authored text, and a value interpolated into one of those needs
TmuxFormats.literal applied to it. No MCP tool reaches these positions with
model-supplied text.
Diagnostics are redacted. CommandRequest.toString reports argument counts
and a timeout, never argument values, because argv carries pane content, socket
paths and whatever a caller sent to a shell. A failure message or log line
should not become the disclosure.
A socket path is an access boundary. Anyone who can reach a tmux socket can run commands in every pane on that server. The tests keep each server under a directory of its own for isolation, not for secrecy.
A pane's typed echo is held in memory to filter it out.
libtmux-mcp's wait_for_text discounts recognized echoes of text sent by
this library. Pending input stays in memory while unsubmitted, is bounded per
pane, and expires after an hour without another write. Submitted or erased
lines expire after ten seconds; a live wait retains those echoes it has
observed until that wait ends. Tracking writes nothing to disk or logs and
sends nothing elsewhere; the text exists only for comparison with pane reads.
Every artifact the Gradle build resolves, plugins included, is checked against
the SHA-256 recorded for it in gradle/verification-metadata.xml, and a
mismatch fails the build. The build does not verify PGP signatures. A signature
proves an artifact came from whoever holds the signing key. Many artifacts on
Maven Central are signed by keys nobody has cross-certified, and trusting such a
key is only trust on first use again. The recorded checksum already detects an
artifact changed after it was reviewed, so a signature adds a key to manage
without a real gain in assurance. Adding or upgrading a dependency changes that
file, so the change shows up in review.
Before 1.0, only the latest release is supported.