A new second stage of the GhostAction campaign uses a fake .woff2 font file to make VS Code run a credential stealer. It also plants a cron job that keeps working after the malicious GitHub workflow is deleted.
Researchers at OpenSourceMalware tied the activity to 94 compromised repositories. They treat it as a copycat of North Korea’s PolinRider technique, not an extension of it.
Jamf Threat Labs researcher Thijs Xhaflaire found the cluster while hunting for new PolinRider victims. The launcher matched PolinRider’s: a hidden .vscode/tasks.json that runs on folder open, pointing at public/fonts/fa-solid-400.woff2.
GhostAction Copies PolinRider
The payload was different. It was 38 lines of plain, readable Node.js, and it called a hardcoded IP address that matched GhostAction’s server. OpenSourceMalware found no indicators shared with any DPRK malware, so it labels this a copy.
Stage 1 pushed a fake “security audit” workflow to every repository an account could write to, and it sent secrets and git-history credentials to the attacker.
Stage 2 adds three files and one scheduled job. The task is labeled eslint-check and is hidden from view. Node runs the “font” because it ignores file extensions.
The script reads eight credential files, including AWS, PyPI, npm, Git, Kubernetes, Claude, and Gemini credentials. It takes SSH private keys in full and greps the home directory for AWS, Anthropic, GitHub, npm, and PyPI tokens. It then posts everything over plain HTTP to port 8080.
The author made mistakes. The environment-variable filter is anchored so that most API-key variables slip past. The grep has a 10-second timeout and discards its results if it expires. Neither flaw affects direct reads of credential files and SSH keys.
The bigger danger is the cron entry, tagged #ghc2. Every five minutes it fetches a script from the server, keyed by hostname, and pipes it to bash.
Researchers haven’t retrieved what the server returns, so Stage 3 is unknown. Deleting the font file doesn’t remove the cron job. A second workflow, with seven different filenames, serves as a fallback and can upload findings to a secret gist.
Stage 1 hit 72 of the 79 analyzed repositories on October 8. The main Stage 2 burst landed on October 15 between 01:00 and 10:31 UTC. The median gap between stages was 6.3 days, but it tracked a date rather than a delay. Someone chose October 15.
Opensourcemalware stated that the 94 repositories belong to 66 owners, and 79 are cloneable. Most were dormant: 65 of the 79 had gone more than a year without a commit. The automation pushes to everything an account can write to, so forgotten projects get infected too.
Two owners removed the Stage 1 workflow on October 9 and were hit again on October 15. Deleting a workflow doesn’t revoke the token that pushed it.
One repository, bagisto-dhs, also carried a genuine PolinRider loader hidden in Tailwind config files. Researchers found no link between the two compromises.
On October 9, GitHub began blocking the malicious workflow from running, and later waves extracted no secrets. Stage 2 needs no runner because it runs on the developer’s machine.
Researchers can’t prove the block caused the shift. Meanwhile, a phishing kit that steals sessions and OAuth tokens suggests the operator is refreshing access in parallel.
Revoke access first, from a clean device. That covers sessions, personal access tokens, OAuth grants, GitHub Apps, SSH keys, and deploy keys. Then remove the workflows, tasks.json, and fake font from every branch and fork.
Anyone who opened an affected repo in VS Code and allowed automatic tasks should assume compromise. Run crontab -l on macOS and Linux and remove any line ending in #ghc2 or naming the server IP.
Consider rebuilding the machine, then rotate SSH, cloud, registry, GitHub, kubeconfig, and AI-tool credentials. Keep task.allowAutomaticTasks to off, require approval for workflow runs, and protect .github/workflows with required reviews.
Site: Thecyberdef.com
Follow TheCyberDef on Google News, LinkedIn & X for the latest cybersecurity updates. Stay informed.