<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>README — npm</title>
<link>https://readme.news/tags/npm/</link>
<atom:link href="https://readme.news/tags/npm/feed.xml" rel="self" type="application/rss+xml"/>
<description>README pieces tagged npm.</description>
<language>en-us</language>
<lastBuildDate>Thu, 01 Oct 2026 13:20:31 +0000</lastBuildDate>
<item><title>Shai-Hulud: npm gets a self-replicating worm</title><link>https://readme.news/shai-hulud-npm-gets-a-self-replicating-worm/</link><guid isPermaLink="true">https://readme.news/shai-hulud-npm-gets-a-self-replicating-worm/</guid><pubDate>Tue, 16 Sep 2025 09:00:00 +0000</pubDate><description>A payload that steals credentials and then uses them to publish itself into other packages. This is the escalation everyone predicted.</description><content:encoded><![CDATA[<p>A self-propagating worm spread through npm this week, compromising hundreds of packages. The mechanism is the escalation people have been warning about for years, and it arrived exactly as described.</p>
<h2 id="how-it-works">how it works<a class="anchor" href="#how-it-works" aria-label="link to this section">#</a></h2>
<p>The loop:</p>
<ol><li>A malicious package version is installed. Its <code>postinstall</code> runs.</li><li>The payload harvests credentials from the machine — npm tokens, GitHub tokens, cloud provider credentials — using TruffleHog-style secret scanning.</li><li>It exfiltrates them to a public repository created in the victim's GitHub account.</li><li><strong>It uses the stolen npm token to publish trojanized versions of every package that token can publish to.</strong></li><li>Those packages get installed. Go to 1.</li></ol>
<p>Step four is the difference between an incident and an outbreak. Previous npm compromises required an attacker to manually obtain credentials for each package. This one propagates on its own, and its rate of spread is proportional to how many packages the compromised maintainers control.</p>
<p>Additional behavior observed: creating public forks of private repositories in victims' organizations, and installing a GitHub Actions workflow for persistence.</p>
<h2 id="why-this-was-inevitable">why this was inevitable<a class="anchor" href="#why-this-was-inevitable" aria-label="link to this section">#</a></h2>
<p>Every precondition has been in place for years:</p>
<ul><li>Packages execute arbitrary code on install, by default.</li><li>Publishing credentials are commonly stored on developer machines in plaintext.</li><li>One credential frequently controls many packages.</li><li>Nothing in the publishing flow requires human presence.</li></ul>
<p>Given those four facts, a worm is not a clever attack. It is the obvious one. Security researchers have described this exact scenario in talks for the better part of a decade.</p>
<h2 id="the-immediate-response">the immediate response<a class="anchor" href="#the-immediate-response" aria-label="link to this section">#</a></h2>
<p>npm has accelerated changes it had already announced: shorter token lifetimes, mandatory two-factor for high-impact publishing, restrictions on classic tokens, and a strong push toward trusted publishing.</p>
<p>Those are the right changes. They are also the changes that were "coming" for years and are now arriving under emergency conditions, which is how infrastructure security usually improves.</p>
<h2 id="what-to-do-right-now">what to do right now<a class="anchor" href="#what-to-do-right-now" aria-label="link to this section">#</a></h2>
<p><strong>Rotate every npm token you have</strong>, especially any on a developer machine or in a CI secret store. Assume anything that was on a machine that ran an install during the window is exposed.</p>
<p><strong>Enable trusted publishing</strong> on every package you maintain. This removes the long-lived token entirely — publishing is authorized by an OIDC assertion from a specific workflow in a specific repository.</p>
<p><strong>Audit your GitHub account</strong> for repositories and workflows you did not create. The persistence mechanism was a workflow file; it survives credential rotation.</p>
<p><strong>Turn off install scripts</strong> everywhere you can. This remains the single highest value control and remains widely unused.</p>
<p><strong>Adopt a version cooldown.</strong> Every one of these incidents has a window between publication and detection measured in hours. A 72-hour delay before adopting new versions costs you nothing and avoids nearly all of them.</p>
<h2 id="the-structural-conclusion">the structural conclusion<a class="anchor" href="#the-structural-conclusion" aria-label="link to this section">#</a></h2>
<p>The npm ecosystem's install-time code execution is a design decision from 2010 that made sense when the registry had a few thousand packages maintained by people who mostly knew each other.</p>
<p>It does not make sense now, and every incident makes the argument more forcefully. Other ecosystems handle this differently — Go has no install-time execution at all, and it turns out you can build a package ecosystem without it.</p>
<p>The migration cost for npm is enormous and the cost of not migrating is this, repeatedly, with escalating sophistication. At some point the arithmetic flips. It may have just flipped.</p>]]></content:encoded></item><item><title>chalk and debug get compromised, and 2 billion weekly downloads flinch</title><link>https://readme.news/chalk-and-debug-get-compromised-and-2-billion-weekly-downloads-flinch/</link><guid isPermaLink="true">https://readme.news/chalk-and-debug-get-compromised-and-2-billion-weekly-downloads-flinch/</guid><pubDate>Tue, 09 Sep 2025 09:00:00 +0000</pubDate><description>A phishing email to a maintainer, eighteen packages, and a crypto-stealing payload in the browser.</description><content:encoded><![CDATA[<p>A maintainer of several extremely widely-used npm packages — including <code>chalk</code>, <code>debug</code>, <code>ansi-styles</code>, and <code>strip-ansi</code> — was phished, and malicious versions of eighteen packages were published.</p>
<p>Combined weekly download counts for the affected packages are on the order of two billion.</p>
<h2 id="the-phish">the phish<a class="anchor" href="#the-phish" aria-label="link to this section">#</a></h2>
<p>An email from <code>npmjs.help</code> — a lookalike domain — claiming the account required two-factor re-verification, with a link to a credential harvesting page that also captured the TOTP code.</p>
<p>The maintainer has written publicly about it. The email was well-constructed, it arrived at a plausible time, and the domain was close enough to pass a quick glance.</p>
<p>This is worth saying clearly: the target was a competent, security-aware, long-time open source maintainer. Phishing that captures a TOTP in real time defeats the second factor. The control that actually stops this is a hardware security key or passkey, where the authentication is bound to the origin and a lookalike domain simply cannot complete it.</p>
<p><strong>If you publish packages, use a passkey or hardware key. Today. TOTP is not sufficient against this attack and has not been for years.</strong></p>
<h2 id="the-payload">the payload<a class="anchor" href="#the-payload" aria-label="link to this section">#</a></h2>
<p>Browser-targeted, not server-targeted, which is unusual and clever.</p>
<p>The injected code hooked <code>window.ethereum</code> and intercepted <code>fetch</code> and <code>XMLHttpRequest</code>, watching for cryptocurrency transactions and swapping destination addresses for attacker-controlled ones — selecting a visually similar address to survive a casual glance at the confirmation dialog.</p>
<p>Because these packages are ubiquitous transitive dependencies of frontend build tooling, the payload had a plausible path into a very large number of shipped bundles.</p>
<p>Actual losses appear to have been small. Detection was fast — within a couple of hours — and most builds during the window did not pull the affected versions.</p>
<h2 id="why-the-damage-was-limited">why the damage was limited<a class="anchor" href="#why-the-damage-was-limited" aria-label="link to this section">#</a></h2>
<p>Three things, in order:</p>
<ol><li><strong>Lockfiles.</strong> Most production builds resolve to pinned versions. A new malicious release does not enter an existing lockfile without someone running an update.</li><li><strong>Speed of detection.</strong> The community noticed within hours. Someone diffed the published tarball against the repository and the mismatch was obvious.</li><li><strong>Payload specificity.</strong> Targeting crypto wallets is narrow. A payload targeting build-time credential theft would have done far more damage.</li></ol>
<p>That third point should not be comforting. The same access with a better payload would have been much worse.</p>
<h2 id="the-controls-again">the controls, again<a class="anchor" href="#the-controls-again" aria-label="link to this section">#</a></h2>
<p>The same short list as every one of these:</p>
<ul><li><strong>Phishing-resistant auth on publishing accounts.</strong> Non-negotiable.</li><li><strong>Trusted publishing via OIDC</strong> rather than long-lived tokens.</li><li><strong>Lockfiles, committed, with exact versions in production.</strong></li><li><strong><code>--ignore-scripts</code> in CI.</strong></li><li><strong>A cooldown before adopting new versions.</strong></li></ul>
<p>None of these are new. All of them are still not universally adopted, which is the actual story.</p>
<h2 id="the-structural-observation">the structural observation<a class="anchor" href="#the-structural-observation" aria-label="link to this section">#</a></h2>
<p><code>chalk</code> adds color to terminal output. It is about a thousand lines. It has two billion weekly downloads because it is a transitive dependency of nearly everything in the JavaScript ecosystem.</p>
<p>A thousand-line utility maintained by a volunteer sits in the trusted computing base of a substantial fraction of the world's software, and the ecosystem's security posture depends on that person's email hygiene.</p>
<p>That is not a criticism of the maintainer, who did nothing unreasonable. It is a description of a system that has grown a dependency structure nobody designed and nobody can now change.</p>
<p>The fix is not "fewer dependencies." The fix is publishing infrastructure where a single compromised human credential cannot ship code to two billion machines. The registry has to make that impossible, because asking every maintainer to be unphishable has failed for a decade.</p>]]></content:encoded></item><item><title>The Nx compromise and the credential-stealing postinstall</title><link>https://readme.news/the-nx-compromise-and-the-credential-stealing-postinstall/</link><guid isPermaLink="true">https://readme.news/the-nx-compromise-and-the-credential-stealing-postinstall/</guid><pubDate>Fri, 29 Aug 2025 09:00:00 +0000</pubDate><description>A popular build tool&#x27;s npm packages ship a payload that harvests tokens and pushes them to public repos.</description><content:encoded><![CDATA[<p>Malicious versions of the <code>nx</code> build tool and several related packages were published to npm with a <code>postinstall</code> script that scanned the developer's machine for credentials and published them to a public GitHub repository created under the victim's own account.</p>
<h2 id="the-payload">the payload<a class="anchor" href="#the-payload" aria-label="link to this section">#</a></h2>
<p>The script searched for:</p>
<ul><li>npm and GitHub tokens</li><li>SSH private keys</li><li>Cloud provider credentials in the usual locations</li><li>Environment variables matching credential-shaped patterns</li><li>Cryptocurrency wallet files</li></ul>
<p>It then created a public repository in the victim's GitHub account named with a recognizable prefix and pushed the harvested data to it.</p>
<p>Some variants additionally invoked locally-installed AI coding CLI tools with a prompt asking them to enumerate sensitive files — using the developer's own agent as a discovery mechanism. That detail is new and it is going to be studied.</p>
<h2 id="the-entry-point">the entry point<a class="anchor" href="#the-entry-point" aria-label="link to this section">#</a></h2>
<p>Compromised publishing credentials. The specifics of how they were obtained matter less than the pattern: a maintainer's token, or a CI workflow with publishing rights, was reachable by an attacker.</p>
<p>This is the dominant supply chain attack shape. Not typosquatting, not dependency confusion, not a malicious contribution. <strong>Take over the account of a legitimate maintainer of a package people already depend on.</strong></p>
<h2 id="the-controls-that-would-have-stopped-it">the controls that would have stopped it<a class="anchor" href="#the-controls-that-would-have-stopped-it" aria-label="link to this section">#</a></h2>
<p>In order of effectiveness:</p>
<p><strong>1. <code>--ignore-scripts</code>.</strong> The payload was in <code>postinstall</code>. An install that does not run lifecycle scripts does not run the payload.</p>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">npm ci --ignore-scripts</code></pre></div>
<p>Set it in <code>.npmrc</code> for your CI and see what breaks. For most projects the answer is: nothing, or one package that needs a native build, which you allowlist.</p>
<p><strong>2. Trusted publishing.</strong> OIDC-based publishing from a verified CI workflow rather than a long-lived token. A token that does not exist cannot be stolen. npm supports this now and adoption is the bottleneck.</p>
<p><strong>3. Install cooldown.</strong> The malicious versions were live for hours before removal. A policy of not adopting a version until it is 24 to 72 hours old would have avoided this entirely, and avoids most incidents of this shape, because the window between publish and detection is short.</p>
<div class="code"><span class="code-lang">json</span><pre><code class="lang-json">{ "minimumReleaseAge": 4320 }</code></pre></div>
<p><strong>4. Short-lived credentials.</strong> The payload harvested long-lived tokens. If your CI uses OIDC federation to get a fifteen-minute cloud credential, there is nothing durable to steal.</p>
<h2 id="if-you-were-affected">if you were affected<a class="anchor" href="#if-you-were-affected" aria-label="link to this section">#</a></h2>
<p>The order matters:</p>
<ol><li><strong>Rotate everything</strong> the machine had access to. npm tokens, GitHub PATs, SSH keys, cloud credentials. Assume everything readable was read.</li><li><strong>Check for the public repository</strong> in your GitHub account and delete it — but capture its contents first for your incident record.</li><li><strong>Audit for persistence.</strong> Check <code>~/.bashrc</code>, <code>~/.zshrc</code>, <code>~/.profile</code>, shell history, cron, launch agents, and git hooks in your repositories.</li><li><strong>Review recent activity</strong> on every account whose credentials were on that machine.</li></ol>
<p>Rotation before cleanup. Cleanup on a compromised machine while live credentials are still valid is how you spend an afternoon on remediation and the attacker spends it on access.</p>
<h2 id="the-trend">the trend<a class="anchor" href="#the-trend" aria-label="link to this section">#</a></h2>
<p>This is one of several npm incidents this year and they are getting more sophisticated: better targeting, cleaner payloads, and now the use of local AI tooling as an attack primitive.</p>
<p>The ecosystem's structural weakness has not changed. A single maintainer's credentials protect a package that millions of machines install and execute automatically. Every control above mitigates; none of them fix that.</p>
<p>The fix requires the registry to make unattended publishing hard by default, and that is a change to how a lot of people work. It is going to happen anyway, because the alternative is this every few weeks.</p>]]></content:encoded></item><item><title>You did not audit that dependency and neither did I</title><link>https://readme.news/you-did-not-audit-that-dependency-and-neither-did-i/</link><guid isPermaLink="true">https://readme.news/you-did-not-audit-that-dependency-and-neither-did-i/</guid><pubDate>Wed, 12 Feb 2025 09:00:00 +0000</pubDate><description>A practical model for supply chain risk that doesn&#x27;t require pretending you read 40,000 files of transitive JavaScript.</description><content:encoded><![CDATA[<p>Run <code>npm ls --all</code> on a mid-sized frontend project. Count the packages. It is probably somewhere between eight hundred and two thousand. Now ask yourself, honestly, how many of those you have looked at.</p>
<p>The answer is zero, and the answer is zero for everyone, and the security advice industry has spent a decade pretending otherwise.</p>
<h2 id="the-useless-advice">the useless advice<a class="anchor" href="#the-useless-advice" aria-label="link to this section">#</a></h2>
<p>"Audit your dependencies." Nobody does this. It is not a discipline problem, it is an arithmetic problem. Two thousand packages at even five minutes each is eighty engineer-days, for one project, once, and it is invalidated by the next <code>npm update</code>.</p>
<p>"Minimize dependencies." Good advice that does not scale. You are not writing your own date library, and if you did it would have more bugs than the one you avoided.</p>
<p>"Use a lockfile." Necessary and completely insufficient. A lockfile pins you to a specific version of the compromised package.</p>
<h2 id="the-useful-model">the useful model<a class="anchor" href="#the-useful-model" aria-label="link to this section">#</a></h2>
<p>Stop trying to verify code. Start managing <strong>blast radius</strong> and <strong>time to detect</strong>.</p>
<p><strong>Blast radius.</strong> Ask: if this package were malicious right now, what could it reach? A dev-only dependency that runs in CI can read your CI secrets — that is a large blast radius, and most people classify dev dependencies as low risk, which is exactly backwards. A package in your production server can reach your database. A package in your build step can modify your output artifact, which is the worst one, because it is silent.</p>
<p>Concretely:</p>
<ul><li>Separate the CI credentials that can publish from the ones that can test. Publishing should require a manual approval or a protected environment, always.</li><li>Run installs with lifecycle scripts disabled where you can. <code>npm ci --ignore-scripts</code> for the test job costs you almost nothing on most trees.</li><li>Do not put long-lived cloud credentials in the same job that runs <code>npm install</code> on a third-party tree. Use OIDC federation with a short-lived token scoped to what the job needs.</li></ul>
<p><strong>Time to detect.</strong> Most supply chain compromises are discovered within hours to days, usually by someone noticing weird network traffic or a suspicious diff. The question is whether you shipped during the window.</p>
<ul><li>Pin exact versions in production. Not ranges. <code>1.2.3</code>, not <code>^1.2.3</code>.</li><li>Delay adoption. A cooldown of even 24 to 72 hours before a new version enters your tree catches an enormous fraction of real-world incidents, because the attacker's window is short and loud. Several tools now do this automatically; Renovate calls it <code>minimumReleaseAge</code>.</li><li>Subscribe to advisories for the fifty packages that actually matter, not all two thousand.</li></ul>
<h2 id="the-two-changes-with-the-best-ratio">the two changes with the best ratio<a class="anchor" href="#the-two-changes-with-the-best-ratio" aria-label="link to this section">#</a></h2>
<p>If you do nothing else:</p>
<ol><li><strong>Turn off install scripts in CI.</strong> The overwhelming majority of npm compromise payloads are in a <code>postinstall</code>. This one flag defeats most of them.</li><li><strong>Make publishing require a human.</strong> Trusted publishing via OIDC, provenance attestations, and a protected environment. The most damaging incidents are maintainer account takeovers, and a stolen token that cannot publish is worthless.</li></ol>
<h2 id="the-thing-nobody-wants-to-say">the thing nobody wants to say<a class="anchor" href="#the-thing-nobody-wants-to-say" aria-label="link to this section">#</a></h2>
<p>You are trusting hundreds of strangers, most of whom are unpaid, several of whom are burned out, at least one of whom will eventually hand their package to someone they should not have.</p>
<p>That trust is not going away, because the alternative is not writing software. The job is not to eliminate the trust. It is to make sure that when it is betrayed — and it will be, on a schedule — the damage is contained and you find out fast.</p>]]></content:encoded></item>
</channel>
</rss>
