Why bank change control never saw the npm hijack

Malicious versions of widely used JavaScript packages were live for about two hours in September 2025. Bank change control approves code people write; this attack arrived in code the build pipeline fetched on its own, which nobody approves.

Looking Back revisits one week at a time, from January 2020 to today, and asks what it meant for the banks, health systems and public agencies we serve. The series was written in 2026, with the benefit of hindsight, and each article says which week it looks back on.

The week

On September 8, 2025, attackers published malicious versions of about 18 popular npm packages, including chalk and debug, after phishing their maintainer with a fake two-factor reset email sent from a look-alike domain, npmjs.help. Security firms tracking the incident put the packages’ combined downloads at more than two billion a week, mostly as indirect dependencies. The injected code ran in web browsers. It hooked network requests and wallet interfaces to swap cryptocurrency destination addresses for the attackers’ own. Security researchers flagged the change quickly, and the maintainer and npm removed the malicious versions within roughly two hours. A week later a separate self-propagating campaign, later named Shai-Hulud, began stealing developer tokens and using them to publish infected versions of hundreds more packages.

The payload targeted cryptocurrency wallets, so most banks read the news as someone else’s problem. The delivery mechanism was the part that applied to them. For two hours, any build that resolved a fresh version of these packages could have shipped attacker code to customers. Bank change control would not have stopped it. A change advisory board reviews a diff written by an engineer and approves a release window. The dependency tree that the build tool resolves at that moment is not in the diff, and no one signs off on it. We break the gap into four parts. They are what change control actually reviews, time as a control, the packages a bank publishes, and protection at the moment of payment.

What change control actually reviews

A typical online banking front end pulls in hundreds or thousands of open-source packages, most of them several layers removed from anything a developer chose. Change records describe features and fixes. They rarely list the third-party code that changed between two releases, because nobody writes that code and the tooling hides it.

Closing this gap starts with making dependency changes visible as changes. Builds should run from a committed lock file using a clean-install command that refuses to resolve new versions, so the dependency set changes only when someone edits the lock file. Each edit then appears in the pull request, where a reviewer can see that a utility package moved versions and why. A software bill of materials generated per release, and stored with the change record, lets an incident team answer within minutes whether a named version ever shipped.

Classification matters as well. Many banks treat front-end releases as lower risk than core payment changes. In a browser-based payment flow, the front end decides which account number travels to the server. It deserves the same change category as the payment service behind it.

Time as a control

The September 8 versions were removed within about two hours. That window is typical for loud registry compromises, which are often caught quickly by researchers who scan new releases. An institution that waits a few days before adopting any new version avoids most of them at almost no cost.

Tooling caught up with the idea in 2025. GitHub’s Dependabot added a cooldown setting in July 2025 that delays update proposals for new releases, and the pnpm package manager added a minimum release age setting that September. Internal package proxies can enforce the same rule centrally by refusing versions younger than a set age.

The trade-off is security patches. A blanket delay also slows urgent fixes. The workable policy is a default wait with a documented exception path, where the security team can approve an urgent version after checking it. That keeps the decision with a named person rather than with whichever build ran first.

Delay has limits. It works against compromises that are noisy and quickly reported. It does little against a patient attacker whose code stays dormant for weeks, or against a package that was malicious from its first release. Those cases still depend on scanning new versions in the proxy and on an inventory precise enough to find affected releases once an advisory appears.

The packages a bank publishes

Banks and fintechs are also publishers. Many maintain software development kits, widgets and shared component libraries, on public registries or internal ones. Shai-Hulud showed how that role becomes an attack path. It stole tokens from developer machines and build systems, then used them to publish infected versions of whatever packages those tokens could reach.

Controls here look like privileged access management. Long-lived publishing tokens should be retired in favor of trusted publishing, where the build system obtains a short-lived credential tied to a specific repository and workflow. Publishing should require phishing-resistant authentication. Developer workstations and build runners should hold no standing registry credentials. A compromised bank SDK would reach partners and customers under the bank’s own name, which turns a supply chain incident into a reputational one.

Protection at the moment of payment

Even with all three controls, some malicious code will eventually reach a browser, whether through a dependency, a third-party script or an extension on the customer’s device. The last layer has to assume that.

Content Security Policy and Subresource Integrity limit which scripts a page can load and detect tampering with fixed third-party files. Server-side checks catch what the browser hides: payments to a never-before-seen destination, or a payee whose name does not match the account. Out-of-band confirmation, such as a push notification on a registered device that shows the payee, gives the customer a view the compromised page cannot alter. These controls are familiar from fraud programs. The npm incident is a reason to treat them as protection against software supply chain failures as well.

Card industry rules point the same way. PCI DSS version 4.0 requirements for inventorying and monitoring scripts on payment pages became mandatory on March 31, 2025. They apply to entities handling card data, and they are a reasonable model for any payment page a bank runs.

With hindsight

The September 8 compromise was contained quickly, but Shai-Hulud spread further. CISA issued an alert on September 23, 2025 describing a worm that had compromised more than 500 packages. A day earlier, GitHub had announced changes to npm publishing. They included mandatory two-factor authentication for local publishing, a seven-day maximum lifetime for granular publishing tokens, deprecation of classic tokens, and wider trusted publishing. A second wave, described by researchers as Shai-Hulud 2.0, began on November 24, 2025, and hit packages from several well-known software companies. Release-age delays spread across package managers and update tools through 2026. Looking back, the incidents showed that registry hygiene improves slowly and unevenly. Banks that pinned dependencies, delayed adoption and recorded what each release contained could answer customer and examiner questions in hours.

  • npm
  • software supply chain
  • change management
  • web applications
  • payments fraud