The library, used to detect browser and user data, has close to 8M weekly downloads by developers around the world and is used as a dependency by 1200+ other packages in the public npm registry.
This critical security issue teaches us two major things for the npm ecosystem:
- The dependency tree comes with security risks. Your direct dependencies might not be malicious, but your dependencies of direct dependencies may be targeted. These transitive dependencies often range in hundreds and are large weak points.
- Organizations need to expand the scope of security and protect more than the CI/CD. Developer environments are often more numerous and more difficult to control, making it a more probable target to be compromised by malicious packages.
In times where ransomware is common, it’s more important than ever to protect your whole organization - controlling what packages are allowed in your environments.
The UA-parser-js incident in short
Three malicious versions (0.7.29, 0.8.0 1.0.0) of the popular library ua-parser-js were published on October 22, 2021. The versions were available as the latest major, minor and patch versions between 12:15 — 16:27 UTC (~4 hours) before all 3 versions were deprecated, unpublished and superseded by new non-malicious versions.
Users that installed npm packages during that time may be affected and should scan their environments for the malicious versions. Users that added an affected version should consider their whole system fully compromised. According to user reports, the malicious packages installed both a trojan and a crypto-currency miner.
See the security advisory for more details.
Case-study: Compromised without you even knowing it
Joe, the junior developer, helps his team with application testing over the course of the weekend. His environment is clean, so he diligently installs the required dependencies for the application using
The app only has a few direct dependencies, but with all the transitive dependencies he ends up adding over 700 dependencies. Unfortunately for Joe and his company, it’s already too late as he unknowingly installed a trojan into his environment…
This scenario demonstrates how easy it is to install npm dependencies with package managers, and what can go wrong when packages are allowed to execute arbitrary scripts as part of the install process.
Unlike when dealing with known vulnerabilities, malicious packages are a big threat for your development environment where potential user data, passwords and sensitive information is stored and therefore can be stolen by hackers.
Dependency tree and levels of dependencies
Npm dependencies in web applications are specified in the file
Looking at the specifics of the ua-parser-js attack, users installing any package with a dependency resolution like
ua-parser-js: ^0.7.xx would have gotten a malicious version (0.7.29) at the time of the incident.
That’s because the dependency resolution tells us to fetch the latest 0.7 patch version - unless remediated by factors like locked dependency versions.
So who was affected? Projects depending directly on
ua-parser-js were obviously at risk. But in the typical case the compromised dependency was added as a transitive dependency (dependency of a dependency).
Leading to users working with popular libraries and frameworks like
angular reporting inclusion of the compromised library.
# Example of dependency tree where ua-parser-js is included as a transitive dependency. 'npm ls' is used to identify the path for a specific dependency
$ npm ls ua-parser-js
# Excerpt from email@example.com where ua-parser-js is included as a dependency
# ua-parser-js: ^0.7.18 would resolve to the latest 0.7.x version. Installing a compromised version with malware during the time of the incident.
With transitive dependencies, the number of packages your projects depend on increases dramatically. To the point where it quickly becomes impossible to fully grasp what and how many dependencies your team uses - unless you use appropriate tooling.
The aftermath after ua-parser-js made one thing apparent. The biggest issue wasn’t figuring out if your applications used ua-parser, instead it was trying to figure out if you were exposed - in any environment, in any way, across hundreds of developers. A task a lot of companies worked vigorously with, as they lacked proper control over packages that enter their environment.
How can Bytesafe help? Control dependencies for the whole organization
Avoiding similar issues in the future should be a priority - and any investment into proper protection would save time and money in the long run.
So, the million dollar question - how do we avoid this in the future? We can mitigate most of the issues by inserting control over dependencies and the patch management process.
- Avoid unintended dependency version changes
- Use a single source for dependencies
- Continuously monitor dependencies
Locking dependency versions
Pinning inexact dependency versions grants flexibility for the ecosystem - but comes with inherent security risks. Having consecutive
npm install yield slightly different and non-deterministic results is not desired in neither CI/CD nor dev environments.
Dependency versions should be updated with intention and not as a side effect. To comply, organizations should put in place a process that updates, commits and reviews project lock-files and makes sure every subsequent installation (and user) uses the files.
Using lock-files (
yarn.lock) together with
npm ci for complete and deterministic installations introduces the necessary friction that make updating dependency versions a controlled process.
Organizations requiring an extra safe-guard against user error can freeze versions in a registry at a desired safe state with Bytesafe’s Freeze Policy. Temporarily disallowing updates from upstream sources, allowing to unfreeze when needed for a controlled patch management.
For more information on how to work securely with projects, locking versions and getting deterministic results, refer to Using per project registries in the documentation.
Using a dependency firewall
Depending directly on public registries and countless GitHub repositories, instead of using a single package source, quickly makes control over the flow of dependencies an impossible task. With multiple different sources, how are you going to make sure dependencies comply with your business policies, that they are safe and contain approved licenses?
The solution: a single hub like Bytesafe to enforce rules and monitor the flow of dependencies - for every developer, tester and build system.
Bytesafe is intentionally built for you to be able to have multiple registries, one for your every use-case. This segmentation allow you to separate the dependencies you use for every application and new release. With this, organizations can easily identify what dependencies have been used and where.
To make sure everyone is using the same registry source, projects should include a
.npmrc config file and
yarn.lock files that define what registry to use.
# Example .npmrc config setting the default registry to be used by npm clients
Let every user in your organization have secure access to Bytesafe. Add or publish dependencies, create registries and work on issues - securely as a team.
Identify and track problems with issues
Bytesafe continuously monitors the dependencies in your supply chain to identify vulnerabilities, as well as malicious and non-compliant packages. Issues are opened for any problem detected providing you with key details on vulnerabilities, problems and license issues - so you can review and remediate easily.
Key information includes:
- when the version was added to Bytesafe
- affected registry
- What event (and user) that included the problematic version
Notifications for issues are sent in-app, through email and Slack. Making sure you never miss security critical information.
Defend your supply chain with automatic quarantine of threats
What happens when it’s no longer a developer installing dependencies, but rather an automated environment? A key component of modern security tooling is to make sure threats are actively blocked, even if no human is actively monitoring.
Whenever a critical vulnerability is detected you want to take immediate actions. Making sure your teams, environments and business are protected - and your software supply chain can remain secure.
Bytesafe Quarantine allows you to automatically block the use of specific packages that surpass security threshold levels, for example critical advisories like
Allowing for a conscious decision if you decide to release a version from quarantine after review.
Read more in our post on using Quarantine in your dependency firewall.
Most package maintainers do not use 2FA to protect their npm accounts
As little as 10% of all npm maintainers used two-factor authentication (2FA) to secure their accounts in 2020. Making account takeover a very real threat. A supply chain attack that gives premium access to any environment that depends on it and considers it a trusted source. Make sure you are protected.
We are here to help
Dependency security can be a complex topic - but luckily Bytesafe can help you keep unwanted dependencies out of your organization.
If you have any questions or need guidance, feel free to contact me directly at firstname.lastname@example.org.
Thanks for reading!