Back to Intelligence

GitHub Actions Credential-Theft Campaign Hits 340+ Repos via Compromised Maintainers: Detection and Response Guide

SA
Security Arsenal Team
October 9, 2026
11 min read

Security researchers at StepSecurity have disclosed an ongoing credential-theft campaign that compromises GitHub maintainer accounts and uses them to push malicious GitHub Actions workflows into large fleets of repositories at machine speed. In one confirmed branch of the campaign, the account of Takashi Kitao — author of the 18,400-star open-source game engine pyxel — was hijacked and used to push a malicious workflow to 27 repositories starting at 13:20 UTC, with the broader campaign touching over 340 repositories across at least two high-profile maintainer accounts.

This is not a vulnerability in GitHub or Actions itself. It is a trust-chain compromise: the attacker's entire playbook is to inherit the maintainer's write access, plant a CI/CD workflow that executes on GitHub's infrastructure, and harvest credentials — repository secrets, GITHUB_TOKEN material, cloud credentials, npm/PyPI publishing tokens — straight out of the Actions runtime. Every organization that consumes open-source packages, runs self-hosted runners, or grants long-lived publishing tokens to CI is in scope.

If you run a SOC, manage a GitHub organization, or maintain open-source projects, treat this as an active incident class, not a news item.

Why This Campaign Is Dangerous

Three characteristics make this campaign structurally worse than a typical account takeover:

  1. Scale through a single account. One compromised maintainer = dozens of poisoned repositories pushed within minutes. The pyxel branch alone hit 27 repos beginning at 13:20 UTC — automation speed that no manual review process can catch without telemetry.
  2. Execution inside the trust boundary. Malicious workflows run on GitHub-hosted or self-hosted runners with access to the repository's encrypted secrets. Secrets that are masked in logs are still fully available to the workflow process memory. The exfiltration happens from infrastructure you implicitly trust.
  3. Downstream supply-chain blast radius. If the stolen secrets include package-registry publishing tokens (npm, PyPI, RubyGems), the attacker pivots from credential theft to shipping malicious package versions to thousands of downstream consumers. pyxel's 18,400 stars represent a large installed base.

Technical Analysis

Attack Chain

  1. Initial access — maintainer account compromise. The attacker obtains control of a high-reputation GitHub account (phishing, session-token theft, credential stuffing, or a compromised personal access token with repo/workflow scopes). The pyxel maintainer and at least one other high-profile maintainer were compromised in this campaign.
  2. Malicious workflow push. Using the account's legitimate write access, the attacker commits a new or modified file under .github/workflows/ across every repository the account can write to — 27 repos in the pyxel case, 340+ across the campaign.
  3. Trigger and execution. The workflow is typically configured with broad triggers (push, pull_request, workflow_dispatch) so it fires immediately and on subsequent activity. On execution, the runner environment exposes:
    • GITHUB_TOKEN (scoped to the repo, but sufficient to push code, modify releases, or read additional workflow context)
    • Repository and organization encrypted secrets injected as environment variables (cloud keys, registry tokens, signing keys)
    • On self-hosted runners: whatever the host itself can reach (internal networks, metadata services, mounted credentials)
  4. Exfiltration. Workflow steps typically curl the secrets to an attacker-controlled endpoint — disposable request-bin services, webhook collectors, or attacker infrastructure — often obfuscated (base64, environment indirection) to evade casual log review.
  5. Secondary monetization. Stolen registry tokens are used to publish backdoored package versions; stolen cloud credentials enable broader environment intrusion.

What Defenders Can Actually Observe

Because the malicious code executes inside GitHub's runner infrastructure, endpoint EDR will not see GitHub-hosted execution. Your observable surfaces are:

  • GitHub audit log (organization/enterprise): workflow file creation/modification events, workflow run records, actions by the compromised account, token/session anomalies. If you are not streaming this to a SIEM, you are blind.
  • Workflow file content in repositories: the malicious workflow itself is a forensic artifact. Diff any workflow file modified in the campaign window.
  • Workflow run logs: outbound curl/wget to non-standard destinations visible in step logs (secrets themselves will be masked, but the exfil URL won't be).
  • Self-hosted runners: full process telemetry — Runner.Worker spawning network utilities is high-fidelity.
  • Package registries: unexpected version publishes from maintainer accounts in the same window.

Exploitation Status

  • Confirmed active, in-the-wild campaign — this is ongoing, not theoretical.
  • 340+ repositories confirmed affected across at least two compromised maintainer accounts, per StepSecurity's disclosure.
  • No CVE applies — this is identity compromise plus abuse of legitimate CI/CD functionality, which is precisely why patching cannot fix it.

Detection & Response

The detections below are tuned for low noise. The Sigma rules target self-hosted runner telemetry (the only place you get endpoint visibility); the KQL covers GitHub audit log hunting in Sentinel; the VQL hunts runner hosts.

YAML
---
title: GitHub Actions Self-Hosted Runner Spawning Network Exfiltration Tool
id: 3f9a1b72-8c4d-4e6f-a2b1-7d5c9e8f2034
status: experimental
description: Detects GitHub Actions Runner.Worker spawning curl, wget, or PowerShell web requests to disposable exfiltration endpoints. Consistent with credential-stealing workflows pushed via compromised maintainer accounts (StepSecurity GitHub Actions campaign, October 2026).
references:
  - https://thehackernews.com/2026/10/credential-stealing-github-actions.html
  - https://attack.mitre.org/techniques/T1567/002/
author: Security Arsenal
date: 2026/10/22
tags:
  - attack.exfiltration
  - attack.t1567.002
  - attack.t1071.001
logsource:
  category: process_creation
  product: windows
detection:
  selection_parent:
    ParentImage|endswith:
      - '\Runner.Worker.exe'
      - '\Runner.Listener.exe'
  selection_tool:
    Image|endswith:
      - '\curl.exe'
      - '\powershell.exe'
      - '\pwsh.exe'
  selection_exfil:
    CommandLine|contains:
      - 'webhook.site'
      - 'requestbin'
      - 'pipedream'
      - 'ngrok'
      - 'transfer.sh'
      - 'pastebin.com'
      - 'Invoke-WebRequest'
      - 'curl.exe -d'
      - '--data @'
  condition: selection_parent and selection_tool and selection_exfil
falsepositives:
  - Legitimate workflows uploading artifacts to known destinations; baseline your workflow usage and tune by destination domain
level: high
---
title: Linux GitHub Actions Runner Exfiltrating Environment Variables or Secrets
id: 8b2c4d91-5e7a-4f3b-9c1d-2a6e8b0f3157
status: experimental
description: Detects a Linux GitHub Actions runner worker process piping environment variables or files into curl/wget, a hallmark of CI credential-theft workflows. Seen in the October 2026 campaign pushing malicious workflows via hijacked maintainer accounts.
references:
  - https://thehackernews.com/2026/10/credential-stealing-github-actions.html
  - https://attack.mitre.org/techniques/T1552/001/
author: Security Arsenal
date: 2026/10/22
tags:
  - attack.credential_access
  - attack.t1552.001
  - attack.exfiltration
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentCommandLine|contains:
      - 'Runner.Worker'
  selection_cmd:
    CommandLine|contains:
      - 'env | curl'
      - 'printenv | curl'
      - 'env | base64'
      - 'curl -d "$(env)'
      - 'curl --data-binary'
      - 'wget --post-data'
      - '/home/runner/work/_temp'
  condition: selection_parent and selection_cmd
falsepositives:
  - Rare legitimate CI telemetry uploads; validate destination against an allowlist
level: critical
---
title: Workflow File Modified Followed by Immediate Runner Network Connection to Rare Destination
id: 6d1e5f83-2a9b-4c8e-b3f7-9c4a1d6e5208
status: experimental
description: Detects a GitHub Actions runner worker establishing an outbound connection shortly after execution start to a non-standard destination. Correlates with malicious workflow execution following a pushed workflow change.
references:
  - https://thehackernews.com/2026/10/credential-stealing-github-actions.html
author: Security Arsenal
date: 2026/10/22
tags:
  - attack.command_and_control
  - attack.t1071.001
logsource:
  category: network_connection
  product: windows
detection:
  selection:
    Image|endswith:
      - '\Runner.Worker.exe'
      - '\curl.exe'
  filter_known:
    DestinationHostname|contains:
      - 'github.com'
      - 'githubusercontent.com'
      - 'githubassets.com'
      - 'actions.githubusercontent.com'
      - 'azure.com'
      - 'npmjs.org'
      - 'pypi.org'
  condition: selection and not filter_known
falsepositives:
  - Workflows pulling dependencies from third-party CDNs; build an org-specific destination baseline before enforcing
level: medium
KQL — Microsoft Sentinel / Defender
// Hunt 1: GitHub audit log — workflow files created/modified, clustered by actor and time.
// Requires GitHub audit log streaming into Sentinel (GitHubAuditLog table or via custom connector).
// Look for a single account touching many repos' .github/workflows in minutes — the campaign signature.
GitHubAuditLog
| where TimeGenerated > ago(7d)
| where tostring(AdditionalFields) has ".github/workflows"
| extend Repo = tostring(AdditionalFields.repo), Actor = tostring(AdditionalFields.actor)
| summarize ReposTouched = dcount(Repo), RepoList = make_set(Repo), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
    by Actor, bin(TimeGenerated, 1h)
| where ReposTouched > 3
| project TimeGenerated, Actor, ReposTouched, RepoList, FirstSeen, LastSeen
| order by ReposTouched desc;

// Hunt 2: Self-hosted runner process telemetry — Runner.Worker spawning network tools with exfil indicators.
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName in~ ("Runner.Worker.exe", "Runner.Listener.exe", "run.cmd", "run.sh")
| where FileName in~ ("curl.exe", "wget.exe", "powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("webhook.site", "requestbin", "pipedream", "ngrok", "transfer.sh", "pastebin", "Invoke-WebRequest", "--data-binary", "curl -d")
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
| order by TimeGenerated desc;

// Hunt 3: Syslog-ingested Linux runner hosts — env/secrets piped to network tools.
Syslog
| where TimeGenerated > ago(7d)
| where SyslogMessage has "Runner.Worker" or SyslogMessage has "/home/runner/work"
| where SyslogMessage has_any ("env | curl", "printenv", "--data-binary", "curl -d", "wget --post-data")
| project TimeGenerated, Computer, SyslogMessage
| order by TimeGenerated desc
VQL — Velociraptor
-- Hunt self-hosted GitHub Actions runner hosts for exfiltration behavior
-- Targets: runner worker children calling network tools, plus recently modified workflow files on disk

-- Part 1: Process execution with exfil indicators under runner context
SELECT Pid, Ppid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE CommandLine =~ '(?i)(webhook\.site|requestbin|pipedream|ngrok|transfer\.sh|pastebin|--data-binary|curl -d|Invoke-WebRequest|printenv|env \| curl)'
  AND (CommandLine =~ '(?i)(runner|actions)' OR Exe =~ '(?i)(curl|wget|powershell|pwsh)')

-- Part 2: Workflow files modified within the campaign window on runner/cache hosts
SELECT FullPath, Size, Mtime, Ctime
FROM glob(globs=['C:/actions-runner/**/.github/workflows/*.yml',
                 'C:/actions-runner/**/.github/workflows/*.yaml',
                 '/home/runner/**/.github/workflows/*.yml',
                 '/opt/actions-runner/**/.github/workflows/*.yml'])
WHERE Mtime > '2026-10-01'
ORDER BY Mtime DESC

Remediation

Immediate (next 24 hours)

  1. Inventory and diff workflow files. Across every repository in your organization, list all files under .github/workflows/ modified in the last 14 days. Review each diff for unexpected steps, outbound network calls, or new triggers. The script below automates this with the GitHub CLI.
  2. Rotate all secrets exposed to Actions. Assume any repository secret, organization secret, or environment secret reachable by a tampered workflow is compromised. Rotate: cloud access keys, npm/PyPI/registry tokens, signing keys, deploy keys, and any PATs used in workflows.
  3. Audit maintainer account sessions. In GitHub: Settings → Security log and Sessions for each maintainer. Revoke unknown sessions, revoke all PATs and OAuth grants with workflow or repo scope that aren't actively needed.
  4. Enforce phishing-resistant MFA. Require hardware keys (FIDO2/WebAuthn) for every member with write access. This campaign starts with account takeover — TOTP alone is phishable; hardware keys break the chain.
Bash / Shell
#!/bin/bash
# Audit GitHub org repos for recently modified/added workflow files and suspicious patterns
# Requires: gh CLI authenticated with org read scope
# Usage: ./audit-workflows.sh <org-name> <days>

ORG="${1:?Usage: $0 <org> <days>}"
DAYS="${2:-14}"
SINCE=$(date -u -d "${DAYS} days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-${DAYS}d +%Y-%m-%dT%H:%M:%SZ)
SUSPICIOUS='webhook.site|requestbin|pipedream|ngrok|transfer.sh|pastebin|env \| curl|printenv|--data-binary|curl -d'

echo "[*] Auditing org: ${ORG} — workflow changes since ${SINCE}"

for REPO in $(gh repo list "${ORG}" --limit 500 --json nameWithOwner -q '.[].nameWithOwner'); do
  # List workflow files and their last-commit dates
  FILES=$(gh api "repos/${REPO}/contents/.github/workflows" --jq '.[].path' 2>/dev/null)
  [ -z "$FILES" ] && continue
  for WF in $FILES; do
    LAST=$(gh api "repos/${REPO}/commits?path=${WF}&per_page=1" --jq '.[0].commit.committer.date' 2>/dev/null)
    if [[ "$LAST" > "$SINCE" ]]; then
      echo "[!] RECENTLY MODIFIED: ${REPO} :: ${WF} (last commit: ${LAST})"
      # Pull content and scan for exfil indicators
      CONTENT=$(gh api "repos/${REPO}/contents/${WF}" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null)
      if echo "$CONTENT" | grep -Eiq "$SUSPICIOUS"; then
        echo "[!!!] SUSPICIOUS CONTENT in ${REPO} :: ${WF}"
        echo "$CONTENT" | grep -Ein "$SUSPICIOUS"
      fi
    fi
  done
done

echo "[*] Audit complete. Manually review every [!] entry — do not auto-dismiss."

Short-term hardening (this week)

  1. Require pull-request review for workflow changes. Use branch protection rules plus a CODEOWNERS entry covering .github/workflows/ so no single account — compromised or not — can silently land a workflow change. Pair this with GitHub's setting Actions → Fork pull request workflows from outside collaborators: require approval, and consider requiring approval for all workflow runs from non-collaborators.
  2. Pin third-party actions by full commit SHA, never by mutable tag (@v3). Tags can be moved by a compromised action maintainer; SHAs cannot.
  3. Kill long-lived registry tokens in CI. Migrate npm/PyPI/cloud publishing to OIDC trusted publishing (short-lived, workload-identity tokens) so there is no static credential in a repository secret to steal.
  4. Scope GITHUB_TOKEN to read-only by default (Settings → Actions → Workflow permissions) and grant write permissions per-job only where required.
  5. Restrict self-hosted runners to private repos and ephemeral (just-in-time, single-run) runners. A persistent self-hosted runner executing a poisoned workflow is a beachhead into your internal network.

Detection engineering (ongoing)

  1. Stream GitHub audit logs to your SIEM. Enable organization/enterprise audit log streaming and deploy the hunt queries above as scheduled analytics. The single-account-touches-many-repos pattern (Hunt 1) is the highest-fidelity signal this campaign produces.
  2. Alert on workflow-trigger anomalies. A workflow_dispatch or push-triggered run from an account that hasn't committed in months is an anomaly worth paging on.
  3. Monitor package registries. If any of your maintainers publish to npm/PyPI, watch for unexpected version publishes in the same window as suspicious workflow activity — that is the downstream compromise indicator.

The Bottom Line

This campaign converts one phished maintainer into hundreds of poisoned repositories in minutes, and it monetizes through secrets that most organizations treat as plumbing. The fix is not a patch — it is identity hardening (hardware-key MFA), CI governance (reviewed workflow changes, SHA-pinned actions, OIDC publishing), least-privilege tokens, and audit-log telemetry that actually reaches your SOC. Organizations that stream GitHub audit logs and alert on workflow-change velocity will catch the next iteration of this campaign in minutes. Organizations that don't will find out from their package consumers.

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.