Yesterday We Shipped a Malicious-Package Blocker. Today a Real npm Compromise Dropped. Here's the Receipt.
- Patrick Duggan
- 1 hour ago
- 4 min read
Yesterday afternoon we shipped a free tool called check-package: a single call your AI coding agent can make to ask us whether a dependency is malicious before it runs the install. One URL, no key, backed by an OSV-curated deny-list of a quarter-million named-bad packages. We said the whole point was to collapse the friction between "a supply-chain attack exists" and "your agent is protected from it."
Then, roughly a day later, the world handed us a test.
What dropped
On July 11, the official jscrambler npm package — the command-line client for a well-known JavaScript protection product, around sixty thousand downloads a month — was compromised. Not a lookalike, not a typosquat. The real package, trojanized at the source through an account or CI compromise, across five published versions: 8.14.0, 8.16.0, 8.17.0, 8.18.0, and 8.20.0.
The payload was not subtle. Every bad version dropped a 7.8-megabyte binary container carrying a Rust-compiled infostealer built for Linux, Windows, and macOS. It went after browser credentials, Bitwarden vault data, and Steam sessions, and it dug in with a Windows scheduled task and a macOS LaunchAgent. The attacker even evolved the technique in real time as defenders reacted — early versions used a preinstall hook, later ones moved the injection to require-time to slip past the standard ignore-scripts defense.
Credit where it belongs: Socket's scanners flagged the compromised package about six minutes after it was published, and their write-up is the source of the indicators below. The maintainers fought back and reclaimed the latest tag with clean releases at 8.15.0 and 8.22.0.
What we did with it
This is the part that matters to us, because it is the thing we said the tool was for.
We pulled the indicators, and this morning we ingested them into our feed: the five malicious versions as a version-scoped deny-list entry, plus the six file hashes — the two malicious JavaScript files, the poisoned package manifest, and the three native stealer payloads. High confidence, named, sourced. No command-and-control domains were disclosed in the analysis, so we did not invent any; the exfiltration rides over TLS to an undisclosed endpoint.
Then we asked our own tool the question a developer's agent would ask.
Check-package on jscrambler version 8.14.0 comes back malicious true, verdict block. Check-package on the clean 8.22.0 comes back malicious false, verdict review — because it is version-aware: it knows the package name was compromised but this specific version was not, so it does not block the fix, it just tells you to confirm your version. That distinction is the difference between a guardrail and a blunt instrument.
So the loop that we described yesterday as a design goal was, this morning, a measured fact: a real, named, in-the-wild supply-chain compromise went from public disclosure to blocked-by-our-tool in under twenty-four hours, with no key and no signup on the consuming side. Anyone whose agent calls check-package before installing is now protected against this specific attack, and so is anyone consuming the same deny-list as a CI build gate.
The indicators, plainly
For anyone hunting their own environment, the malicious jscrambler npm versions are 8.14.0, 8.16.0, 8.17.0, 8.18.0, and 8.20.0. The clean, safe versions are 8.15.0 and 8.22.0 — pin to 8.22.0 and audit any machine that installed one of the bad ones.
The SHA-256 hashes: the preinstall script dist/setup.js is a742de963f14a92d24ebcbc7b44ac867e23a20d31d1b0094a13a4f83287f4e60; the payload container dist/intro.js is a41a523ef9517aab37ed6eea0ec881821bdcb7aefcb5c5f603adc7907f868c86; the poisoned package.json is bba32ddeab075a5e5015eec50f5d2af364c95b848732c714aea6b6baf78f49f0; the Linux ELF stealer is fbbcf4d8f98168f78f5c0c47a9ae56d59ec8ac84a7c9ca6b797fedfb8d62d2bd; the Windows PE is b7ca95d1b23c8e67416a25cedf741de0917c2096bbc9d24649eea7853d054903; and the macOS Mach-O is c8fd47d36bdf7c825378593ab82ed8c24d1dc52e26b507812393e24e1d5201fd.
All six now live in our STIX feed, and the package entry now answers to check-package.
The honest part
Held to about ninety-five percent, as always. We did not detect this — Socket did, and they did it in six minutes, which is genuinely fast and worth saying out loud. Our contribution is narrower and specific: we take an intelligence signal like this one, turn it into a machine-actionable verdict, and make that verdict free to consume at the exact moment an agent is about to make the mistake. The deny-list is large and curated, but it is not the entire universe of bad packages, so this is a strong layer and not a force field. Absence from the list is not proof of safety; pin and review your dependencies regardless.
But the shape of the day is the argument. Yesterday the guardrail was a claim. Today a real infostealer rode a trusted package into sixty thousand downloads a month, and the same guardrail — free, one URL, running since yesterday afternoon — now stops it cold and knows not to punish the fix. The attackers understand that your AI agent is the way in. The distance between their next move and your protection is the number that matters, and this week that number was under a day.
The tool is at the base of our API, path /api/v1/mcp. Point your agent at it and tell it to check before it installs. It costs nothing, and jscrambler is the reason we built it.
Every indicator in this post is in the feed. Free.
1.58M+ IOCs, STIX 2.1 / TAXII, 88% novel vs ThreatFox, exploited-CVE leads ahead of CISA. No credit card — a free API key in 30 seconds, and you can audit every claim above against the live endpoints.
