Skip to main content

GhostAction: When GitHub Becomes a Software Supply Chain Weapon

Updated 17 min readMarkdown
Part of seriesNewsletters-eng
GhostAction: When GitHub Becomes a Software Supply Chain Weapon

Executive Summary

Between August 31 and October 21, 2026, the GhostAction campaign used nearly 2,000 hijacked GitHub accounts to plant malicious workflows in tens of thousands of repositories (more than 30,000 in the October 8–12 wave alone, according to GitGuardian). The attackers did not exploit any GitHub vulnerability. They used valid tokens to install workflows that extract CI/CD secrets, cloud keys and credentials buried in git history. GitHub began blocking the malicious workflows on October 9, 2026. From October 14, the operator also planted a fake font file and a VS Code task that run on developer machines, where that block does not reach.

The biggest risk is wholesale exposure of AWS and Kubernetes credentials, npm and PyPI registry tokens, and API keys for AI tools. With those, an attacker can take over cloud infrastructure or push a supply chain attack into shipped software. The single most important action is to revoke GitHub tokens immediately from a clean device and rotate every secret, rather than only deleting the malicious workflow.

What GhostAction Really Is

GhostAction does not exploit any weakness in the GitHub platform. The attacker uses personal tokens and valid accounts that victims had already leaked. According to StepSecurity, the tokens most likely come from infostealer logs or credential dumps that were never cleaned up. The attacker commits a malicious workflow to every repository the victim account can write to. Every commit carries the victim's real identity, which makes the activity hard to spot with ordinary audit controls.

Many teams simply delete the malicious workflow file once they notice it. That does not fix the root cause. A token that has not been revoked lets the attacker push a new malicious workflow again. According to GitGuardian, 1,382 of the 1,792 accounts abused in the October 13–21 wave had already been used on October 8.

GhostAction has never really stopped since September 2025. The attacker has kept rotating the collection infrastructure from one phase to the next:

Period Collection endpoint Delivery Observed scale
Sep 2025 bold-dhawan.45-139-104-115.plesk[.]page, carte-avantage[.]com Secret-exfiltration workflow 817 repos, 327 accounts, at least 3,325 secrets taken
Oct–Dec 2025 & Mar 2026 170.39.218[.]2 Secret-exfiltration workflow About 75 repositories
Nov 2025 & Mar–Apr 2026 *.oast[.]fun (Interactsh) Secret-exfiltration workflow About 250 repositories
Aug 31 – Sep 30, 2026 193.32.204[.]199 (port 80; the Sep 7 variant used port 3000) github_actions_security.yml 772 repos, 373 accounts, 2,577 secrets targeted
Oct 8–12, 2026 193.32.204[.]199 security-audit.yml More than 30,000 repos, about 1,900 hijacked accounts
Oct 13–21, 2026 193.32.204[.]199 (port 8080) 5 payload families (see below) 22,964 repos, 1,792 pushing accounts

Timeline and Attack Chain

The campaign evolved continuously, changing techniques several times:

Date Event
Sep 2025 GhostAction disclosed: malicious workflows planted in 817 public repositories of 327 users, at least 3,325 secrets exposed.
Oct 2025 – Apr 2026 The same workflow template keeps appearing with endpoints 170.39.218[.]2 (about 75 repos) and *.oast[.]fun (about 250 repos).
Aug 31 – Sep 30, 2026 github_actions_security.yml pushed to 772 repositories of 373 users and organizations. GitHub held most runs for approval: of 3,669 runs GitGuardian collected across 605 repositories, only 499 executed, in 32 repositories; 336 succeeded and took 26 secrets from 13 repositories. By October 5, 2026, only 124 repositories (16%) had been cleaned up.
Oct 8, 2026 The kitao account pushes the workflow to 27 repositories from 13:20 UTC. The henrywoo account then pushes it to 318 repositories in 16 minutes. The security-audit.yml workflow sweeps the entire git history. Socket confirms successful exfiltration.
Oct 9, 2026 GitHub starts blocking the malicious workflows and requiring manual approval. The domain my-github[.]com is registered at 09:50 UTC, pointing to the GhostAction exfiltration IP; a phishing email follows the same day (per The Hacker News, citing OpenSourceMalware).
Oct 10 & 12, 2026 The attacker keeps pushing workflows, but no secrets are captured thanks to GitHub's block.
Oct 13–21, 2026 Major escalation: 72,026 commits to 22,964 repositories of 2,875 owners from 1,792 accounts. Peak on October 15 with 32,099 commits. Fake fonts and VS Code tasks appear. Last commit at 04:37 UTC on October 21.
Oct 22–26, 2026 188 GhostAction commits are amended with a PolinRider loader and force-pushed.

The diagram below traces the attack from leaked tokens to GitHub's block and the shift to developer workstations:

Figure 1: GhostAction attack chain, from leaked tokens to secret theft in CI/CD and on developer workstations.

Commit volume swung sharply during the October escalation:

Daily commits and targeted repositories

Figure 2: Daily commits and targeted repositories from October 13 to 21, 2026, peaking on October 15.

The October 8 Burst: Committing Under the Victim's Identity

October 8, 2026 saw a sharp burst after the attacker took over two well-known developer accounts. At 13:20 UTC, the kitao account (author of the pyxel game engine, about 18,400 stars) pushed the malicious workflow to 27 repositories. About eight hours later, the henrywoo account (original author of uber/athenadriver) pushed it to 318 repositories in just 16 minutes (21:10–21:26 UTC). The list covered 39 source repositories, 279 forks and uber/athenadriver itself. In total, 346 repositories were hit, according to StepSecurity, Socket and OpenSourceMalware.

The new security-audit.yml workflow (display name "Security Audit", commit message "Add security audit workflow") sweeps both the working tree and the full git history. The sources differ on the number of credential patterns: StepSecurity counted 13 regexes, while GitGuardian found 11 in the first version. Either way, secrets that were deleted from the code but remain in commit history can still be harvested.

Socket and StepSecurity confirmed successful exfiltration in the October 8 wave, including data from uber/athenadriver. The commits were made through the API with a valid token and were unsigned. The audit log simply shows normal activity by the victim. According to Socket (via The Hacker News), more than 500 GitHub accounts had committed the malicious workflow to tens of thousands of repositories since October 7. GitGuardian's October 13 update said the October 8–12 wave targeted more than 30,000 repositories belonging to about 3,000 users and organizations, from nearly 1,900 hijacked accounts, and attempted to take 3,174 secrets.

GitHub Steps In, and Five Payload Families Follow (October 13–21)

From October 9, 2026, GitHub began automatically blocking malicious workflows. When it flags a suspicious workflow, the run is held and must be approved by someone with write access. The October 10 and 12 waves captured no secrets at all:

GitHub holding security-audit.yml for approval

Figure 3: GitHub Actions flags the malicious security-audit.yml workflow and holds it pending approval.

From October 13, the operator rolled out five different payload families (GitGuardian figures). OpenSourceMalware notes that it cannot say GitHub's block caused this change:

Payload family Files Runs on Commits Repos
Security-audit re-point security-audit.yml (endpoint moved to port 8080) GitHub-hosted runner 6,988 6,902
Seven workflows audit.yml, build-check.yml, repo-health.yml, lint.yml, ci-check.yml, test-suite.yml, code-review.yml GitHub-hosted runner 33,411 19,153
Font stealer public/fonts/fa-solid-400.woff2 plus .vscode/tasks.json Developer machine 30,804 17,667
Self-hosted maintenance.yml, cache-cleanup.yml, runner-maintenance.yml, cleanup-job.yml, cleanup_cache.yml Self-hosted runner 257 172
Legacy OIDC github_actions_security.yml GitHub-hosted runner + AWS 568 418

In the seven-workflows family, the attacker added search patterns for AI service API keys. If the upload to the C2 fails, the script creates a private GitHub Gist named "build cache" containing a c.dat file as a fallback channel. GitHub's defenses kept blocking these workflows as well:

GitHub blocking build-check.yml

Figure 4: GitHub blocks execution of build-check.yml from the seven-workflows family.

GitGuardian found no successful CI run of any of these payloads during October 13–21. In GitGuardian's data, GitHub's workflow hold stopped the CI branch of this wave.

Beyond CI: Font Stealer, Cron Persistence and OIDC

Alongside the workflows, the attacker targeted developer workstations with a font stealer (starting October 14). The file public/fonts/fa-solid-400.woff2 is actually Node.js source code. It ships with a .vscode/tasks.json that declares a hidden task which runs automatically when the folder is opened (folderOpen), once the user chooses to trust the workspace.

When a developer opens the project in VS Code, the malware runs on the workstation and reads a long list of secrets: ~/.aws/credentials, .pypirc, .npmrc, .git-credentials, .netrc, the AI tool credentials ~/.claude/.credentials.json and ~/.gemini/oauth_creds.json, .kube/config, SSH keys and environment variables. The data is sent to 193.32.204[.]199:8080. GitHub's workflow block does not apply to personal workstations.

The sources disagree on how many repositories carry the font stealer. OpenSourceMalware (via The Hacker News on October 26) counted 1,230 repositories of 391 accounts with both files (and 1,279 repositories of 411 accounts with some components), while GitGuardian counted 17,667 repositories carrying the font and tasks.json pair. The two figures reflect different survey scopes.

To keep control, three payload families (font stealer, self-hosted and the full1 variant) install a cron job that runs every five minutes, fetches a script from the C2 by hostname and is tagged #ghc2. Cleaning the repository does not remove this cron job.

On self-hosted runners, the malware queries IMDS for cloud role credentials and reads local credentials and SSH keys. GitGuardian warns that self-hosted runners sit inside the internal network and can easily become a pivot point. In the legacy OIDC family, the script tries to obtain a GitHub OIDC token to assume roles in AWS. Jenn Gile (OpenSourceMalware, via The Hacker News) said the code looks inert as written and that she cannot say whether it has worked anywhere.

From October 22 to 26, 2026, 188 GhostAction commits were amended with a PolinRider loader and force-pushed. PolinRider is a separate campaign originally attributed to the DPRK. GitGuardian says there are no clear indicators that the two groups are related. GitGuardian's working hypothesis is that GhostAction copied PolinRider's public launcher and that the two have overlapping sets of victims.

The my-github Phishing Lure and OAuth Token Harvesting

The domain my-github[.]com was registered on October 9, 2026 at 09:50 UTC, pointing to 193.32.204[.]199 (the GhostAction exfiltration IP) with wildcard DNS *.my-github[.]com. OpenSourceMalware initially classified it as suspicious infrastructure linked by IP, with the operator unconfirmed. According to The Hacker News, citing OpenSourceMalware, the domain was later used in a fake GitHub security alert sent from noreply@my-github[.]com to an open-source maintainer, warning of a suspicious login from Moscow and asking them to click a link to secure the account.

The fake page acts as a reverse proxy that harvests the password and 2FA code. Injected JavaScript silently approves a GitHub OAuth device flow with the repo, workflow and read:org scopes. The OAuth token is sent to my-github[.]com on port 8443. The victim never has to type the device code.

A repository-scoped OAuth token is a reusable API credential, and the repo and workflow scopes match GhostAction's ability to enumerate repositories and plant workflows under .github/workflows/ (per The Hacker News, citing OpenSourceMalware). The domain my-gitlab[.]com (registered September 22, 2026) has not been shown to be malicious and has no infrastructure link to GhostAction, so it is only on a watchlist.

IOCs (Indicators of Compromise)

Current GhostAction infrastructure

193.32.204[.]199 (HTTP port 80; 3000 /api/workflow/receive?inj=<id>; 8080 /?c=<tag> and /c2/<hostname>)
my-github[.]com, *.my-github[.]com (ghassets., github., api., gh.)

Legacy GhostAction infrastructure

170.39.218[.]2
45.139.104[.]115
bold-dhawan.45-139-104-115.plesk[.]page
objective-hopper.45-139-104-115.plesk[.]page
carte-avantage[.]com

Markers and Gist

Crontab marker: #ghc2
Private Gist: "build cache" containing c.dat

File paths in repositories

Note: A file name alone is not an IOC; check the file content and related infrastructure.

.github/workflows/github_actions_security.yml
.github/workflows/security-check.yml
.github/workflows/security-audit.yml
.github/workflows/audit.yml
.github/workflows/build-check.yml
.github/workflows/repo-health.yml
.github/workflows/lint.yml
.github/workflows/ci-check.yml
.github/workflows/test-suite.yml
.github/workflows/code-review.yml
.github/workflows/maintenance.yml
.github/workflows/cache-cleanup.yml
.github/workflows/runner-maintenance.yml
.github/workflows/cleanup-job.yml
.github/workflows/cleanup_cache.yml
public/fonts/fa-solid-400.woff2
.vscode/tasks.json

PolinRider in co-infected repositories

Ethereum address: 0x1eef0849eda15d87f64e51c73c8442cf3c8bcef3
Fallback C2: hxxp://95.135.181[.]151:443
SHA-256 api.js: b59e598ded824c7318ca9fae4e9a6d98c9600873677ece7d9896870e3e7ced8e
SHA-256 fa-solid-300.ttf: 7ba0a30eabfa46008d6417532533199bbf351b27f3841477e9133f43fb404d24
SHA-256 tasks.json: 284d167f51c93b90100b7faeaec971959c1f8bb642e7ea0de50190a5a306f3d8
Persistence: ~/.node_modules/.npm-ws-runner.js, .npm-ws.json, .npm-ws.pid

Detection & Hunting

Security teams can apply the following queries and rules right away to hunt for GhostAction:

Use OpenSourceMalware's exact search syntax to find malicious workflows:

"193.32.204.199" path:.github/workflows

Search for the campaign's characteristic commit messages:

"Add Github Actions Security workflow"
"Add security audit workflow"

Scanning local repositories

Look for VS Code tasks that run on folder open:

grep -rl --include=tasks.json 'folderOpen' .

Identify fake fonts (a real font is reported as "Web Open Font Format"; the fake one is a text script):

find . -path '*/fonts/*' -name '*.woff2' -exec file {} \;

Review unexpected workflows and the commit history of the workflow folder:

ls -la .github/workflows/
git log --all --format='%h %an %ad %s' -- .github/workflows/

Developer machines and Linux/macOS runners

Search for the malicious cron job with the #ghc2 marker:

crontab -l | grep -n 'ghc2'

Auditing Gists and the Audit Log

List secret Gists and look for the "build cache" title:

gh gist list --secret | grep -i "build cache"

In the GitHub organization audit log, review pushes that change .github/workflows/ from unusual accounts or at unusual times, and new OAuth apps or tokens granted the repo and workflow scopes. Exact event names depend on your GitHub plan; check GitHub's audit log documentation.

AWS CloudTrail monitoring

Trace OIDC authentication requests to AWS STS that may impersonate a repository:

{
  "eventSource": "sts.amazonaws.com",
  "eventName": "AssumeRoleWithWebIdentity",
  "requestParameters": {
    "roleArn": "arn:aws:iam::123456789012:role/<TargetRolePlaceholder>"
  }
}

Note: Check the sub claim of the OIDC token to confirm that the repository and branch (repo:<org>/<repo>:ref:refs/heads/<branch>) are legitimate.

Sigma rules for suspicious process execution

The following multi-document Sigma YAML contains two rules with proper UUID v4 identifiers:

title: Suspicious Execution of Script Files Disguised as Fonts via Node.js
id: 787f69d6-d0ee-41f4-a16c-d965170a1caf
status: experimental
description: Detects Node.js running a file with a font extension (.woff2, .woff, .ttf) as a script, an indicator of the GhostAction font stealer and the PolinRider loader.
references:
    - https://blog.gitguardian.com/ghostaction-x-polinrider/
author: FPT IS CSC - Threat Intelligence
date: 2026-11-04
tags:
    - attack.execution
    - attack.t1059.007
logsource:
    category: process_creation
    product: linux
detection:
    selection_image:
        Image|endswith: '/node'
    selection_cli:
        CommandLine|endswith:
            - '.woff2'
            - '.woff'
            - '.ttf'
    condition: selection_image and selection_cli
falsepositives:
    - Rare; front-end build scripts that take a font path as the last argument (exclude based on baseline).
level: high
---
title: GhostAction Campaign Indicators in Process Command Line
id: 94704fd6-5095-4c8c-ac1c-2c0922eba670
status: experimental
description: Detects command lines containing the GhostAction C2 IP, the phishing domain or the crontab marker.
references:
    - https://blog.gitguardian.com/ghostaction-x-polinrider/
author: FPT IS CSC - Threat Intelligence
date: 2026-11-04
tags:
    - attack.exfiltration
    - attack.t1041
    - attack.persistence
    - attack.t1053.003
logsource:
    category: process_creation
    product: linux
detection:
    selection:
        CommandLine|contains:
            - '193.32.204.199'
            - 'my-github.com'
            - '#ghc2'
    condition: selection
falsepositives:
    - Security testing or internal hunting activity.
level: high

SIEM and EDR hunting queries

Splunk SPL to review proxy, firewall and DNS traffic:

(index=proxy OR index=firewall OR index=dns)
(dest_ip="193.32.204.199" OR dest_ip="170.39.218.2" OR dest_ip="45.139.104.115"
 OR query="*my-github.com*" OR url="*my-github.com*")
| eval query_target=coalesce(url, query, dest_ip)
| stats earliest(_time) as first_seen latest(_time) as last_seen count by src_ip, dest_ip, query_target
| convert ctime(first_seen) ctime(last_seen)

Note: The index, dest_ip, query and url fields are placeholders; adapt them to your own data model.

KQL for Microsoft Defender for Endpoint:

// Connections to GhostAction C2 infrastructure
DeviceNetworkEvents
| where RemoteIP in ("193.32.204.199", "170.39.218.2", "45.139.104.115")
     or RemoteUrl contains "my-github.com"
| project Timestamp, DeviceName, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteIP, RemotePort, RemoteUrl

// Node.js running a suspicious font file
DeviceProcessEvents
| where FileName =~ "node" or InitiatingProcessFileName =~ "node"
| where ProcessCommandLine contains ".woff2" or ProcessCommandLine contains "/fonts/" or ProcessCommandLine contains @"\fonts\"
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessCommandLine

Note: The fields above follow the Microsoft Defender for Endpoint schema but have not been tested in a live environment.

Assessment

In our analysis, the root cause of GhostAction is tokens that were never revoked. Deleting the malicious workflow file does not make a repository safe. The valid key is still in the attacker's hands, so "file deleted" does not mean "secure".

GitHub's workflow block proved effective on CI infrastructure. Yet within a few days, the operator had added a branch that runs on developer workstations through the font stealer. In our view, this shows that VS Code auto-run tasks are an easily overlooked attack surface [Inference].

Technically, the campaign shows limited skill, with basic YAML syntax errors and unfinished OIDC code. Even so, it reached a very large scale by automating the abuse of a stockpile of old tokens. The added search patterns for AI tool credentials (Claude, Gemini, OpenAI) suggest these keys have become a valuable target [Inference].

The sources contain no figures specific to Vietnam. In our assessment, any organization that uses GitHub with self-hosted runners inside its internal network, or uses OIDC to AWS without restricting trust by repository and branch, should treat GhostAction as a direct risk [Inference]. A single leaked token or a repository carrying the font stealer can open a path from a developer machine or runner into the internal network [Inference].

Phased Defense Recommendations

Immediate actions

  1. Revoke all tokens from a clean machine: Sign in to GitHub from a trusted device, revoke every PAT, remove unknown OAuth apps and review SSH keys.
  2. Remove IOCs on every branch; do not revert "Update" commits: Delete the malicious files on all branches and forks. Never revert an "Update" commit, because that restores the previous malicious version.
  3. Rotate every secret: Replace all secrets in the repository, in git history and in the internal wiki (AWS, registries, AI tokens).
  4. Clean up the #ghc2 cron job: Find and remove the #ghc2 cron entry on developer machines and runners, and rotate local credentials (~/.aws, SSH keys).
  5. Audit Gists and CloudTrail: Delete the private "build cache" Gist and review AssumeRoleWithWebIdentity events in AWS CloudTrail.

Short-term measures

  1. Tighten the AWS OIDC trust policy: Restrict the sub claim to specific repositories and branches and remove wildcard conditions.
  2. Require approval for workflow runs: According to StepSecurity, requiring approval for workflow runs is the only control observed stopping exfiltration in the October 8 wave (repository typecho-fans/plugins). Note: a setting that applies only to forks or outside collaborators will not stop commits pushed with the hijacked account itself [Inference].
  3. Branch protection and CODEOWNERS: Require code review for every change under .github/workflows/**.
  4. Egress allowlist for runners: Block runners from connecting directly to raw IP addresses that bypass DNS resolution.
  5. Disable VS Code auto-run tasks: Set "task.allowAutomaticTasks": "off" in VS Code settings, keep Workspace Trust enabled and do not trust unknown repositories.

Long-term strategy

  1. Standardize on fine-grained PATs: Require fine-grained PATs with minimal per-repository scope and a lifetime under 30 days.
  2. Enforce hardware-key 2FA (FIDO2/passkeys): Origin-bound keys help resist reverse-proxy phishing like my-github[.]com, where OTP codes get captured [Inference].
  3. Secret scanning in pre-commit hooks: Add automated secret scanning so credentials never land in git history.
  4. Ephemeral runners: Use runners that are destroyed after each run to limit persistence such as the #ghc2 cron job on self-hosted runners.

References

  1. GitGuardian (Oct 7, 2026, updated Oct 13): GhostAction Returns: 772 Repos Hit, 30k More in Oct
  2. GitGuardian (Oct 27, 2026): GhostAction Crosses Paths With PolinRider: Inside the October Escalation
  3. OpenSourceMalware (Oct 9, 2026): GhostAction Attack Escalates By Targeting GitHub Users
  4. StepSecurity (Oct 2026): GhostAction Returns: Malicious "Security Audit" Workflows Now Mine Credentials from Entire Git Histories
  5. The Hacker News (Oct 9, 2026, updated Oct 15): Credential-Stealing GitHub Actions Workflows Planted in Tens of Thousands of Repositories
  6. The Hacker News (Oct 19, 2026, updated Oct 28): GhostAction Attackers Target Nearly 23,000 GitHub Repos with Credential-Stealing Malware
  7. OpenSourceMalware (Oct 2026): GhostAction Copycats PolinRider by Hiding a Stealer in a Fake Font File

No comments yet. Be the first to comment.

More from this blog