Jul 25, 2026·8 min read·44 visits
Oh My Posh rendered raw control characters from untrusted sources like directory names and Git commit logs, allowing attackers to hijack system clipboards or execute arbitrary commands upon terminal directory navigation.
Oh My Posh prior to version 29.35.1 is vulnerable to terminal escape sequence injection. Dynamic strings from directory names or Git repository metadata are written to the prompt output without neutralization, enabling unauthenticated remote code execution, clipboard hijacking, or terminal DoS when users navigate to malicious folders.
Oh My Posh is a popular, highly customizable prompt engine used across multiple shells (such as Bash, Zsh, Fish, PowerShell, Command Prompt, and NuShell) to render rich CLI themes. To display context-aware metadata, the engine queries the operating system and Version Control Systems (VCS) to extract active directory paths, Git branch details, and environment status. This dynamic prompt generation operates by formatting and writing text segments directly to standard output (stdout), which is subsequently parsed and rendered by the host terminal emulator.\n\nA terminal escape sequence injection vulnerability (tracked as GHSA-FWJX-9P69-H25H) exists in Oh My Posh prior to version 29.35.1. The flaw stems from the engine writing dynamic strings retrieved from untrusted external sources directly into the prompt rendering buffer without neutralizing raw control characters. An attacker who controls one of these data sources—such as by creating a folder with a malicious name or distributing a Git repository containing an engineered commit subject—can inject arbitrary ANSI or Operating System Command (OSC) control sequences.\n\nWhen a victim navigates to a directory containing the malicious payload or performs standard shell interactions inside an affected folder, Oh My Posh automatically processes the untrusted string. The terminal emulator receives the raw escape sequences, parses them as control directives, and performs automated actions on behalf of the user. This breaks the boundary between dynamic console text and terminal process control, allowing attackers to perform silent clipboard modifications, screen spoofing, or terminal denial-of-service.
The root cause of this vulnerability lies in the character-by-character rendering loop implemented within the terminal writer subsystem of Oh My Posh. In command-line environments, prompt engines write styled text using terminal control codes like ANSI escape sequences. Terminal emulators continually scan the standard output stream for special initiator bytes (such as the C0 Escape character \x1b or C1 control bytes) to alter text color, change the window title, or access advanced host integration protocols.\n\nPrior to the security patch, the character-writing routine did not inspect, sanitize, or filter dynamic text parsed from external context providers before printing to the stdout stream. This processing flow took place within the write(s rune) function in src/terminal/writer.go. While Oh My Posh's internal formatting sequences bypassed this routine through dedicated high-level styling helpers, any text representing the dynamic data of active prompt segments—such as path components, Git log responses, and command outputs—passed straight through to the lower-level writer verbatim.\n\nBecause the host terminal emulator's parsing engine cannot distinguish between layout styling emitted intentionally by Oh My Posh and rogue control sequences embedded in untrusted text fields, the stream became a direct vector for command execution. By placing control codes like the Escape character (\x1b) and the Bell character (\x07) inside directory names or Git metadata, an attacker can bypass the layout constraints and direct the terminal to parse and execute internal commands.\n\nmermaid\ngraph LR\n A["Untrusted Source (Git Log / Folder Name)"] --> B["Oh My Posh Rendering Loop (write())"]\n B --> C["Raw standard output (stdout) with unchecked C0/C1 runes"]\n C --> D["Terminal Emulator Stream Parser"]\n D --> E["Action Trigger (Clipboard Injection / Window Title Spoof)"]\n
To understand the implementation flaw, consider the vulnerable logic within the character writer in src/terminal/writer.go. Prior to the patch, the write function processed character runes without performing sanitization checks on control characters, appending them directly to the active prompt string builder.\n\ngo\n// Vulnerable code in src/terminal/writer.go\nfunc write(s rune) {\n\tif isInvisible {\n\t\t// ...\n\t\treturn\n\t}\n\n\tif isHyperlink {\n\t\tbuilder.WriteRune(s) // Vulnerable write of untrusted link data\n\t\treturn\n\t}\n\n\t// ...\n\tbuilder.WriteRune(s) // Vulnerable write of raw segment runes\n}\n\n\nBecause builder.WriteRune(s) executes on all runes parsed from segment strings, any raw control bytes like \x1b (ESC) or \x07 (BEL) were stored inside the output stream. The security patch introduced in commit edcf3c88f3fb582e84358b385c49d33d04c04224 mitigates this vulnerability by adding an inline sanitization check, isControlRune(s), to drop dangerous character classes before they can reach the builder.\n\ngo\n// Patched code in src/terminal/writer.go\nfunc write(s rune) {\n\tif isInvisible {\n\t\treturn\n\t}\n\n\t// segment content (directory names, git metadata, environment variables,\n\t// command output) is potentially attacker-controlled and never passes through\n\t// this function when it's Oh My Posh's own styling; drop C0/C1 control runes\n\t// (ESC, BEL, CSI, OSC, ...) so they can't be interpreted as escape sequences\n\t// by the terminal, including inside a hyperlink target.\n\tif isControlRune(s) {\n\t\treturn\n\t}\n\n\tif isHyperlink {\n\t\tbuilder.WriteRune(s)\n\t\treturn\n\t}\n\n\tbuilder.WriteRune(s)\n}\n\n// isControlRune reports whether s is a C0 (0x00-0x1F), DEL (0x7F), or C1\n// (0x80-0x9F) control character. These are the bytes a terminal can interpret\n// as the start of an escape sequence; no legitimate rendered segment content needs them.\nfunc isControlRune(s rune) bool {\n\tif s == '\\n' {\n\t\treturn false\n\t}\n\n\treturn s <= 0x1f || (s >= 0x7f && s <= 0x9f)\n}\n\n\nThis boundary check drops all control characters within the C0 range (0x00 - 0x1F), the delete character (0x7F), and the C1 range (0x80 - 0x9F). These control character sets are responsible for initiating terminal instructions like CSI (Control Sequence Introducer) and OSC (Operating System Command). By stripping these bytes, the emulator parses the remaining string as safe, literal text.
Exploitation of GHSA-FWJX-9P69-H25H is highly reliable and can be achieved through two primary vectors: Git metadata poisoning and directory name injection. In a typical scenario, an attacker designs a payload that leverages Operating System Command 52 (OSC 52), which allows standard terminal programs to write to the system clipboard.\n\nTo craft a malicious Git repository, an attacker creates an empty repository and structures a commit subject with the necessary terminal command escape sequences. The sequence uses \033] to open an OSC control channel, 52;c; to direct a clipboard copy, a base64-encoded command string, and the \007 (BEL) byte to close the channel.\n\nbash\n# Construct a malicious commit payload\n# OSC 0 sets the terminal title to "HACKED"\n# OSC 52 writes "echo PWNED" into the clipboard buffer\nprintf 'feat: \033]0;HACKED\007\033]52;c;ZWNobyBQV05FRA==\007 update' > msg.txt\n\ngit commit --allow-empty -F msg.txt\n\n\nWhen a victim navigates to this directory, Oh My Posh executes internal commands to build the Git status segment (such as extracting the last commit title via git log). This output is fed into the rendering engine, passing the raw control bytes to stdout. As the terminal emulator processes the stream, the OSC 52 sequence executes, instantly writing the command echo PWNED to the host's system clipboard. If the user subsequently performs a paste operation, the payload executes inside their active shell.\n\nA secondary attack vector targets the file system directly on Unix-based systems. Because macOS and Linux filesystems permit the use of raw control characters within directory names, an attacker can create nested directories containing injection sequences. When the victim navigates into the compromised folder structure using the cd command, the path rendering segment processes the raw escape sequences, triggering the payload execution.
The security implications of GHSA-FWJX-9P69-H25H are significant due to the scope change (S:C in CVSS metrics) inherent to terminal escape sequence injections. Because the execution transitions from the prompt layout parser to the host terminal emulator, the attack bypasses shell boundaries and impacts the underlying operating system. The vulnerability has been assigned a CVSS v3.1 score of 6.3.\n\nThe primary operational risk is silent clipboard manipulation. Attackers can overwrite the victim's host clipboard with complex, malicious shell scripts. Given that developers and system administrators frequently paste commands, configuration snippets, and credentials, an attacker can reliably achieve arbitrary command execution. This technique does not require user validation beyond standard navigation or paste actions.\n\nAdditionally, attackers can execute terminal-based denial of service (DoS) and visual spoofing. By sending terminal control sequences that manipulate terminal windows (such as OSC 0 or OSC 2), attackers can change window titles to spoof running programs. They can also use ANSI sequence loops to corrupt terminal buffers, rendering the command line unresponsive and forcing the victim to terminate their session, potentially losing unsaved work.
To remediate this vulnerability, users must update Oh My Posh immediately to version 29.35.1 or newer. Updates can be applied via major package managers across supported environments. For systems where binary updates cannot be executed immediately, administrators should implement defensive configuration changes to limit the impact.\n\nbash\n# Update via Homebrew (macOS/Linux)\nbrew update && brew upgrade oh-my-posh\n\n# Update via winget (Windows)\nwinget upgrade JanDeDobbeleer.OhMyPosh\n\n\nIf upgrading is not immediately possible, users can apply configuration-level workarounds. Modifying active Oh My Posh configuration JSON templates to remove dynamic segments—specifically the git and path blocks—removes the primary attack surfaces. Additionally, terminal emulators should be configured to disable clipboard integration. In tmux, this can be done by adding set -s set-clipboard off to the .tmux.conf file, and in iTerm2, by disabling the option 'Applications in terminal may access clipboard'.\n\nAn evaluation of the fix implemented in version 29.35.1 indicates that while it successfully blocks the execution of dangerous control sequences, some structural limitations remain. Specifically, the newline character \\n is explicitly exempted in isControlRune. An attacker can exploit this exception to inject line breaks into directory names or Git logs. This allows for prompt-spoofing attacks, where a modified output pushes the authentic prompt out of view and displays a fake console layout to capture input or credentials. Security teams should remain aware of these residual layout manipulation vectors.
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
oh-my-posh JanDeDobbeleer | < 29.35.1 | 29.35.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-150 |
| Attack Vector | Local |
| CVSS Score | 6.3 |
| EPSS Score | N/A (No associated CVE) |
| Impact | Scope Change, Clipboard Hijacking, Potential Code Execution |
| Exploit Status | Proof-of-Concept (PoC) |
| KEV Status | Not Listed |
The application processes inputs containing control sequences and outputs them to a terminal stream without proper neutralization, allowing arbitrary terminal command execution.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.