Tabby auto-confirms ZMODEM detection on terminal output, leading to shell command execution from displayed file content under fish, bash, and zsh · Advisory · Eugeny/tabby · GitHub
//repos/advisories/show" data-turbo-transient="true" />
Skip to content
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
--><br>We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
-->
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
//repos/advisories/show;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
Eugeny
tabby
Public
Uh oh!
There was an error while loading. Please reload this page.
Notifications<br>You must be signed in to change notification settings
Fork<br>4k
Star<br>71.4k
Tabby auto-confirms ZMODEM detection on terminal output, leading to shell command execution from displayed file content under fish, bash, and zsh
High
Eugeny<br>published<br>GHSA-qr3x-j8g9-xhf6<br>May 7, 2026
Package
tabby<br>(Binary)
Affected versions
Patched versions
1.0.233
Description
Tabby auto-confirms ZMODEM detection on terminal output, leading to shell command execution from displayed file content
Summary
Tabby installs a ZMODEM middleware on every terminal session that consumes all session output, auto-confirms zmodem ZRQINIT/ZRINIT detections without any in-app prompt, and writes zmodem protocol bytes back into the active PTY. Because the middleware runs on raw session output, attacker-controlled bytes printed to the terminal — for example by cat , an SSH peer, a serial device, or any process that emits a zmodem init header — cause Tabby to inject fixed protocol bytes into the session as PTY input.
After the foreground process (cat) exits, those injected bytes are interpreted by the user's shell. Under fish in its default configuration, the resulting line equals **B0100000023be50 and fish's default recursive ** glob expands an attacker-placed nested executable to a command word containing /, executing it without depending on PATH.
Bash and zsh can also be reached without PATH=. by combining the ZMODEM feedback with a terminal color-query feedback in the same displayed file. The file first causes xterm.js to answer OSC 10 ; ? with a fixed-format rgb:.../.../... string, then completes the ZMODEM header. The queued input becomes a line like 10;rgb:2e2e/2f2f/2a2a**0100000023be50; the 10 command fails, and the slash-containing rgb:.../.../... command glob expands to an attacker-placed executable path. The user takes no action other than displaying a file in the terminal.
Affected component
Package: tabby-terminal
Module: tabby-terminal/src/features/zmodem.ts (ZModemDecorator, ZModemMiddleware)
The middleware is registered as a default TerminalDecorator and is active on local PTY sessions out of the box.
Root cause
ZModemMiddleware.feedFromSession feeds every session-output chunk into a Zmodem.Sentry. The on_detect callback unconditionally calls detection.confirm() and the resulting session's sender callback writes back into outputToSession, which BaseSession.write forwards to the active PTY. There is no Tabby-level prompt, allowlist, or origin check before the protocol auto-runs and before bytes are emitted to the session.
Relevant paths:
Decorator registration: tabby-terminal/src/index.ts
Middleware install and auto-confirmation: tabby-terminal/src/features/zmodem.ts (attachToSession(), feedFromSession, process(), sender callback)
PTY write-back: tabby-terminal/src/session.ts, tabby-local/src/session.ts
Detailed dataflow
1. Detection and auto-confirmation
SessionMiddlewareStack.feedFromSession delivers every session-output chunk into the middleware stack. ZModemMiddleware.feedFromSession calls this.sentry.consume(data) for every chunk regardless of whether a transfer is active, then forwards the original bytes to the terminal renderer. zmodem.js's Sentry._parse requires the parsed init header to be at the end of the consumed chunk; if so, on_detect fires and Tabby auto-calls detection.confirm(), which wires the zmodem session's sender to Tabby's outputToSession subject.
2. PTY feedback
A locally-cat'd file containing only an LF-only ZRQINIT hex header (zmodem.js tolerates this; a normal PTY remaps it to CR LF on the terminal side) triggers detection. Tabby's zsession.start() writes the fixed ZRINIT response bytes back to the PTY:
2a 2a 18 42 30 31 30 30 30 30 30 30 32 33 62 65 35 30 0d 0a 11
i.e....