How a university group project led me down a rabbit hole of fake .woff2/.ttf/.eot font files, 150-space config injections, Ethereum smart-contract C2s, and Chrome 127+ encryption bypasses—plus the open-source scanner & AI audit prompt I built to help you check your repos.
When you review a pull request or clone a friend's project from GitHub, how closely do you inspect public/fonts/fa-solid-900.woff2?
Probably not at all. It's just a Font Awesome icon file, right?
Wrong.
What started as a routine university group project turned into a full-scale malware investigation spanning 21 infected GitHub repositories from developers around the world, 3 distinct malware families, Ethereum blockchain dead-drop Command & Control ("EtherHiding"), and a Python credential stealer that impersonates lsass.exe (SYSTEM) to bypass Chrome 127+ App-Bound Encryption.
Even worse: once one developer's GitHub credentials are stolen, the malware automatically infects every repository they have push access to—turning trusted teammates into unintentional malware distributors.
Here is the full story of how this campaign works, how it hides in plain sight inside GitHub's web UI, and how you can use the free, open-source Python Account Scanner & Cleaner (and Ready-to-Copy AI Coding Agent Prompt) I built to audit and clean your own repositories safely.
thisal-d
/
fake-font-malware-case-study
Static-analysis case study of a malicious repo that hid JavaScript in fake font files and auto-ran it via a VS Code folderOpen task, plus a related Python stealer sample, IOCs, YARA/Sigma rules, and cleanup steps.
Fake Font Malware: A Case Study & Unified GitHub Account Scanner / Repository Cleaner
Last updated: November 12, 2026 | Status: v2.0, expanded global investigation (19 world repositories + 6 commit-pinned candidates) & unified standalone Python scanner/cleaner
Repository: fake-font-malware-case-study
This repository documents a comprehensive static-analysis case study of a global GitHub supply-chain malware campaign affecting web, mobile, and backend repositories around the world (including university web coursework, a React Native / Expo IoT project smart-water-tank-system, and 19 additional real-world developer repositories). The campaign disguises obfuscated Node.js payloads inside files developers treat as harmless—especially typography font assets (.woff2, .woff, .ttf, .otf, .eot, .orc, and .lkf) and extensionless/decoy scripts (init, api.js, dist/setup.js)—auto-executing them via Visual Studio Code .vscode/tasks.json ("runOn": "folderOpen"), build-tool configuration horizontal whitespace/tab injections (babel.config.js, postcss.config.mjs, postcss.config.js, vite.config.js, tailwind.config.js, eslint.config.mjs…
🚨 Wait—Am I Infected? (Read This Before Opening Any Repo!)
⚠️ CRITICAL IDE WARNING: DO NOT OPEN SUSPICIOUS REPOS IN VS CODE, GOOGLE ANTIGRAVITY, CURSOR, OR WINDSURF!
Do not open unverified or potentially infected repositories directly in VS Code, Google Antigravity, Cursor, Windsurf, or any other graphical / AI-powered IDE before inspecting them from an isolated terminal!
Why?
- Silent Workspace Auto-Execution: Infected repos contain
.vscode/tasks.jsonconfigured with"runOn": "folderOpen"and"reveal": "never". The moment you open and trust the folder,nodesilently executes the malware in the background.- IDE Extensions Auto-Evaluating Configs: Extensions for ESLint, PostCSS, Tailwind CSS, Vite, Babel, and TypeScript automatically load and evaluate
vite.config.js,postcss.config.mjs,babel.config.js, andeslint.config.mjsin the background to provide IntelliSense—triggering injected payloads before you ever runnpm start!- AI Agents Running Workspace Commands: AI-powered IDEs may index files or invoke project scripts automatically during workspace initialization.
Always use an isolated CLI workflow first!
⚡ 60-Second PowerShell Self-Check (Windows)
If you recently cloned a repository from a classmate, colleague, or freelance client—or saw an unexpected Windows UAC administrator popup—run these read-only checks in PowerShell:
# 1. Check %TEMP% for dropped stealer scripts or batch files
Get-ChildItem -Path $env:TEMP -Include "tmp*.py", "config.bat" -File -ErrorAction SilentlyContinue
# 2. Check ~/.npm for hidden Family-B dropper directories
Get-ChildItem -Path "$env:USERPROFILE\.npm" -Filter "scoped_dir*" -Directory -ErrorAction SilentlyContinue
# 3. Check for active connections to the known Python stealer C2
Get-NetTCPConnection -RemoteAddress "91.218.183.174" -ErrorAction SilentlyContinue
🕵️♂️ How I Discovered It: The "Same-Day Timestamp" Clue
My team was collaborating on a standard university project hosted on GitHub. Also, I had an older IoT / React Native project (smart-water-tank-system) where a friend was a collaborator.
One day, I visited my collaborator's GitHub profile and noticed something deeply strange: every single repository on their account had been updated on the exact same day.
When I opened the commits, nothing looked obviously broken at first glance. The attackers had manipulated the repository files so their additions blended into normal project assets.
Then I looked closer—and found four layers of deception.
🎭 The 4 Evasion Tricks Hiding in Plain Sight
Trick #1: The 150-Space "Invisible Line" & GitHub's Symbols Sidebar Blindspot
Open postcss.config.mjs, vite.config.js, babel.config.js, tailwind.config.js, or even regular application files like counter.controller.js in GitHub's web viewer, and the file looks completely normal:
Now scroll horizontally past column 150 (up to column 579 in some files):
The attacker appended 120 to 579 ASCII space characters (0x20) (or dozens of tabs 0x09) at the end of a legitimate line of code, followed by 30 KB to 40 KB of heavily obfuscated JavaScript on that single line!
Even more clever: in GitHub's web UI, if you have the right-hand Symbols sidebar open, the narrower code viewport hides the horizontal injection completely off-screen:
Trick #2: Fake Font Files Staged Alongside Real Fonts
Instead of dropping an obvious malware.js file, the attacker creates a font directory (like public/fonts/ or Manifest Configuration/xml/public/fonts/) containing 14 real, valid binary Font Awesome files—and 1 fake font file containing plain-text obfuscated JavaScript:
When you open fa-solid-900.woff2 or fa-brand-400.orc as text, there are no binary font headers (wOF2, wOFF, OTTO, \x00\x01\x00\x00). It is pure JavaScript:
Across the 21 repositories and 6 candidate samples we analyzed, we found identical or evolved JavaScript payloads disguised inside:
-
.woff2(fa-solid-900.woff2,fa-solid-500.woff2,fa-brands-regular.woff2) -
.woff(fa-brand-400.woff— note the singularfa-brandtypo alongside a realfa-brands-400.woff!) -
.ttf(fa-brands-400.ttf,fa-solid-600.ttf,fa-solid-300.ttf) -
.otf(fa-solid-700.otf) -
.eot(fa-solid-900.eot) -
.svg(fa-brands-400.svg) -
.orc&.lkf(fa-brand-400.orc,fa-solid.lkf) - Decoy app files:
api.js,dist/setup.js, and extensionlessinit
💡 Key Proof from Our Research:
Two files in completely different repositories—fa-brand-400.orcin one repo andfa-solid-600.ttfin another—had the exact same SHA-256 hash (89ADAC5B...). The file extension means nothing tonode—Node.js will happily execute any file you pass to it, even if it ends in.woff2or.ttf!
Trick #3: Silent Execution via .vscode/tasks.json & package.json
How does a .woff2 or .eot file get executed by Node.js? The attacker plants multiple traps:
-
VS Code Auto-Run Tasks (
.vscode/tasks.json):
{
"version": "2.0.0",
"tasks": [
{
"label": "eslint-check",
"type": "shell",
"command": "node ./public/fonts/fa-solid-900.woff2",
"runOptions": { "runOn": "folderOpen" },
"presentation": { "reveal": "never", "echo": false }
}
]
}
-
Hijacked
package.jsonScripts: In my React Native / Expo project (smart-water-tank-system), the attacker modifiedpackage.json:
"scripts": {
"start": "node api.js && expo start"
}
And inside .gitignore, the attacker added config.bat, branch_structure.json, temp_auto_push.bat, and temp_interactive_push.bat so their infection scripts wouldn't show up in git status!
🧬 Reverse-Engineering the Payloads: 3 Malware Families & Blockchain C2
We statically reversed every layer of obfuscation across the global corpus without executing a single line of untrusted code. Here is what the malware actually does:
Compromised GitHub Account Pushes to Repo
│
├─► Trigger 1: .vscode/tasks.json ("runOn": "folderOpen")
├─► Trigger 2: 150-Space Config Injection (vite / postcss / babel / tailwind)
└─► Trigger 3: Hijacked package.json ("node api.js && ...")
│
▼
Stage-1 Obfuscated Node.js Loader (inside .woff2 / .ttf / .eot / .orc / api.js)
│
├─► Family-A ("EtherHiding"): Queries Ethereum Mainnet RPCs for Attacker Wallet
│ ├─ Gen 1 Wallet: 0xa322E5f3D311D3080e6f0121063e9aDC2490Ef1a
│ ├─ Gen 2 Wallet: 0x1eEf0849EDa15d87f64e51C73C8442Cf3c8bCEF3
│ └─ Gen 3 Wallet: 0x026279659f39006684fC45279939D21F7f31280A
│ └─► Extracts C2 IPv4 addresses from tx.to bytes 0..7!
│
└─► Family-B ("js-confuser" Base91 Dropper):
└─► Drops ~/.npm/scoped_dir6760_*/main.js, lowers PowerShell ExecutionPolicy,
and fetches Stage-2 from eth-mainnet-alchemy.com
│
▼
Stage-2 Python Credential Stealer ("Verify-Human" / "BRW" in %TEMP%\tmp*.py)
│
├─► Windows: UAC Elevation Loop -> Impersonates lsass.exe (SYSTEM)
│ Bypasses Chrome 127+ / Brave / Edge App-Bound Encryption!
├─► macOS: Dumps Keychain Git credentials (/usr/bin/security)
├─► Linux: Queries D-Bus Secret Service / KWallet
└─► Exfiltrates master keys & tokens -> Self-deletes (%TEMP%\tmp*.py)
1. Why Take Down a C2 Server When You Can Hide It on Ethereum? ("EtherHiding")
In Family-A, the Stage-1 JavaScript loader doesn't hardcode a C2 IP address that defenders can block. Instead, it queries up to 27 public Ethereum Mainnet RPC endpoints (1rpc.io/eth, eth.drpc.org, ethereum-rpc.publicnode.com, etc.) to fetch the latest transaction from the attacker's wallet (0xa322...Ef1a, 0x1eEf...CEF3, or 0x0262...280A).
It then takes the first 8 bytes of the 20-byte Ethereum recipient address (tx.to) and converts them directly into two IPv4 C2 server addresses (ip1 and ip2)! Whenever a C2 server gets taken down, the attacker simply sends a tiny Ethereum transaction to update the IP address globally for all infected repositories.
2. Bypassing Chrome 127+ App-Bound Encryption via lsass.exe
The final stage (Verify-Human / BRW Python stealer) writes itself to %TEMP%\tmpXXXXXXXX.py and loops a Windows UAC prompt (ShellExecuteExW with runas) until the developer clicks "Yes".
Once elevated:
- It enables
SeDebugPrivilege. - Finds
lsass.exeand duplicates its primaryNT AUTHORITY\SYSTEMtoken. - Uses
SYSTEMDPAPI + Windows CNG Key Storage Provider (Google Chromekey1,Brave Softwarekey1,Microsoft Edgekey1) to decrypt Chrome 127+ App-Bound Encryption master keys. - Sweeps Git credentials, macOS Keychains, and Linux Secret Service stores, posts everything to
91.218.183.174/verify-human/BRW, and deletes itself from disk (os.remove(sys.argv[0])).
🛠️ Free Open-Source Tool: Unified GitHub Account Scanner & Cleaner (v2.0.0)
Manually checking dozens of repositories and branches for 150-space horizontal injections and fake font headers is nearly impossible.
So I built github_malware_scanner_cleaner.py—a single, standalone, zero-dependency Python 3.8+ tool with a full color-coded terminal UI that anyone can run.
✨ What It Does:
-
Extension-Independent 5-Layer Detection:
- Validates real binary font headers & table offsets (
.ttf,.otf,.woff,.woff2,.eot). If a font file contains plain-text JavaScript or trailing polyglot bytes, it catches it immediately—while preserving 100% of your legitimate binary fonts. - Detects all 3 Malware Families and 9 Obfuscation Generations (including horizontal space/tab padding, ESM
createRequireshims,.vscode/tasks.jsonfolderOpenhooks,package.jsonnode api.js &&chains, and.gitignoretampering). - Safely inspects
.zipand.tar.gzarchives in memory with zip-bomb protection.
- Validates real binary font headers & table offsets (
-
100% Trustworthy Private Repository Support (Never Asks for Your Password or Token!):
- A security script should never ask you to type or paste your GitHub password or Personal Access Token into a terminal prompt.
- Our script lists your public repositories automatically via GitHub's public API, explains why private repos aren't listed by the public API, and lets you type your private repository names separated by commas (
repo1, repo2) so your computer's official Git / Git Credential Manager clones and cleans them directly!
-
Safe Surgical Cleanup + External Quarantine Vault (
--restore):- Defaults to read-only / dry-run mode until you confirm.
- Backs up every infected file to an external Quarantine Vault (
manifest.json) before deleting standalone fake fonts or surgically stripping malicious tails fromvite.config.js/postcss.config.mjs/babel.config.js/package.jsonwithout breaking your app! - Supports both Mode A (Safe Normal Commit & Push) and Mode B (Complete Git History Rewrite with mandatory
--mirrorbackup).
🚀 Quick Start (Run It in 30 Seconds)
# Clone the case study & tool safely
git clone https://github.com/thisal-d/fake-font-malware-case-study.git
cd fake-font-malware-case-study
# Launch the interactive color-coded GitHub Account Scanner & Cleaner
python tools/github_malware_scanner_cleaner.py
Or scan a local repository folder in read-only mode:
python tools/github_malware_scanner_cleaner.py --local-repo /path/to/repo --scan-only --all-branches
🤖 Defense-in-Depth: Audit Your Account with an Independent AI Coding Agent
Because malware authors constantly evolve their obfuscation and delivery tricks, no single Python script can guarantee that a repository is 100% clean.
To get a much higher level of confidence, I also created a complete, ready-to-copy 15-step prompt that you can paste into an independent CLI-based AI coding agent (like Claude Code, OpenAI Codex CLI, or another terminal agent):
👉 Copy the AI Agent Security Audit Prompt Here (docs/AI_AGENT_SECURITY_AUDIT_PROMPT.md)
When you paste that prompt into your CLI agent (inside an empty working folder), the agent will:
- Ask for your GitHub username and verify safe
ghCLI login (never asking for tokens or passwords in chat). - Clone and study
https://github.com/thisal-d/fake-font-malware-case-studyfirst as its mandatory malware intelligence knowledge base. - Discover all your public and private repositories with pagination.
- Clone and inspect repositories one at a time across all branches in an isolated CLI workflow (never executing code or running
npm install). - Run our unified Python scanner plus its own independent deep AI code audit to catch any mutated variants, show you the diffs, ask for your approval, and push clean commits safely.
(Remember: even an independent AI audit cannot guarantee 100% immunity—combining deterministic static scanning + AI semantic auditing + your own review of git diff gives you layered defense-in-depth!)
🛑 Crucial: Cleaning Your GitHub Repo Does NOT Clean Your PC!
If you or your friend ever opened an infected repository in VS Code or ran npm install / npm start on it, cleaning the GitHub repository is only half the battle. You must also recover your workstation and accounts:
-
💿 Reinstall Windows Cleanly (Bootable USB Format):
Because this malware spawns detached background processes, drops files into
%TEMP%and~/.npm/scoped_dir*, lowers PowerShell ExecutionPolicy, and impersonateslsass.exe(NT AUTHORITY\SYSTEM), do not rely on antivirus scans alone. Back up only plain personal documents and perform a clean Windows reinstallation from a USB created on a clean computer. - 🔒 Install Only Trustworthy Software & Extensions: On your fresh OS installation, only install verified, official applications and trusted IDE extensions. Never open unverified repositories in VS Code, Google Antigravity, or Cursor before scanning them via CLI.
-
🔑 Change All Passwords & Revoke Sessions (From a Clean Phone or PC First!):
Because the stealer bypasses Chrome/Edge/Brave App-Bound Encryption to steal saved browser passwords and session cookies:
- Log out / revoke all active sessions on GitHub (
Settings -> Sessions -> Revoke all), Google/Gmail, Microsoft, cloud providers, social media, and banking. - Change all passwords to strong, unique passwords and enable 2FA.
-
Delete all old GitHub Personal Access Tokens (PATs), SSH keys, and unfamiliar OAuth apps (
Settings -> Applications), then re-authenticategit/ghcleanly after reinstalling Windows. -
Rotate all project secrets (
.envfiles, database URLs, AWS/cloud keys, npm tokens) that were ever stored in the infected repos.
- Log out / revoke all active sessions on GitHub (
📚 Full Case Study, IOCs & Resources
Everything we uncovered—including deobfuscated Stage-1 JavaScript loaders, deobfuscated Python stealer code, YARA/Sigma rules, SHA-256 hashes, video demonstrations, and the 19-repository global forensic report—is open-sourced in our repository:
- 📦 Main Repository & Case Study: thisal-d/fake-font-malware-case-study
- 🛠️ Unified Python Scanner & Cleaner (
v2.0.0):tools/github_malware_scanner_cleaner.py - 🤖 Ready-to-Copy AI Coding Agent Audit Prompt:
docs/AI_AGENT_SECURITY_AUDIT_PROMPT.md - 🌍 19-Repository Global Campaign & 6 Candidate Forensic Report:
analysis/global-campaign-19-repos-and-candidates.md - 📖 Complete CLI & Interactive Usage Guide:
docs/USAGE_GUIDE.md - 🧪 Automated Validation Report (
10/10 PASS):docs/TESTING_AND_VALIDATION.md - ⚠️ Security Limitations & Host Remediation Checklist:
docs/SECURITY_LIMITATIONS.md - 🛡️ Machine-Readable IOCs (SHA-256, Wallets, C2s):
iocs/ioc.json
If this post helped you or saved your team from a compromised repo, please star the repository on GitHub and share this with your fellow developers and university project groups! Stay safe out there. 🛡️





Top comments (0)