eslint-scope npm Publish Token Theft (2018)
An attacker stole an ESLint maintainer's npm credentials and published a malicious eslint-scope version that exfiltrated developer .npmrc tokens to a remote server.
Summary
On July 12, 2018, an attacker stole the npm publish credentials of an ESLint maintainer and published malicious versions of eslint-scope and eslint-config-eslint. The malicious code read the .npmrc file on the developer's machine, extracted the npm authentication token, and POSTed it to an attacker-controlled pastebin endpoint. The attack was a credential-harvesting worm: every developer who installed the package became a potential vector for further publish-token theft. ESLint detected the attack within hours and revoked the malicious versions, but not before an unknown number of authentication tokens were exfiltrated.
Timeline
- 2018-07-12 ~02:49 UTC — Attacker logs into the compromised npm account and publishes
eslint-scope@3.7.2with malicious code. - 2018-07-12 ~03:24 UTC — Attacker also publishes
eslint-config-eslint@5.0.2carrying similar payload. - 2018-07-12 ~05:39 UTC — npm reports anomalous activity to ESLint; ESLint begins investigating.
- 2018-07-12 ~07:00 UTC — Both malicious packages are unpublished. npm begins force-rotating credentials of any account that downloaded the affected versions.
- 2018-07-13 — ESLint and npm publish post-mortems.
Attack Vector
The compromise was credential theft, not a maintainer handoff. The maintainer's npm credentials were obtained, almost certainly via password reuse — npm later confirmed the credentials matched a historical breach corpus. The attack chain:
- Attacker logs into a maintainer's npm account using stolen reused credentials.
- Attacker publishes new patch versions of widely-installed packages with credential-harvesting payload appended.
- Payload reads
~/.npmrcon the install target's machine, extracts the_authTokenline. - Payload POSTs the token to a pastebin endpoint controlled by the attacker.
- Attacker harvests pastebin contents, gains npm publish rights to whatever those tokens authorize.
The intended outcome was self-propagation. Each token harvested let the attacker publish malicious patch versions of further packages, harvesting more tokens, expanding the surface.
Tokens & Credentials Exposed
- npm authentication tokens stored in
~/.npmrcon every machine that installedeslint-scope@3.7.2oreslint-config-eslint@5.0.2during the active window. - For developers who used the same machine for multiple npm scopes, the harvested token authorized publishing across all of them.
- For organizations whose CI pipelines installed the malicious version into a runner that also held publish credentials, the runner's token was harvested.
The exposure window was ~5 hours, which sounds short but corresponds to billions of dependency-tree resolutions globally — every CI build kicked off in that window potentially pulled the malicious version.
Confirmed Impact
- npm forced credential rotation for every account whose
.npmrcwas potentially exfiltrated. The exact number was not published. - No second-stage publishes were observed by the defender community before the malicious versions were unpublished. The window was short enough that the worm did not propagate visibly.
- Long-tail risk persisted for any token harvested but not rotated. npm's forced rotation covered tokens used by accounts that downloaded the package, but tokens stored in long-lived CI environments may have been missed.
Mitigation & Lessons
The structural lessons:
- Multi-factor authentication for publish rights is non-negotiable. npm rolled out 2FA enforcement for top-1000 packages in the months after this incident; mandatory 2FA for all public package maintainers landed years later.
- Tokens in
.npmrcare platform-scoped credentials. A token in~/.npmrcis identity-equivalent to a logged-in npm session for that account. Treat it accordingly: avoid storing publish-scoped tokens on developer laptops at all. - Granular publish scopes matter. Fine-grained tokens that can publish only to a specific package or scope dramatically reduce blast radius if harvested. They were not available in 2018; they are now.
- Provenance + 2FA + lockfile pinning is the defense-in-depth posture that would have stopped this attack at multiple layers today.
Cremit Analysis
This is the cleanest ghost-key case in the index. The credential involved had no human face — it was a long-lived static token in a config file, used routinely by automation. When the attacker presented it, npm had no way to distinguish "the real maintainer publishing a routine patch" from "an attacker holding a stolen token." Both look identical on the wire.
It is also a drifted-key case. The maintainer's npm credentials were almost certainly minted years before, used across multiple personal and work scopes, and reused as a password somewhere else along the way. By 2018 the credential reached more packages, with more downloads, than it did when first issued. Nobody had reviewed the scope.
The NHI Severity Index score of 7.6 reflects ecosystem-wide reach of eslint-scope (a common transitive dependency in JavaScript projects) tempered by the developer-environment reachability — the harvest target was developer laptops and CI runners, not production credentials directly. The blast radius is what made the attack worth attempting: a single harvested npm token could itself be used to ship the next malicious version, multiplying reach geometrically.
This incident set the precedent for the credential-harvesting npm worm pattern that would later show up in Shai-Hulud (2025–2026) at much larger scale. The technique did not change. What changed is the volume of credentials a modern developer or CI runner holds beyond just .npmrc.
References
- [1]Postmortem for Malicious Packages Published on July 12th, 2018primary·2018-07-13·eslint.org
- [2]npm Blog — Reported malicious module: getcookiesprimary·2018-07-13·blog.npmjs.org
- [3]Hacker breaches npm account, infects ESLint packagereporting·2018-07-13·zdnet.com
