DEV Community

Edison Flores
Edison Flores

Posted on

I built 8 security layers for an MCP marketplace. Here's what each one actually catches.

After a real trojan slipped through my MCP marketplace last week (Trojan:Win64/Lazy.PGPK!MTB hidden in a nested zip), I went deep on defense-in-depth. The result is 8 layers running in production at marketnow.site.

Here's what each layer actually catches — with concrete examples.

Layer 1: L1.5 — Metadata checks (6 rules)

The cheapest layer. Runs on every skill's metadata (name, description, system_prompt, install command).

Catches:

  • Skills that say "no auth required" (warning)
  • Skills with prompt injection patterns in descriptions ("ignore previous instructions")
  • Skills with file/SQL/HTTP access declared in metadata
  • Skills with Access-Control-Allow-Origin: *
  • Skills using unscoped OAuth tokens
  • Skills with no rate limiting

Doesn't catch: anything inside the actual package zip. That's why the trojan got through initially.

Layer 2: L1.6 — Semgrep + Secrets + OSV (36 rules)

18 Semgrep-equivalent rules + 18 secret detection patterns + OSV dependency vulnerability check.

Catches:

  • Hardcoded API keys (Stripe sk_live_*, GitHub ghp_*, AWS AKIA*)
  • Wallet mnemonics in descriptions
  • Command injection patterns (exec(req.body))
  • SSRF patterns (fetch(req.url))
  • Path traversal (readFile(req.params.path))
  • Tool name spoofing (a tool calling itself read_file to impersonate the official one)
  • Known vulnerable npm dependencies (via OSV API)

Doesn't catch: secrets inside code blocks in README (we strip those — false positives), process.env.X references (variable lookups, not hardcoded).

Layer 3: L1.7 — Binary & malware detection (8 patterns)

This is the layer I built after the trojan incident. It opens the actual package zip (recursively — zips inside zips) and scans for:

  • Windows binaries (.exe, .dll, .scr, .msi) → instant quarantine
  • Launcher scripts (.bat, .cmd, .vbs, .ps1) → instant quarantine
  • Nested archives (zips inside zips — legit MCP skills don't do this)
  • Staged launchers (start X.exe Y.txt pattern — the exact prospector trojan signature)
  • Obfuscated Lua bytecode (high-arity function signature function(o,R,F,U,b,p,E,M,Z,W,...))
  • External download URLs in README (raw.githubusercontent.com/.../...zip)
  • PowerShell -encodedcommand with long base64
  • eval(atob(...)) obfuscation
  • Oversized text files (>100KB non-JSON = likely bytecode payload)

Catches: the exact trojan that hit us. Verified with a smoke test that scans the original malicious zip from git history.

Layer 4: L1.8 — Malware family signatures (17 families)

YARA-equivalent rules for specific malware families:

  • Win64/Lazy.PGPK (the one that hit us)
  • Emotet (banking trojan)
  • Cobalt Strike (post-exploitation beacon)
  • Mimikatz (credential dumper)
  • QakBot, TrickBot (banking trojans)
  • Agent Tesla (keylogger)
  • RedLine, Vidar, Raccoon, LummaC2 (stealers)
  • AsyncRAT, njRAT, Remcos (RATs)
  • SolarMarker (backdoor)
  • Lokibot (credential stealer)
  • DoS tools (hping3, slowloris, goldeneye)

Each rule has a MITRE ATT&CK technique ID. Any match → instant quarantine.

Layer 5: WAF — Web Application Firewall (40 rules)

Inspects every incoming HTTP request for attack patterns:

  • SQLi (7 rules): UNION SELECT, OR 1=1, stacked queries, time-based, information_schema
  • XSS (7 rules): script tags, event handlers, javascript: URIs, img onerror, svg onload
  • Path traversal (5 rules): ../, encoded %2e%2e, /etc/passwd, /proc/self, Windows paths
  • SSRF (6 rules): AWS/GCP/Azure metadata IPs, file://, gopher://, dict://
  • Command injection (5 rules): backticks, $(), chained ; ls, pipe | cat, && ||
  • NoSQL injection (3 rules): $where, $ne, $gt
  • Prototype pollution: __proto__, constructor.prototype
  • SSTI: Jinja2 {{ }}, Twig {% %}, JS ${ }
  • Log injection: with header injection

Auto-ban after 5 WAF hits in 10 minutes (1-hour ban).

Layer 6: Honeypot (50+ paths)

Fake vulnerable paths that auto-ban scanners for 24 hours:

  • /.env → serves a fake env file with canary tokens
  • /admin → serves a fake admin login form
  • /wp-admin → serves a fake WordPress login
  • /.git/config → serves a fake git config
  • /.aws/credentials → serves fake AWS credentials
  • /.ssh/id_rsa → serves a fake SSH key
  • /phpmyadmin → serves a fake phpMyAdmin
  • /backup.sql → serves a fake database dump
  • /server-status, /.DS_Store, /web.config, /Dockerfile, etc.

Any access → IP banned 24h + logged publicly at /api/security?view=honeypot.

Layer 7: Threat Intelligence (3 feeds)

Real-time IOC feeds from abuse.ch:

  • URLhaus — last 1000 malicious URLs (5-min cache)
  • MalwareBazaar — last 100 malware sample hashes
  • ThreatFox — IOCs from active malware campaigns (7-day window)

Used to check skill source URLs and file hashes. If a skill's source URL is in URLhaus, it gets quarantined.

Layer 8: Auto-Quarantine

If any layer flags a skill as critical/high:

  1. Skill certificate moves to _data/quarantine/
  2. Skill removed from public catalog (skills_index.json)
  3. Listed publicly at /api/security?view=quarantine for transparency
  4. Pre-import scan in the auto-discovery pipeline blocks it before entering the catalog

What actually happened when I ran all 8 layers

Re-audited all 14,581 skills with the new layers.

Result: 0 skills in quarantine.

The catalog was clean — the only malicious skill (prospector-email-finder) had already been removed manually. The 8 layers now run on every new skill import and every weekly batch re-audit.

The stack

  • Vercel Hobby (free tier, 11 serverless functions)
  • GitHub Actions for batch audits (120-min timeout, 16GB RAM)
  • Docker + gVisor for L2 sandbox (--network none, --read-only, --cap-drop ALL)
  • Base (L2) for USDC payment verification
  • ethers.js for EIP-191 signature verification
  • abuse.ch for threat intel feeds (free, no API key)

Total infrastructure cost: $0/month. The marketplace is free. The security infrastructure is the product.

Try it

Not selling anything. Looking for feedback from people who run MCP servers in production — what would make you trust a marketplace enough to install skills from it?

Edison Flores, AliceLabs LLC

Top comments (2)

Collapse
 
nazar-boyko profile image
Nazar Boyko

Naming the specific failure class per layer is what makes this readable instead of one big "we scan for malware" claim, so that part really works. The honest tension I'd sit with is that layers 3 and 4 are pattern and family matching, which is exactly the shape of defense that missed the first trojan until you'd already seen it. The next one won't match a known family or the prospector signature, so those layers start clean-again by definition. That's not a knock, signatures catch the cheap 90%, but it does mean the "0 in quarantine" result is partly "nothing matched what we know," not "nothing bad is present." Do any of the layers flag on behavior or capability rather than known-bad strings, so an unknown payload still trips something? That's the gap I'd worry about with untrusted skills.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.