Ramblings of a Tampa engineer

November 4, 2021 was an interesting day for the Node ecosystem. Two packages (rc & coa) found themselves with malicious updates published.

Both packages published updates on NPM, but the corresponding GitHub repo for those packages saw no changes. Normally, a new packaged release without the corresponding source code just smells like an issue and this was no different.


RC
The non-configurable configuration loader for lazy people.

GHSA-g2q5-5433-rhrf
  • Last "safe" release - 1.2.8 (May 26, 2018)
  • Malicious Updates (1.2.9, 1.3.9, 2.3.9)
  • GHSA-g2q5-5433-rhrf (Advisory)
  • First noticed November 4, 2021 - 3:27pm EDT (Issue 131)
  • 1,780 packages use this dependency
  • 4,727,300 repositories on GitHub use this

COA
Yet another parser for command line options.

GHSA-73qr-pfmq-6rp8
  • Last "safe" release - 2.0.2 (December 10, 2018)
  • Malicious Updates (2.0.3, 2.0.4, 2.1.1, 2.1.3, 3.0.1, 3.1.3)
  • GHSA-73qr-pfmq-6rp8 (Advisory)
  • First noticed November 4, 2021 - 9:31am EDT (Issue 99)
  • 206 packages use this repository
  • 4,872,966 repositories on GitHub use this

Dependency Tree

The first thing to question and notice is how does a package like COA have 5 million installs. The package itself looks near abandoned and that is not an indication of a popular package when the source repository has hints of abandonment.

It doesn't take long to find a few very popular packages that have/had COA as a dependency.

Painful coa was replaced with well maintained commander.
Release notes from version 2 of svgo

I did some digging and svgo actually dropped it in v2, but that brings up an interesting point. If you move to a new major version of a package - that requires work and changes, but then you become immune to this specific issue.

However, on the flip side the actual updating of packages led from a safe to vulnerable update. So this is a tough pickle to solve. If you never update dependencies you are susceptible to known issues of vulnerable packages. If you do upgrade package you risk pulling in a malicious package that used to be otherwise secure.

NPM/Yarn/GitHub Responsibility

So there are indeed a lot of interesting discussions here that could neutralize this problem, but implementation and talking about each one is incredibly different.

Make install scripts opt-in [rfc]

This idea is simple, the scripts that can run on events (preinstall, install, postinstall, prepublish, preprepare, prepare, postprepare) become opt-in as a default instead of opt-out.

I don't think this method will get traction in theory, because as I look at even one package I worked on. We use the install event to build our source code at install time vs relying on a implicitly trusted compiled source. Yes, this is quite slower but it gives me satisfaction that folks obtain the same exact code on GitHub and compile it themselves.

If this RFC went forward - that workflow would no longer work without some sort of new flag to explicitly enable that event. However, perhaps a more heuristic driven approach here might work. If a dependency has a lockfile, then you can probably safely run those scripts vs say a project's own package.json update procedure.

Audit packages prior to publish.

This idea cost some serious amount of work hours. Imagine prior to any package being published on NPM that some human or machine had to scan it prior to publishing for approval. This could be also easily programmable from automated behavior:

  • New automatic event - flagged
  • New IP publishing - flagged
  • New version published without source control changing - flagged

I publish packages on Maven via Sonatype (Nexus) and this has a cool automated report to make sure I'm not publishing dependencies that are vulnerable.

Sonatype Lift report for Apktool v2.6.0

Now I'm not sure how Sonatype handles day zero kind of things. If someone updates a dependency tree then immediately pushes an update on the morning of November 4 - what system can catch that?

We know the truly manual approach that Firefox employs is sometimes very painful. Publishing extensions and waiting weeks for an approval for a bug fix can really destroy the extension. Though, can you take shortcuts with security?

Dependabot to the rescue (almost)

So GitHub and by proxy now NPM have this cool feature to automatically push package update commits to projects to upgrade to non-vulnerable packages. However, in this circumstance the update was vulnerable!

We are still processing this advisory. You may have affected repositories that are not yet on this list. Check back soon for more.

GitHub itself I don't think has yet to support this with Dependabot - I don't know exactly so I asked them. The purpose of Dependabot is to update packages and I'm sure so many checks and balances exist so it doesn't accidentally downgrade things. However, in this circumstance if GitHub detected a malicious package version of like COA 2.0.3 - could they do a PR to undo that update back to 2.0.2?

That feature would give peace of mind if a project accidentally pulled a vulnerable version.

NPM/Yarn Upgrade enhancements

I do really wish I could do yarn upgrade --min-days 14. This would basically find the up to date dependencies published at least 14 days ago. I want to update packages and really have to in the React Native community. I just don't want to accidentally roll the dice with pulling in packages published 6 minutes ago unless intentional.

Not updating dependencies in the React Native community means not supporting iOS 15 and Android 12 or not being able to build on an M1 Mac. You can't ignore package updates there, we just have to make the method easier to safely take updates.


In closing, these issues will continue to happen and I believe the attractive nature of the Node ecosystem will push more issues into the light.

https://npm.anvaka.com/#/view/2d/react-native

Since for example, take React Native - if we just install it right now we will take in 564 packages. So this is maybe why working in security is stressful.

You’ve successfully subscribed to Connor Tumbleson
Welcome back! You’ve successfully signed in.
Great! You’ve successfully signed up.
Success! Your email is updated.
Your link has expired
Success! Check your email for magic link to sign-in.