In September 2025, a phishing email landed in the inbox of Josh Junon, the maintainer of chalk, debug, ansi-styles, and a handful of other npm packages that together see more than 2 billion downloads a week. The email spoofed npm's support domain and asked him to "re-verify" his account before a fake two-factor deadline. He clicked, entered his credentials, and within hours attackers had push access to some of the most depended-upon packages in the JavaScript ecosystem. They shipped versions laced with a crypto-clipper that silently swapped wallet addresses in transit. No firewall was breached. No zero-day was exploited. A person with legitimate publish rights was tricked into handing them over — and every project that ran npm install inherited the consequences. This is how most package takeovers actually happen, and it's worth walking through the mechanics.
What actually happens when an attacker phishes a maintainer's credentials?
The attacker sends a convincing, targeted email impersonating the registry itself, and the maintainer logs into a fake site that captures their username and password. In the September 2025 npm incident, the phishing domain was npmjs.help — a lookalike registered just for the campaign, styled to match npm's real login page pixel-for-pixel. Because the maintainer entered credentials directly into the fake site, the attacker didn't need to guess a password or break encryption; they simply received it in plaintext. From there they logged into the real npmjs.com, reset two-factor settings where possible, and pushed new versions of chalk@5.6.1, debug@4.4.2, and roughly 18 other packages within the same dependency cluster — all within about two hours of the phishing email being sent. Static analysis firms flagged the payload (a browser-based clipboard hijacker targeting Ethereum and Bitcoin addresses) before most CI pipelines had even finished pulling the new versions, but by then millions of installs had already occurred.
Why do stolen or reused credentials still work without multi-factor authentication?
Because a shocking number of maintainer accounts, even on high-download packages, never had MFA enabled until registries started forcing it. In October 2021, the maintainer of ua-parser-js — a browser-detection library pulled roughly 8 million times a week — had their npm password compromised, most likely through credential reuse from an unrelated third-party breach rather than a phishing attack on npm directly. The attacker logged in, published three malicious versions containing a Monero cryptominer and a Windows password stealer, and moved on before anyone noticed. npm's security team pulled the versions within about four hours, but that window was long enough for the package to be downloaded thousands of times and for at least one downstream incident (a compromised CI runner) to be reported. A single reused password from a completely unrelated site was the entire attack surface. Weeks later, in November 2021, the same pattern repeated with coa and rc, two more high-traffic npm packages hit by what researchers believe was the same credential-stuffing operation.
How does an attacker engineer a legitimate handoff instead of stealing anything?
Sometimes there's no theft at all — the attacker is voluntarily given the keys because they spent months or years building trust as a "helpful" contributor. The clearest example is the XZ Utils backdoor, tracked as CVE-2024-3094. Starting around 2021, a contributor using the name "Jia Tan" began submitting legitimate patches to the xz/liblzma compression library, gradually taking on more maintenance responsibility as the original maintainer, dealing with personal burnout, welcomed the help. By 2023, Jia Tan had commit and release access. In February 2024, they quietly introduced obfuscated build-script changes that injected a backdoor into the liblzma shared library, one used by OpenSSH indirectly through systemd on major Linux distributions. It was discovered almost by accident on March 29, 2024, when Microsoft engineer Andres Freund noticed SSH logins were consuming slightly more CPU than expected and traced it back through the build chain. No password was ever stolen; the "compromise" was two years of patient social engineering aimed at a real human being's trust and workload.
What does the attacker actually do once they have publish access?
They ship a version bump that looks routine but carries a payload triggered at install time, usually through postinstall scripts or obfuscated runtime code rather than an obviously malicious diff. The 2018 event-stream incident set the template: a user named "right9ctrl" asked the original maintainer, Dominic Tarr, for commit access to help maintain the abandoned-feeling package, was granted it, and then added a new dependency called flatmap-stream containing code that specifically activated only inside the Copay bitcoin wallet application, scanning for private keys and exfiltrating them. It went undetected for roughly two and a half months and had been downloaded an estimated 8 million times before a developer noticed the minified, unreadable code in a routine audit. More recent incidents have gone further: the "Shai-Hulud" worm that hit npm in September 2025 didn't just steal from one package — once it landed via a compromised maintainer token, it scanned the infected machine for any other npm credentials it could find and automatically republished itself into every package those credentials could touch, spreading to several hundred packages within days without any additional phishing required.
How long does it typically take the ecosystem to notice, and what's the actual blast radius?
Detection windows range from a few hours to several years, and the blast radius scales directly with how central the package is to the dependency graph. ua-parser-js was caught in about four hours because it's popular enough that security vendors monitor it closely; event-stream took over two months because it was a smaller, less-watched utility buried deep in transitive dependencies. XZ Utils took roughly two years from the first social-engineering contact to backdoor discovery, and even then it was luck — a few milliseconds of extra CPU latency — that surfaced it, not registry monitoring. The math matters more than any individual timeline: chalk and debug alone are transitive dependencies of hundreds of thousands of other packages, meaning a two-hour compromise window in September 2025 still touched an enormous share of the JavaScript build pipelines running that week, whether or not those teams had any idea the packages had been touched.
How Safeguard Helps
Every one of these incidents shares a common failure point: nobody outside the attacker knew a trusted maintainer's publishing behavior had changed until damage was already done. Safeguard is built to close that gap. We continuously monitor your dependency tree for anomalous publish events — a new maintainer added to a package with no prior commit history, a version released outside the maintainer's normal cadence or geography, or a release that introduces install-time scripts where none existed before — and flag them before your CI pulls the update. We diff package contents against source repositories automatically, so a postinstall script or obfuscated payload that doesn't match what's in the public GitHub tag gets caught instead of silently executing in your build environment. And because maintainer takeover often unfolds over hours, not days, Safeguard's alerting is designed to reach your team fast enough to pin a version or block a pull before the compromised release ever reaches production. Package registries can't force every maintainer to adopt hardware-key MFA or reject every well-crafted social-engineering attempt — but your organization doesn't have to depend on that. Safeguard gives you the independent, automated layer of scrutiny that catches what trust-based maintainer relationships, by design, cannot.