The threat actor behind the GhostAction supply chain campaign has evolved its tradecraft again — and this time the delivery vehicle is something most developers would never think twice about: a font file. According to reporting from OpenSourceMalware, the operators have moved beyond simply injecting malicious GitHub Actions workflows into compromised projects and are now social engineering open-source developers directly, delivering a credential stealer disguised inside a fake font. At least 94 GitHub repositories are confirmed impacted.
This matters far beyond the 94 repos on that list. GhostAction's original campaign demonstrated a nasty recursive quality: steal maintainer credentials, use those credentials to push malicious workflow changes into legitimate projects, harvest CI/CD secrets from the pipelines of those projects, and repeat. Every compromised maintainer becomes a launchpad for the next wave. If your organization consumes open-source dependencies — and every organization does — your developers, your build runners, and your secrets are in the blast radius.
There is no CVE here and no patch to deploy. The remediation is behavioral, architectural, and detective. This post breaks down the attack chain and gives you concrete hunts for your endpoints, your CI/CD runners, and your GitHub organizations.
Technical Analysis
What GhostAction Is Doing
GhostAction's initial modus operandi was CI/CD pipeline poisoning: after compromising a project, the actor injected malicious GitHub Actions workflow files designed to dump secrets — GitHub tokens, cloud provider credentials, package registry tokens (npm, PyPI), SSH keys, and signing material — and exfiltrate them during workflow execution. Workflow contexts are treasure troves: they routinely hold elevated tokens and long-lived credentials with write access to production infrastructure.
The new evolution, documented by OpenSourceMalware, adds a human layer:
- Social engineering of maintainers. The actor approaches open-source developers — through issues, pull requests, or direct contact — and convinces them to download or open a payload presented as a font file (extensions such as
.ttf,.otf,.woff, or.woff2). - Masqueraded stealer payload. The "font" is not a font. It is a stealer executable relying on extension masquerading and user trust. On execution, it performs classic stealer behavior: harvesting browser credential stores (
Login Data,Local State, cookies), session tokens, saved credentials, and developer-specific material such as SSH keys and CLI tokens. - Recursive compromise. Stolen maintainer credentials are then used to push malicious workflow changes into the victim's repositories, feeding the original pipeline-poisoning campaign. The stealer is the seed; the workflow injection is the harvest.
Affected Scope
- Confirmed: 94 GitHub repositories, per OpenSourceMalware's disclosure.
- At risk: open-source maintainers and contributors (the social engineering targets), self-hosted and hosted GitHub Actions runners processing poisoned workflows, and every downstream consumer of packages published from compromised accounts.
- Platforms: developer workstations (Windows primary target for the stealer component), Linux/macOS CI runners for the workflow component.
Exploitation Status
- CVE: None assigned. This is a campaign, not a vulnerability — do not wait for a CVE to act.
- Status: Confirmed active, in-the-wild exploitation. This is not theoretical. Repositories are compromised and credentials are being stolen today.
- CISA KEV: Not applicable (no CVE).
The strategic read: the actor is adapting in response to defensive pressure on workflow-injection detection. When defenders started auditing .github/workflows diffs, the actor moved upstream — to the human with the commit bit. Expect continued iteration.
Detection & Response
The detections below target three distinct, high-fidelity observables from this campaign: (1) execution of files masquerading as fonts, (2) stealer access to browser credential stores, and (3) secret-harvesting behavior on GitHub Actions runners. Tune thresholds to your environment, but these patterns should be close to silent in a healthy one.
Sigma Rules
---
title: Execution of File Masquerading as Font
description: Detects execution of a file whose name carries a font extension (.ttf, .otf, .woff, .woff2, .fnt). Fonts are data files rendered by system libraries; they should never appear as an executed image. Consistent with GhostAction's stealer delivery disguised as a fake font file.
references:
- https://thehackernews.com/2026/10/ghostaction-attackers-plant-credential.html
- https://attack.mitre.org/techniques/T1036/
author: Security Arsenal
id: 3f7a2c41-9b5e-4d18-a6c2-7e1f0d8b4a93
status: experimental
date: 2026/10/15
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith:
- '.ttf'
- '.otf'
- '.woff'
- '.woff2'
- '.fnt'
condition: selection
falsepositives:
- Extremely rare; corrupted telemetry or misnamed internal tooling
level: high
tags:
- attack.defense_evasion
- attack.t1036
---
title: Non-Browser Process Accessing Browser Credential Stores
description: Detects a non-browser process referencing browser credential artifacts (Login Data, Local State, Cookies, Web Data) on the command line. Stealers, including those distributed in the GhostAction campaign, routinely copy these locked files before parsing credentials and session tokens offline.
references:
- https://thehackernews.com/2026/10/ghostaction-attackers-plant-credential.html
- https://attack.mitre.org/techniques/T1555/003/
author: Security Arsenal
id: 8c1d4e62-2a7f-4b39-9d05-5c6e3a1f7b28
status: experimental
date: 2026/10/15
logsource:
category: process_creation
product: windows
detection:
selection_paths:
CommandLine|contains:
- '\Login Data'
- '\Local State'
- '\Network\Cookies'
- '\Web Data'
- 'logins.json'
- 'key4.db'
filter_browsers:
Image|endswith:
- '\chrome.exe'
- '\msedge.exe'
- '\brave.exe'
- '\firefox.exe'
- '\opera.exe'
filter_browsers_child:
ParentImage|endswith:
- '\chrome.exe'
- '\msedge.exe'
- '\brave.exe'
- '\firefox.exe'
condition: selection_paths and not 1 of filter_*
falsepositives:
- Enterprise password managers or EDR forensics tooling legitimately inspecting browser stores; allowlist by signer and hash
level: high
tags:
- attack.credential_access
- attack.t1555.003
---
title: GitHub Actions Runner Secret Harvesting Behavior
description: Detects secret-dumping behavior from a GitHub Actions runner process, consistent with malicious workflows injected by the GhostAction actor. Includes environment enumeration (printenv, /proc/self/environ) and base64-encoded piping to curl/wget, which legitimate workflows almost never perform.
references:
- https://thehackernews.com/2026/10/ghostaction-attackers-plant-credential.html
- https://attack.mitre.org/techniques/T1552/
author: Security Arsenal
id: 5b9e7a30-1c4d-48f6-b2a1-9d3c6f0e5a74
status: experimental
date: 2026/10/15
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith:
- '/Runner.Worker'
- '/Runner.Listener'
selection_envdump:
CommandLine|contains:
- 'printenv'
- '/proc/self/environ'
- '/proc/1/environ'
- 'env |'
selection_b64_exfil:
CommandLine|contains:
- 'base64'
Image|endswith:
- '/curl'
- '/wget'
- '/sh'
- '/bash'
condition: selection_parent and 1 of selection_*
falsepositives:
- Rare; some debugging steps dump environment variables in CI. Review the originating workflow file and commit author before dismissing.
level: high
tags:
- attack.credential_access
- attack.t1552
- attack.exfiltration
KQL — Microsoft Sentinel / Defender
Two hunts: endpoint masquerade/stealer behavior via Defender tables, and runner-side secret harvesting via ingested Syslog/CEF from self-hosted runners. Run both over a 14-day lookback initially, then convert to scheduled analytics.
// Hunt 1: Font-masquerade execution + non-browser access to credential stores
let lookback = 14d;
let browsers = dynamic(["chrome.exe","msedge.exe","brave.exe","firefox.exe","opera.exe"]);
let FontExec = DeviceProcessEvents
| where TimeGenerated > ago(lookback)
| where FileName has_any (".ttf",".otf",".woff",".woff2",".fnt")
or ProcessCommandLine has_any (".ttf",".otf",".woff2")
| project TimeGenerated, DeviceName, FileName, FolderPath, ProcessCommandLine, SHA256, InitiatingProcessAccountName;
let CredStoreAccess = DeviceFileEvents
| where TimeGenerated > ago(lookback)
| where FolderPath has_any ("Login Data","Local State","Network\\Cookies","Web Data","logins.json","key4.db")
| where InitiatingProcessFileName !in~ (browsers)
| where ActionType in~ ("FileCreated","FileModified","FileRenamed")
| project TimeGenerated, DeviceName, ActionType, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessSHA256;
FontExec
| union CredStoreAccess
| sort by TimeGenerated desc;
// Hunt 2: Secret harvesting on self-hosted GitHub Actions runners (Syslog ingestion)
Syslog
| where TimeGenerated > ago(lookback)
| where ProcessName has_any ("curl","wget","bash","sh","env")
or SyslogMessage has_any ("Runner.Worker","Runner.Listener")
| where SyslogMessage has_any ("printenv","/proc/self/environ","/proc/1/environ","env |","base64")
| where SyslogMessage has "Runner"
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| sort by TimeGenerated desc;
Velociraptor VQL
This artifact sweeps user-writable directories for files carrying font extensions and validates the magic bytes. A TrueType font starts with 0x00010000 or OTTO; a MZ (PE) or ELF header inside a .ttf is a smoking gun.
-- GhostAction font-masquerade hunt: font-extension files that are actually executables
SELECT FullPath, Size, Mtime, Magic,
hash(path=FullPath) AS Hash
FROM (
SELECT FullPath, Size, Mtime,
read_file(filename=FullPath, length=4) AS Magic
FROM glob(globs=[
'C:\\Users\\*\\Downloads\\**.{ttf,otf,woff,woff2,fnt}',
'C:\\Users\\*\\Desktop\\**.{ttf,otf,woff,woff2,fnt}',
'C:\\Users\\*\\AppData\\Local\\Temp\\**.{ttf,otf,woff,woff2,fnt}'
])
)
WHERE Magic =~ '^(MZ|\\x7fELF)'
Complement with a process listing sweep for anything executing from a font-named path — SELECT Pid, Name, Exe, CommandLine FROM pslist() WHERE Exe =~ '(?i)\\.(ttf|otf|woff2?)$' — to catch live infections before cleanup.
Remediation Scripts
First, audit your GitHub organization for signs of workflow poisoning and masqueraded binaries committed to repos:
#!/usr/bin/env bash
# GhostAction supply-chain audit — requires: gh CLI (authenticated), git
set -euo pipefail
ORG="${1:?Usage: $0 <github-org>}"
DAYS="${2:-30}"
SINCE=$(date -d "-${DAYS} days" +%Y-%m-%d 2>/dev/null || date -v-"${DAYS}"d +%Y-%m-%d)
echo "[*] Auditing org: ${ORG} | workflow changes since ${SINCE}"
# 1) Flag recently added/modified workflow files across all repos
for repo in $(gh repo list "$ORG" --limit 500 --json nameWithOwner -q '.[].nameWithOwner'); do
gh api "repos/${repo}/commits?path=.github/workflows&since=${SINCE}T00:00:00Z" \
--jq '.[] | "\(.sha[0:8]) \(.commit.author.date) \(.commit.author.name) \(.commit.message | split("\n")[0])"' 2>/dev/null \
| while read -r line; do echo "[WORKFLOW-CHANGE] ${repo}: ${line}"; done
done
# 2) Search workflow contents for secret-harvesting / exfil patterns
echo "[*] Scanning workflow content for suspicious patterns"
gh search code --owner "$ORG" --language yaml \
'printenv OR "/proc/self/environ" OR "base64 -d" OR "curl -d" path:.github/workflows' \
--json repository,path -q '.[] | "[SUSPICIOUS-WORKFLOW] \(.repository.nameWithOwner): \(.path)"' 2>/dev/null || true
# 3) Hunt font-extension blobs in repos (potential masqueraded payloads)
echo "[*] Searching for font files committed to repositories"
gh search code --owner "$ORG" --extension ttf --json repository,path \
-q '.[] | "[FONT-FILE] \(.repository.nameWithOwner): \(.path)"' 2>/dev/null || true
gh search code --owner "$ORG" --extension otf --json repository,path \
-q '.[] | "[FONT-FILE] \(.repository.nameWithOwner): \(.path)"' 2>/dev/null || true
gh search code --owner "$ORG" --extension woff2 --json repository,path \
-q '.[] | "[FONT-FILE] \(.repository.nameWithOwner): \(.path)"' 2>/dev/null || true
echo "[*] Done. Manually verify any FONT-FILE hits with: git show <sha>:<path> | xxd | head -1"
echo " 'MZ' or 'ELF' magic in a font file = active compromise. Rotate all repo and maintainer secrets immediately."
Second, sweep Windows developer workstations for masqueraded payloads:
# GhostAction font-masquerade endpoint sweep + quarantine
$ScanPaths = @("$env:USERPROFILE\Downloads", "$env:USERPROFILE\Desktop", "$env:TEMP", "$env:USERPROFILE\AppData\Local\Temp")
$FontExts = @('.ttf', '.otf', '.woff', '.woff2', '.fnt')
$Quarantine = "C:\IR-Quarantine\GhostAction-$(Get-Date -Format 'yyyyMMdd')"
foreach ($path in $ScanPaths) {
if (-not (Test-Path $path)) { continue }
Get-ChildItem -Path $path -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $FontExts -contains $_.Extension.ToLower() } |
ForEach-Object {
$fs = [System.IO.File]::OpenRead($_.FullName)
$buf = New-Object byte[] 4
[void]$fs.Read($buf, 0, 4); $fs.Close()
$magic = [System.Text.Encoding]::ASCII.GetString($buf)
# Legit fonts: 0x00010000, 'OTTO', 'wOFF', 'wOF2'. Anything else is suspicious.
if ($magic -match '^MZ' -or $buf[0] -eq 0x7F -and $magic -match 'ELF' -or
($magic -notmatch 'OTTO|wOFF|wOF2' -and -not ($buf[0] -eq 0x00 -and $buf[1] -eq 0x01))) {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
Write-Warning "MASQUERADE: $($_.FullName) | SHA256: $hash | Magic: $($buf | ForEach-Object { $_.ToString('X2') })"
New-Item -ItemType Directory -Path $Quarantine -Force | Out-Null
Move-Item $_.FullName -Destination $Quarantine -Force
Write-Host "[+] Quarantined to $Quarantine" -ForegroundColor Yellow
}
}
}
Write-Host "[*] Sweep complete. If hits were found: rotate browser-stored credentials, revoke GitHub/cloud CLI tokens on this host, and check for unauthorized workflow commits by this user's account."
Remediation
There is no vendor patch — remediation is a combination of credential hygiene, pipeline hardening, and maintainer education. Prioritize in this order:
- Rotate exposed secrets — assume compromise. If any repo in your org appears in the OpenSourceMalware disclosure, or any hunt above fires, immediately rotate: GitHub personal access tokens and fine-grained tokens, cloud credentials (AWS access keys, Azure service principals, GCP service account keys), npm/PyPI/RubyGems publishing tokens, SSH deploy keys, and code-signing certificates stored in CI secrets. Check GitHub audit logs for token use from unfamiliar IPs/ASNs after rotation.
- Audit
.github/workflowshistory. Review every workflow addition or modification in the last 30–60 days, by author, across every repo. Malicious GhostAction workflows are often single-commit additions from compromised maintainer accounts. Verify each workflow change with the purported author out-of-band. - Pin third-party Actions to full commit SHAs, not mutable tags (
uses: actions/checkout@<sha>), and enable GitHub's "Require approval for all outside collaborators" and "Allow only verified/creator-approved actions" policies where feasible. - Protect the workflow path. Require CODEOWNERS review for changes to
.github/**, enable branch protection on default branches, and setGITHUB_TOKENdefault permissions to read-only, granting write scopes explicitly per-job. - Replace long-lived secrets with OIDC. Use OpenID Connect federation to cloud providers and package registries so there are no static credentials in CI for a poisoned workflow to steal.
- Enable secret scanning and push protection (GitHub Advanced Security or equivalent) so that even if a workflow dumps a secret to logs or a commit, it is caught and revoked quickly.
- Quarantine the 94 confirmed repos. Cross-reference OpenSourceMalware's published list against your dependency tree (direct and transitive). Pin or remove affected packages until maintainers confirm clean states.
- Brief your maintainers. The delivery mechanism is social engineering — a friendly contributor asking a developer to "install this font" or review an asset. Establish a hard rule: no unsolicited binaries, fonts, or "design assets" are opened outside a sandbox. Verify contributor identity through a second channel before engaging with attachments.
- Sanitize runner images. If you run self-hosted runners, treat any runner that executed a flagged workflow as compromised: reimage it, and audit its network egress for the execution window.
Bottom Line
GhostAction is a case study in attacker adaptability: when defenders hardened the pipeline, the actor moved to the person holding the keys to the pipeline. The fake-font stealer is low-tech by design — it doesn't need to be sophisticated when a maintainer's browser profile contains session tokens for every repository they own. Your defenses have to cover both layers: endpoint detection for the masquerade and stealer behavior, and structural hardening of CI/CD so that a single stolen credential cannot cascade into a fleet-wide supply chain compromise. Run the hunts above this week — not after your next dependency update.
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.