Back to Intelligence

Mini Shai-Hulud GitHub Actions Re-Enabled With Malicious Payload: CI/CD Detection and Remediation Guide

SA
Security Arsenal Team
September 26, 2026
11 min read

The reporting is short but operationally important: two third-party GitHub Actions that had been compromised in the Mini Shai-Hulud campaign were re-enabled by their maintainer and remained accessible for more than a week even though they still resolved to malicious code. No CVE is named in the source item, and defenders should not wait for one. The risk here is not a memory-corruption bug; it is trust failure in CI/CD supply chains. Any repository that referenced those actions by mutable tag, floating major version, or cached marketplace entry could have pulled attacker-controlled code into a privileged build environment after the incident was assumed to be over.

GitHub Actions are especially dangerous when compromised because workflows routinely execute with secrets in environment variables: GITHUB_TOKEN, npm publish tokens, cloud credentials, artifact signing keys, deployment SSH keys, package registry credentials, and sometimes OIDC tokens that can mint short-lived cloud sessions. A malicious action does not need kernel access. It only needs a runner to execute its JavaScript, install hooks, or shell commands inside a job that already has the keys to release, deploy, or modify code.

The defensive lesson is direct: incident closure for a supply-chain compromise cannot mean “the maintainer said it is fixed” or “the action is back online.” Closure must mean every downstream consumer has verified what their workflows actually resolve to today, removed mutable references, rotated secrets that were exposed during the window, and hunted for execution artifacts left behind while the malicious payload was still reachable.

Affected products, versions, and platforms

Based on the provided summary, the affected components are third-party GitHub Actions previously tied to the Mini Shai-Hulud campaign and re-enabled while still pointing to malicious code. The specific action names are not included in the summary you provided, so do not guess them in detection content. Instead, treat this as a class of exposure affecting:

  • GitHub-hosted runners and self-hosted runners executing Linux, Windows, or macOS jobs.
  • Repositories using uses: owner/action@v1, uses: owner/action@main, or marketplace references rather than immutable commit SHAs.
  • Reusable workflows and composite actions that indirectly reference third-party actions, where the transitive dependency is easy to miss.
  • Node.js-based actions and setup steps where node, npm, npx, yarn, or pnpm can execute install scripts or download additional payloads.
  • Pipelines that publish npm packages, build release artifacts, deploy to cloud accounts, or run with id-token: write for OIDC federation.

Exploitation requirements are low from the pipeline’s perspective. If a workflow references the malicious action and the job is triggered by push, pull_request_target, schedule, workflow_dispatch, or release events, the runner will fetch and execute the referenced code. The most dangerous conditions are broad trigger permissions, overly permissive GITHUB_TOKEN, cloud OIDC trust, and production deploy credentials available to build jobs.

Attack chain from a defender’s perspective

Mini Shai-Hulud has been discussed publicly as an npm/GitHub ecosystem supply-chain campaign in which malicious packages or actions act like a worm: steal available tokens, use them to modify or publish additional packages/workflows, and spread laterally through maintainer accounts and CI automation. For this specific re-enablement story, assume a simpler but still severe chain:

  1. A workflow references a third-party action by tag or branch instead of a commit SHA.
  2. The action repository becomes reachable again, but its tagged ref, release asset, dist/ bundle, or action dependency still points to malicious code.
  3. GitHub resolves the reference at job start and downloads the action bundle to the runner.
  4. The action executes with job context, reads environment variables, and may spawn node, npm, curl, wget, bash, or powershell to retrieve second-stage content.
  5. Secrets are harvested from the runner environment, step outputs, filesystem caches, git credentials, npm config, cloud metadata paths, or OIDC token exchange endpoints.
  6. The attacker uses stolen credentials to publish packages, commit workflow changes, create releases, register self-hosted runners, or access cloud resources.

There is no confirmed CVE in the item and no basis here to claim CISA KEV inclusion. Treat exploitation status as confirmed malicious code exposure rather than theoretical risk, because the summary states the actions remained accessible while still pointing to malicious code.

Immediate triage questions for every GitHub organization

Answer these before writing rules or rotating keys:

  • Do any workflows use floating refs such as @v1, @latest, @main, @master, or release tags for third-party actions?
  • Do any workflows run on pull_request_target with checkout of untrusted code or write permissions?
  • Are self-hosted runners shared across repositories with different trust levels?
  • Did any job run during the more-than-one-week exposure window, including scheduled jobs and dependabot-triggered runs?
  • Which secrets were available to those jobs, and were any OIDC tokens exchangeable for cloud roles?
  • Are release, publish, or deploy jobs separated from untrusted build/test jobs?

If the answer to the fourth question is yes or unknown, treat secrets as potentially exposed. Supply-chain incidents punish optimistic scoping.

Detection content

The following detections are intentionally scoped to CI runner behavior and GitHub workflow resolution, not generic developer workstation noise. Tune the org, repo, runner-label, and path fields before production deployment.

YAML
---
title: GitHub Actions Runner Suspicious Download or Encoded Execution
description: Detects GitHub Actions runner processes launching shell download cradles, encoded commands, or package manager script execution consistent with supply-chain payload retrieval.
status: experimental
references:
  - https://attack.mitre.org/techniques/T1195/002/
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/02/09
logsource:
  category: process_creation
  product: windows
detection:
  selection_runner:
    ParentImage|endswith:
      - '\\Runner.Worker.exe'
      - '\\Runner.Listener.exe'
    Image|endswith:
      - '\\powershell.exe'
      - '\\pwsh.exe'
      - '\\cmd.exe'
      - '\\curl.exe'
      - '\\wget.exe'
      - '\\node.exe'
      - '\\npm.cmd'
      - '\\npx.cmd'
  selection_cmd:
    CommandLine|contains:
      - 'Invoke-WebRequest'
      - 'iwr '
      - 'curl.exe '
      - 'wget.exe '
      - 'FromBase64String'
      - ' -enc '
      - 'npm install'
      - 'npx '
  condition: selection_runner and selection_cmd
falsepositives:
  - Legitimate build steps that download signed tools; suppress by repository, workflow path, runner group, and exact tool URL after validation.
level: high
---
title: Linux or macOS CI Runner Shell Spawns Egress Utility
description: Detects bash or sh under CI runner context invoking curl/wget/node/npm patterns often used to fetch second-stage supply-chain payloads.
status: experimental
references:
  - https://attack.mitre.org/techniques/T1195/002/
  - https://attack.mitre.org/techniques/T1059/004/
author: Security Arsenal
date: 2026/02/09
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentCommandLine|contains:
      - 'Runner.Worker'
      - 'actions-runner'
      - '/runners/'
      - 'runner'
  selection_img:
    Image|endswith:
      - '/bash'
      - '/sh'
      - '/node'
      - '/npm'
      - '/npx'
      - '/curl'
      - '/wget'
  selection_cli:
    CommandLine|contains:
      - 'curl '
      - 'wget '
      - 'npm install'
      - 'npm ci'
      - 'npx '
      - 'base64 -d'
      - 'https://'
      - 'http://'
  condition: selection_parent and selection_img and selection_cli
falsepositives:
  - Normal dependency installation and tool caching; baseline expected workflow commands per repository and alert only on new destinations, new parent chains, or encode/decode behavior.
level: medium
KQL — Microsoft Sentinel / Defender
// Sentinel/Defender hunt for suspicious CI runner child processes and egress
// Tune RunnerPath/OrgRepo allowlists before using as an analytic rule.
let runnerProc = dynamic(["Runner.Worker.exe","Runner.Listener.exe","runsvc","Runner.Worker","node","npm","npx","bash","sh","curl","wget","powershell.exe","pwsh.exe","cmd.exe"]);
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where FileName in~ (runnerProc) or ProcessCommandLine has_any ("Runner.Worker","actions-runner","_work","GITHUB_ACTIONS")
| where ProcessCommandLine has_any ("curl ","wget ","Invoke-WebRequest","iwr ","FromBase64String"," -enc ","base64 -d","npm install","npm ci","npx ","http://","https://")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, SHA256, FolderPath, AccountName
| sort by TimeGenerated desc;

// If GitHub audit logs or custom runner telemetry are ingested, pivot on workflow refs and job outcomes.
// Example schema-independent search for floating third-party action references collected in logs:
SecurityEvent
| where TimeGenerated > ago(14d)
| where EventData has_any ("uses:", "@main", "@master", "@v1", "pull_request_target", "id-token: write")
| project TimeGenerated, Computer, EventID, EventData
| take 200;
VQL — Velociraptor
-- Hunt runner hosts for processes spawned around GitHub Actions execution and recent egress-capable children
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE CommandLine =~ '(Runner\.Worker|actions-runner|GITHUB_ACTIONS|_work|curl |wget |Invoke-WebRequest|FromBase64String|base64 -d|npm install|npm ci|npx )'
   OR Exe =~ '(Runner\.Worker\.exe|/node|/npm|/npx|curl\.exe|wget\.exe|powershell\.exe|pwsh\.exe)'

-- Review recently modified workflow files and action lock/pin artifacts on self-hosted runners
SELECT FullPath, Size, Mtime, Ctime
FROM glob(globs=['/**/.github/workflows/*','/home/actions-runner/_work/**/.github/workflows/*','C:/actions-runner/_work/**/.github/workflows/*'])
WHERE Mtime > now() - 1209600
ORDER BY Mtime DESC
Bash / Shell
#!/usr/bin/env bash
# Audit a GitHub org for mutable third-party action refs and high-risk workflow settings.
# Requires: gh auth login with read access to the org.
set -euo pipefail
ORG="${1:?Usage: $0 <github-org> [output-dir]}"
OUT="${2:-./gha-supplychain-audit-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$OUT"

echo "[+] Listing repositories in $ORG"
gh repo list "$ORG" --limit 1000 --json nameWithOwner,defaultBranchRef,isArchived > "$OUT/repos.json"

jq -r '.[] | select(.isArchived==false) | .nameWithOwner' "$OUT/repos.json" | while read -r repo; do
  echo "[+] Scanning workflows: $repo"
  gh api "repos/$repo/contents/.github/workflows" --jq '.[].download_url' 2>/dev/null | while read -r url; do
    [ -z "$url" ] && continue
    fname=$(basename "$url")
    curl -fsSL "$url" -o "$OUT/${repo//\//_}--$fname" || true
  done
done

echo "[+] Flagging mutable refs and risky triggers/permissions"
grep -RInE 'uses:[[:space:]]*[^#]+@(main|master|latest|v[0-9]+(\.[0-9]+){0,2})[[:space:]]*$|pull_request_target:|id-token:[[:space:]]*write|actions/checkout@|setup-node@|npm-publish|aws-actions/configure-aws-credentials|azure/login|google-github-actions/auth' "$OUT" > "$OUT/findings.txt" || true

echo "[+] Looking for absence of commit SHA pinning"
grep -RInE 'uses:[[:space:]]*[^#]+@[A-Za-z0-9._/-]+' "$OUT" | grep -Ev '@[0-9a-f]{40}' > "$OUT/unpinned.txt" || true

echo "[+] Done. Review:"
echo "    $OUT/findings.txt"
echo "    $OUT/unpinned.txt"
echo "Next: pin third-party actions to full 40-char commit SHAs, reduce permissions, split untrusted PR jobs from release jobs, and rotate secrets exposed during the window."

Detection engineering guidance

Do not deploy broad rules that fire every time npm runs. CI environments naturally execute package managers. High-value detections correlate three signals: runner lineage, unexpected child behavior, and network destination novelty.

Prioritize these correlations:

  • Runner.Worker or self-hosted runner service spawning curl, wget, powershell, pwsh, node, npm, or npx with a destination not present in that repository’s historical baseline.
  • Workflow files modified outside normal change windows, especially adding pull_request_target, workflow_dispatch, new secrets, or a new third-party uses: line.
  • A job resolving an action to a different commit SHA than the pinned/allowed SHA in policy.
  • Runner egress to newly registered domains, paste/webhook endpoints, raw IP hosts, package registry lookalikes, or cloud metadata endpoints.
  • GitHub audit events for PAT creation, OAuth app grants, deploy key addition, self-hosted runner registration, repository secret changes, or release publishing shortly after suspicious job execution.

For organizations ingesting GitHub audit logs into Sentinel, build an analytic that joins workflow run telemetry with audit events by actor, repository, and time. A runner anomaly followed by repo.secrets.update, org.runner_group.update, packages.publish, or repo.create within minutes is a strong escalation path.

Remediation and hardening

  1. Freeze exposure fast. Temporarily disable the implicated third-party actions in all workflows and block resolution at the organization level if supported. If exact action names are confirmed by the vendor/source, enumerate every reference including reusable workflows and composite actions.

  2. Replace mutable references with immutable pins. Convert uses: owner/action@v3 to uses: owner/action@<40-char-commit-sha> only after reviewing that exact commit diff. Prefer GitHub-maintained actions or internal composite actions for sensitive operations. Record an allowlist of approved action SHAs per organization.

  3. Reduce token blast radius. Set default workflow permissions to read-only, then grant write scopes per job. Remove id-token: write unless OIDC is required. Never give build/test jobs production deploy credentials. Split untrusted PR execution from release/publish/deploy jobs.

  4. Constrain pull_request_target. Do not checkout untrusted PR code in privileged contexts. Use labels, manual approval, environment protection rules, and separate jobs for untrusted code versus privileged operations.

  5. Rotate exposed credentials. Rotate npm tokens, GitHub PATs, deploy keys, webhook secrets, artifact signing credentials, cloud keys, and any secret available to workflows that ran during the window. For OIDC, review cloud role trust policies and revoke sessions issued to suspicious subjects where possible.

  6. Review GitHub audit trails. Search for new PATs, OAuth authorizations, SSH/deploy keys, runner registrations, secret modifications, workflow file commits, package publishes, branch protection changes, and unusual release activity. Preserve runner logs and workflow run logs before retention expires.

  7. Enforce egress control on runners. Route self-hosted runners through egress proxies with allowlists for required endpoints such as GitHub, package registries, and artifact stores. Alert on direct-to-IP, dynamic DNS, paste sites, webhook collectors, and unknown TLS destinations. For GitHub-hosted runners, compensate with job-level secret minimization and post-run audit review.

  8. Verify remediation with evidence. Closure criteria should include: no floating third-party refs remain, all workflow runs resolve to approved SHAs, secrets are rotated, audit review found no persistence, and detections are deployed with a seven-day baseline exception process.

Executive takeaways

  • A re-enabled action is not a remediated action. Trust must be re-established with commit-level verification, not marketplace availability.
  • Pinning to tags is operationally equivalent to letting a stranger change your production build script without a change ticket.
  • CI secrets deserve the same incident response rigor as domain admin credentials; they often unlock publishing, deployment, and cloud control planes.
  • The fastest durable control is separation: untrusted code gets isolated runners and read-only tokens; release actions get approvals, environments, and minimal secrets.
  • Mature programs will continuously diff workflow files, action resolutions, and runner egress against an approved baseline.

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.