I caught a trojan in my MCP marketplace. Here's the 8-layer defense I built. A developer discovered a Windows trojan, Trojan:Win64/Lazy.PGPK!MTB, hidden in a typosquatting MCP server package on their marketplace. In response, they built an 8-layer defense pipeline that includes recursive zip scanning, YARA rules for 17 malware families, and HTTP request inspection to prevent future incidents. Two weeks ago, a Windows trojan slipped into my MCP Model Context Protocol marketplace. The malware was Trojan:Win64/Lazy.PGPK MTB , hidden inside a nested zip in a skill package. This is the technical writeup of what happened, what I built to prevent it, and the architecture of the 8-layer defense pipeline now running in production. Vector: Typosquatting GitHub repository A legitimate MCP server prospector-mcp-email-finder existed. An attacker created a typosquatting copy at JuanquiFortuny/prospector-mcp-email-finder now taken down with an identical README — but with a "Download Latest Release" badge linking to a malicious zip on raw.githubusercontent.com . The payload: Application.cmd → 'start unit.exe package.txt' package.txt → 298 KB of obfuscated Lua bytecode unit.exe → 872 KB PE32+ Windows executable Windows Defender flagged unit.exe as Trojan:Win64/Lazy.PGPK MTB . The Application.cmd → unit.exe + package.txt pattern is a classic staged launcher — small CMD wrapper executes the binary with the bytecode payload as input. How it got in: My import script which fetches MCP servers from GitHub downloaded the zip and committed it to dist/skills/ without scanning inside it. My existing audit pipeline Sentinel L1.5 + L1.6 only checked skill metadata — name, description, system prompt. It never looked inside the actual package. Cheapest layer. Runs on every skill's metadata: ignore previous instructions Access-Control-Allow-Origin: warning 18 Semgrep-equivalent rules implemented as JS regex no Semgrep binary needed : 18 secret detection patterns: sk live , sk test ghp , gho , ghs AKIA , aws secret access key OSV API for known vulnerable dependencies. Bug fix from peer review : process.env.X references were triggering MCP-SL-001 hardcoded API key . Fixed by stripping process.env. patterns before running secret detection. This is the layer I built after the trojan incident. It opens the package zip recursively — zips inside zips and scans for: js const BINARY EXTENSIONS = '.exe', '.dll', '.scr', '.msi' ; const LAUNCHER EXTENSIONS = '.bat', '.cmd', '.vbs', '.ps1' ; Plus 8 regex patterns for: start X.exe Y.txt the exact prospector signature function o,R,F,U,b,p,E,M,Z,W,... high-arity function signature raw.githubusercontent.com/.../...zip -encodedcommand with long base64 eval atob ... obfuscation Quarantine flow: Any critical finding → score 0, risk level critical , status quarantined , removed from catalog, listed at /api/security?view=quarantine . YARA-equivalent rules for specific malware families, each with MITRE ATT&CK technique IDs: | Family | MITRE | What it does | |---|---|---| | Win64/Lazy.PGPK | T1027.002 | The trojan that hit us | | Emotet | T1027.011 | Banking trojan, Office macro delivery | | Cobalt Strike | T1071.001 | Post-exploitation beacon | | Mimikatz | T1003.001 | LSASS credential dumper | | QakBot | T1027 | Banking trojan | | TrickBot | T1027 | Modular trojan, ransomware precursor | | Agent Tesla | T1056.001 | Keylogger | | RedLine Stealer | T1555 | Browser credential theft | | Vidar Stealer | T1555 | Browser + crypto wallet theft | | Raccoon Stealer | T1555 | MaaS stealer | | LummaC2 Stealer | T1555 | Telegram-sold stealer | | AsyncRAT | T1071.001 | Open-source RAT | | njRAT | T1071.001 | .NET RAT | | Remcos RAT | T1071.001 | Commercial RAT | | SolarMarker | T1027 | SEO-poisoned backdoor | | Lokibot | T1555 | Credential stealer | | DoS tools | T1499 | hping3, slowloris, goldeneye | Any match → instant quarantine. Inspects every incoming HTTP request: SQLi 7 : UNION SELECT, OR 1=1, stacked, time-based, info schema, hex XSS 7 : script, event handlers, javascript:, data:, vbscript:, img/svg Path traversal 5 : ../, encoded, /etc/passwd, /proc/self, Windows SSRF 6 : AWS/GCP/Azure metadata, file://, gopher://, dict:// Command injection 5 : backticks, $ , chained, pipe, && || NoSQL 3 : $where, $ne, $gt Prototype pollution: proto , constructor.prototype SSTI: Jinja2 {{ }}, Twig {% %}, JS ${ } Log injection: \n \r header injection Auto-ban after 5 WAF hits in 10 minutes 1-hour ban . Fake vulnerable paths that auto-ban scanners for 24 hours: /.env → serves fake env file with canary tokens /admin → fake admin login form /wp-admin → fake WordPress login /.git/config → fake git config /.aws/credentials → fake AWS credentials /.ssh/id rsa → fake SSH key /phpmyadmin , /backup.sql , /server-status , /.DS Store , etc.Each hit: IP banned 24h + logged publicly at /api/security?view=honeypot . Real-time IOC feeds from abuse.ch free, no API key : Used to check skill source URLs and file hashes. If any layer flags a skill as critical/high: data/quarantine/ /api/security?view=quarantine for transparencyRe-audited all 7,063 skills with the 8 layers. 0 in quarantine. The catalog was clean except for the one we already removed. Request → Honeypot check → WAF 40 rules → Handler ↓ Skill import → L1.5 6 → L1.6 36 → L1.7 8 → L1.8 17 → Quarantine? ↓ no Catalog public ↓ yes data/quarantine/ publicly listed Stack: Vercel Hobby $0/mo , GitHub Actions free , Docker + gVisor for L2 sandbox, abuse.ch feeds. — Edison Flores, AliceLabs LLC