Security researchers have identified an active supply chain attack against GitHub Actions: a malicious workflow component has propagated into tens of thousands of repositories — including repositories owned by Uber — silently exfiltrating CI/CD secrets during pipeline execution. This is not a theoretical supply chain risk scenario from a conference talk. This is a live compromise of the build infrastructure that organizations across every sector rely on to ship code.
If your organization uses GitHub Actions — and statistically, it does — you must assume that any repository referencing the compromised workflow component may have executed attacker-controlled code inside your CI environment. Every secret available to that runner (cloud credentials, deploy keys, API tokens, signing certificates) should be considered exposed until proven otherwise. The time to hunt is now, not after you find anomalous cloud API calls in your AWS bill.
Technical Analysis
What Happened
This campaign follows the well-documented pattern of GitHub Actions workflow poisoning: attackers gained the ability to modify a widely-consumed Action (or inject a malicious workflow definition), then leveraged the trust model of GitHub's CI ecosystem to execute arbitrary code on every runner that invoked it. Because GitHub Actions are frequently referenced by mutable version tags (e.g., @v3) rather than immutable commit SHAs, a single upstream compromise cascades downstream automatically — no further attacker interaction required. Tens of thousands of repositories, including Uber-owned repos, pulled in the malicious code on their next build.
The Attack Chain (Defender's View)
- Upstream compromise: The attacker modifies the Action's source — injecting a payload into the code that runs when the Action executes.
- Automatic propagation: Downstream repositories referencing the Action by mutable tag (or consuming the poisoned workflow) fetch the malicious version on the next pipeline run.
- Execution in a privileged context: The payload runs inside the GitHub runner process (
Runner.Worker), which holds the job's environment — including every secret passed viaenv:blocks,${{ secrets.* }}interpolation, and OIDC tokens for cloud federation. - Secret harvesting: The classic technique — consistent with the March 2025 tj-actions/changed-files compromise tracked as CVE-2025-30066 — is to scrape the runner process memory for secret material and print it into workflow logs (often base64-encoded, split to evade simple pattern matching) or exfiltrate it over HTTPS to attacker-controlled infrastructure.
- Downstream weaponization: Harvested credentials are then used for cloud pivoting, source code theft, package-registry poisoning, or further supply chain expansion.
Why This Works: The Trust Model Problem
GitHub Actions' ecosystem is built on convenience: uses: some-org/some-action@v4 resolves the tag to whatever commit that tag currently points to. Tags are mutable. An attacker who controls the upstream repo (or its publishing credentials) can move a tag to a malicious commit, and every downstream consumer inherits the payload silently. Even repositories that were never directly breached become execution environments for attacker code. No CVE needs to be assigned to your software — the vulnerability is architectural trust in third-party build steps.
Exploitation Status
- Confirmed active exploitation: tens of thousands of repositories affected, per researchers.
- High-profile victims: Uber-owned repositories confirmed among the compromised.
- Prior art: CVE-2025-30066 (tj-actions/changed-files) demonstrated this exact kill chain in March 2025, with secrets dumped into public workflow logs. Treat the current campaign with equal or greater urgency.
Detection & Response
Detection for this threat class lives in two places: (1) your GitHub environment — workflow file changes, Action references, anomalous workflow runs — and (2) your endpoints/runners — if you operate self-hosted runners, the payload executes there and is observable with standard EDR telemetry.
Sigma Rules
The following rules target the highest-fidelity behaviors: self-hosted runner processes performing memory-scraping or outbound retrieval consistent with the secret-harvesting technique, and the use of ephemeral paste/gist infrastructure from build agents. Deploy these against self-hosted runner fleets; tune the runner-path filter to your installation directory.
---
title: GitHub Actions Runner Process Memory Access or Dump
tid: 9f2c4a71-3b8d-4e61-a7c9-2d5e6f8a1b34
status: experimental
description: Detects processes attempting to read or dump memory of the GitHub Actions Runner.Worker process, consistent with CI secret-harvesting payloads observed in supply chain compromises of GitHub Actions.
references:
- https://www.infosecurity-magazine.com/news/supply-chain-attack-thousands/
- https://attack.mitre.org/techniques/T1003/
- https://attack.mitre.org/techniques/T1552/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.credential_access
- attack.t1003
- attack.t1552.001
logsource:
category: process_access
product: windows
detection:
selection_target:
TargetImage|contains: 'Runner.Worker'
selection_granted:
GrantedAccess|contains:
- '0x1010'
- '0x1410'
- '0x1F0FFF'
- '0x1F1FFF'
- '0x1F2FFF'
- '0x1F3FFF'
condition: all of selection_*
falsepositives:
- Legitimate debugging or EDR tooling attached to runner processes
level: high
---
title: Suspicious Outbound Retrieval from GitHub Actions Runner
tid: 4d7e9b12-8c3a-4f56-b2d8-9a1c3e5f7d02
status: experimental
description: Detects curl, wget, or equivalent download/transfer utilities spawning as children of GitHub Actions runner processes reaching out to paste, gist, or ephemeral hosting services — a pattern consistent with payload staging and secret exfiltration in poisoned workflows.
references:
- https://www.infosecurity-magazine.com/news/supply-chain-attack-thousands/
- https://attack.mitre.org/techniques/T1105/
- https://attack.mitre.org/techniques/T1102/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.command_and_control
- attack.t1105
- attack.t1102
logsource:
category: process_creation
product: windows
detection:
selection_parent:
ParentImage|contains:
- 'Runner.Worker'
- 'Runner.Listener'
selection_image:
Image|endswith:
- '\curl.exe'
- '\wget.exe'
- '\certutil.exe'
selection_cli:
CommandLine|contains:
- 'gist.githubusercontent.com'
- 'pastebin.com'
- 'paste.ee'
- 'hastebin'
- 'transfer.sh'
- 'webhook.site'
- 'ngrok.io'
condition: selection_parent and selection_image and selection_cli
falsepositives:
- Workflows legitimately fetching artifacts from paste-style services (rare in mature pipelines)
level: high
---
title: Base64 Encoded Blobs Written to Console from CI Runner Context
tid: 6b1e8f45-2d9c-4a73-c5e1-7f4b8d2a9e61
status: experimental
description: Detects shell or scripting interpreters under GitHub Actions runners emitting long base64 content to stdout — matching the technique of printing harvested secrets into workflow logs to exfiltrate them via publicly visible build output.
references:
- https://www.infosecurity-magazine.com/news/supply-chain-attack-thousands/
- https://attack.mitre.org/techniques/T1020/
- https://attack.mitre.org/techniques/T1132/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.exfiltration
- attack.t1020
- attack.t1132.001
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentCommandLine|contains:
- 'Runner.Worker'
- 'run.sh'
- 'runsvc.sh'
selection_shell:
Image|endswith:
- '/bash'
- '/sh'
- '/python'
- '/python3'
- '/node'
- '/perl'
selection_b64:
CommandLine|contains:
- 'base64'
- 'b64encode'
- 'base64_encode'
- 'ToBase64String'
condition: selection_parent and selection_shell and selection_b64
falsepositives:
- Build steps that legitimately encode artifacts, certificates, or test fixtures
level: medium
KQL — Microsoft Sentinel / Defender
If you ingest GitHub audit logs into Sentinel (via the GitHub connector) and run self-hosted runners under Defender for Endpoint, this query correlates suspicious process lineage on runners with recent workflow modifications — the two sides of this attack.
// Hunt: suspicious child processes of GitHub Actions runners on self-hosted fleets
// Correlate with GitHub audit events for workflow file modifications
let RunnerProcs = DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where InitiatingProcessFileName has_any ("Runner.Worker", "Runner.Listener", "runsvc.sh", "run.sh")
| where FileName in~ ("curl.exe", "wget.exe", "certutil.exe", "powershell.exe", "bash", "sh", "python", "python3", "node")
| where ProcessCommandLine has_any ("gist.githubusercontent.com", "pastebin", "webhook.site", "ngrok.io", "transfer.sh",
"base64", "b64encode", "/proc/", "proc/self/maps", "proc/self/mem", "gcore", "ptrace",
"AWS_SECRET", "GITHUB_TOKEN", "secrets.")
| project TimeGenerated, DeviceName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName;
RunnerProcs
| sort by TimeGenerated desc
// Secondary lens: hunt runner processes touching /proc memory maps directly (Linux self-hosted runners)
// union isfuzzy=true (RunnerProcs),
// (Syslog
// | where TimeGenerated > ago(14d)
// | where SyslogMessage has_any ("/proc/" , "/mem", "Runner.Worker")
// | where SyslogMessage has_any ("cat ", "strings", "gcore", "gdb")
// | project TimeGenerated, HostName, ProcessName, SyslogMessage)
Velociraptor VQL
For self-hosted Linux runners — the most common enterprise runner deployment — this artifact hunts for processes that accessed runner memory regions or staged encoded data in runner working directories.
-- Hunt for secret-harvesting behavior on self-hosted GitHub Actions runners
-- Looks for processes referencing /proc memory maps of runner processes,
-- and for encoded/artifact files staged in runner work directories.
-- Part 1: Process lineage referencing memory-dump tooling or /proc scraping
SELECT Pid, Ppid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE CommandLine =~ '(?i)(/proc/[0-9]+/(mem|maps)|gcore|ptrace|gdb -p|Runner\.Worker)'
OR CommandLine =~ '(?i)(base64|b64encode).*(secret|token|AWS_|GITHUB_)'
-- Part 2: Staged output files in runner work directories (run separately or as second artifact)
-- SELECT FullPath, Size, Mtime, Ctime
-- FROM glob(globs='/home/*/_work/**/*.bin')
-- WHERE Mtime > now() - 86400 * 14
Remediation / Audit Script
The script below audits every repository in a GitHub organization for (a) references to Actions pinned by mutable tags instead of commit SHAs, and (b) workflow runs executed in the exposure window, then flags repos whose secrets must be rotated. Run it with a PAT that has repo and workflow read scopes.
#!/usr/bin/env bash
# audit-actions-exposure.sh — Audit a GitHub org for mutable-tag Action references
# and recent workflow runs following a supply chain compromise disclosure.
# Usage: GITHUB_TOKEN=<pat> ORG=<org> SINCE=2026-03-01 ./audit-actions-exposure.sh
set -euo pipefail
: "${GITHUB_TOKEN:?Set GITHUB_TOKEN}"
: "${ORG:?Set ORG}"
SINCE="${SINCE:-2026-03-01}"
API="https://api.github.com"
AUTH="Authorization: Bearer ${GITHUB_TOKEN}"
OUT="exposure-report-$(date +%Y%m%d).csv"
echo "repo,workflow_file,issue" > "$OUT"
# Enumerate repos in the org
repos=$(curl -sS -H "$AUTH" -H "Accept: application/vnd.github+json" \
"${API}/orgs/${ORG}/repos?per_page=100" | grep '"full_name"' | cut -d'"' -f4)
for repo in $repos; do
# List workflow files
workflows=$(curl -sS -H "$AUTH" \
"${API}/repos/${repo}/actions/workflows" | grep -E '"path"' | grep -o '.github/workflows/[^"]*' || true)
for wf in $workflows; do
content=$(curl -sS -H "$AUTH" -H "Accept: application/vnd.github.raw" \
"${API}/repos/${repo}/contents/${wf}" || true)
# Flag 1: 'uses:' lines pinned to mutable tags (@v1, @main, @master) instead of 40-char SHA
if echo "$content" | grep -E 'uses:\s*[^@]+@(v[0-9]+(\.[0-9]+)*|main|master|latest)\b' | grep -vE '@[0-9a-f]{40}' >/dev/null; then
echo "${repo},${wf},mutable_tag_pinning" >> "$OUT"
fi
# Flag 2: third-party actions from outside your allowlisted orgs (adjust allowlist)
echo "$content" | grep -E 'uses:' | grep -vE "uses:\s*(actions/|github/|docker/|${ORG}/)" | \
while read -r line; do
third_party=$(echo "$line" | sed -E 's/.*uses:\s*//; s/@.*//')
echo "${repo},${wf},third_party_action:${third_party}" >> "$OUT"
done
# Flag 3: pull_request_target with checkout of PR head (known poisoned-pipeline pattern)
if echo "$content" | grep -q 'pull_request_target' && \
echo "$content" | grep -q 'github.head_ref'; then
echo "${repo},${wf},pull_request_target_with_head_checkout" >> "$OUT"
fi
done
# Flag 4: workflow runs since disclosure — these runs may have executed poisoned code
runs=$(curl -sS -H "$AUTH" \
"${API}/repos/${repo}/actions/runs?created=>=${SINCE}&per_page=5" | grep -c '"id"' || true)
if [ "${runs:-0}" -gt 0 ]; then
echo "${repo},ALL,workflow_runs_since_${SINCE}_require_secret_rotation_review" >> "$OUT"
fi
done
echo "[+] Audit complete. Review ${OUT}"
echo "[+] PRIORITY: For every repo with runs since ${SINCE} and mutable-tag or third-party actions,"
echo " rotate ALL secrets available to those workflows (cloud keys, deploy tokens, OIDC trust)."
Remediation
There is no patch to download for a poisoned pipeline — the fix is architectural and immediate. Execute in this order:
- Assume exposure; rotate secrets first. For every repository that ran workflows referencing third-party Actions during the exposure window, rotate: cloud provider keys and OIDC trust relationships, deploy keys, package registry tokens (npm, PyPI, NuGet), signing certificates, and any
${{ secrets.* }}consumed by those jobs. Prioritize production-deploy pipelines and public repos (public logs may already have leaked secrets, as in CVE-2025-30066). - Pin every Action to a full commit SHA. Replace
uses: vendor/action@v3withuses: vendor/action@<full-40-char-sha>. Mutable tags are the propagation vector; SHAs are immutable. Enforce this with a linter (e.g.,step-security/harden-runneraudit mode or an OPA/Conftest policy) in CI itself. - Restrict what Actions can do. In GitHub org settings, set "Allowed actions" to an explicit allowlist of vetted sources. Disable
pull_request_targetworkflows that check out attacker-controllable code with secrets available — a classic poisoned-pipeline execution path. - Harden the runner. For self-hosted runners, deploy egress filtering so runners can only reach approved endpoints (GitHub, your artifact stores, package mirrors). A runner that cannot reach
webhook.siteor arbitrary gists cannot exfiltrate. Use ephemeral runners so no state persists between jobs. - Audit workflow logs for encoded blobs. Search historical workflow run logs for long base64 strings. GitHub masks registered secrets, but double-encoded or chunked values bypass masking — review logs for any repo that consumed the compromised component.
- Hunt your cloud logs. Correlate the exposure window against AWS CloudTrail / Azure Activity Log / GCP Audit Logs for use of CI-associated credentials from IP addresses outside GitHub's documented runner ranges or your self-hosted fleet.
- Monitor advisories. Watch the GitHub security advisories feed and the affected vendor's disclosure for the specific compromised component and affected version range, and verify removal of the malicious tag/commit from any forks or internal mirrors.
This incident is the same lesson the industry keeps paying tuition on: your CI pipeline is production infrastructure with production credentials, and every third-party build step is remote code execution by design. Treat it accordingly.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.