## https://sploitus.com/exploit?id=DF3A1E2B-3984-5D6A-A4D0-95B864B82A66
# CVE-2026-39866 โ Command Injection via unquoted workflow dispatch input
Proof-of-concept lab for command injection in GitHub Actions workflow dispatch
---
## Purpose
CVE-2026-39866 is a command injection bug in Lawnchair's GitHub Actions release workflow. The `release_update.yml` file passes user input directly to bash without quoting, so an attacker can inject commands. This repo runs both the vulnerable and patched versions side by side.
## Quick facts
| Field | Value |
|-------|-------|
| CVE ID | [CVE-2026-39866](https://nvd.nist.gov/vuln/detail/CVE-2026-39866) |
| GHSA | [GHSA-9prc-pp2c-3427](https://github.com/LawnchairLauncher/lawnchair/security/advisories/GHSA-9prc-pp2c-3427) |
| CWE | [CWE-78](https://cwe.mitre.org/data/definitions/78.html) (Improper Neutralization of Special Elements used in an OS Command) |
| Product | [LawnchairLauncher/lawnchair](https://github.com/LawnchairLauncher/lawnchair) |
| Affected | All versions below fcba413 |
| Patched | fcba413 |
| Discovery | [Abhay Kumar (@abhayclasher)](https://github.com/abhayclasher) |
## Vulnerability details
The vulnerability exists in the `release_update.yml` workflow file. The action takes an `artifactName` parameter and passes it directly into a bash command without quoting.
```yaml
- name: Rename artifact
run: mv ${{ inputs.artifactName }}.zip lawnchair.zip
```
GitHub Actions substitutes the variable value into the string before the shell runs it. Without quotes around `${{ inputs.artifactName }}`, the shell interprets spaces and semicolons as command separators.
If an attacker supplies `file.zip; touch pwned.txt #` as the `artifactName`:
**How GitHub Actions parses it:**
`${{ inputs.artifactName }}` โ `file.zip; touch pwned.txt #`
**How the shell executes it:**
```
mv file.zip; touch pwned.txt #.zip lawnchair.zip
```
The shell executes `mv file.zip`, then `touch pwned.txt`, then ignores the rest as a comment.
## Impact
- Execute arbitrary commands on the GitHub Actions runner.
- Steal repository secrets exposed to the runner environment (like `GITHUB_TOKEN` or release signing keys).
- Alter release artifacts before they are uploaded.
- Pivot into connected infrastructure if the runner is self-hosted.
## System requirements
- Node.js 18 or later
- Docker (optional, for containerized lab)
- 500 MB free disk space
## Setup
### Option 1: Run directly
```bash
git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866/app
npm install
node server.js
```
### Option 2: Run with Docker
```bash
git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866
docker compose up -d
```
## Usage
After the server starts, visit these URLs:
| URL | Description |
|-----|-------------|
| `http://localhost:3000/workflow/vulnerable` | Vulnerable workflow โ command injection executes on submit |
| `http://localhost:3001/workflow/patched` | Same workflow with fcba413 patch applied |
The vulnerable version will execute your injected command on the runner. The patched version treats the input as a literal string.
You can also run the standalone PoC:
```bash
cd poc
node exploit.js
```
This spins up a minimal server on port 3000 that demonstrates the same bug without the full application context.
## How it works
### Detection flow
```
1. Attacker controls the artifactName input parameter
2. GitHub Actions expands ${{ inputs.artifactName }} into the YAML
3. The run step sends this to bash
4. Bash sees unquoted special characters and treats them as command syntax
5. Commands execute with runner environment privileges
```
### The attack chain
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Malicious input: โ
โ โ
โ artifactName = "file.zip; id #" โ
โ โ
โ 2. GitHub Actions substitutes into YAML: โ
โ โ
โ run: mv file.zip; id #.zip lawnchair.zip โ
โ โ
โ 3. Bash sees: โ
โ - mv file.zip โ
โ - id
Educational purposes only โ use in isolated environments