Displaying a crafted file in Tabby terminal can run shell commands

logickkk12 pts0 comments

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....

tabby terminal zmodem session file auto

Related Articles