The Hidden Costs of Open Source
We all love and rely on the open-source ecosystem, but how much do we really understand about the risks and complexities that come with it?
This guide goes beyond a simple `npm install` to explore the mechanics, historical incidents, and best practices that shape modern, resilient software development. Let's dive in.
Dependency Tree
Npm builds a recursive graph of all packages and their sub-dependencies. Click the nodes below to see how it works.
Hoisting & Version Conflicts
Resulting `node_modules`:
The Blueprint: `package-lock.json`
This file is a precise, reproducible map of the entire dependency tree, ensuring consistent builds for everyone on the team.
{
"name": "your-project",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-..."
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-..."
}
}
}
The `left-pad` Incident (2016)
The author unpublished the package in a dispute with npm over a package name. Because thousands of projects used it as a transitive dependency, builds failed globally.
Takeaway: Our systems are only as strong as their most obscure dependency. The fragility of the chain is real.
The `event-stream` Attack (2018)
A popular but unmaintained package was taken over by a malicious actor who added a new, backdoored dependency. The code was designed to steal private keys from a specific Bitcoin wallet app.
Takeaway: A sophisticated supply chain attack can go undetected for months. Trust is not permanent.
The `stylus` Incident (2025)
The `stylus` preprocessor was removed by npm's security team, not because it was malicious, but because a contributor was linked to other malicious packages. This broke builds for major frameworks like Angular and Vue.
Takeaway: A package can be removed due to association, not just its own code, adding a new dimension to supply chain risk.
Securing Our Ecosystem
We can't eliminate all risk, but we can adopt practices that make our projects more secure and stable.
Regularly run this command to check for known security vulnerabilities.
Commit `package-lock.json` to ensure reproducible builds.
Use tools like `npm-check-updates` to stay aware of what's in your project.
Be aware that `postinstall` scripts can run arbitrary code on your machine.
Current Status: Major Upgrades Needed
Here's a look at the current dependency status of Uniclient, highlighting major version upgrades that require our attention. These may contain breaking changes and need careful review.
- @babel/preset-react: ^7.14.5 → ^7.27.1 - @builder.io/partytown: ^0.8.1 → ^0.10.3 - @currents/playwright: ^1.8.0 → ^1.15.3 - @cypress/grep: ^4.0.1 → ^4.1.1 - @datadog/browser-rum: ^6.7.0 → ^6.18.0 - @datadog/browser-rum-react: ^6.7.0 → ^6.18.0 - @easyops-cn/docusaurus-search-local: ^0.50.0 → ^0.52.1 - @froxz/vite-plugin-s3: ^1.5.0 → ^2.0.5 - @headlessui/react: ^2.2.0 → ^2.2.7 - @jscutlery/semver: ^5.2.2 → ^5.7.1 - @lemonade-hq/cantina-aws: ^16.1.4 → ^16.12.0 - @lemonade-hq/cantina-cli: ^16.1.4 → ^16.12.0 - @lemonade-hq/cantina-core: ^16.1.4 → ^16.12.0 - @lemonade-hq/cantina-db: ^16.1.4 → ^16.12.0 - @lemonade-hq/cantina-edge: ^16.1.4 → ^16.12.0 - @lemonade-hq/cantina-i18n: ^16.1.4 → ^16.12.0 - @lemonade-hq/cantina-nodejs: ^16.1.4 → ^16.12.0 - @lemonade-hq/chat-engine: 4.144.1 → 4.146.0 - @lemonade-hq/chat-engine-server: 4.144.1 → 4.146.0 - @lemonade-hq/chat-wizard-client-base: 4.144.1 → 4.146.0 - @lemonade-hq/chat-wizard-common-definitions: 4.144.1 → 4.146.0 - @lemonade-hq/e2e-core: ^0.12.26 → ^0.64.21 - @lemonade-hq/eslint-config-base: ^2.2.1 → ^2.10.0 - @lemonade-hq/eslint-config-frontend: ^2.2.1 → ^2.10.0 - @lemonade-hq/eslint-plugin-base: ^2.2.1 → ^2.10.0 - @lemonade-hq/eslint-plugin-frontend: ^2.2.1 → ^2.10.0 - @lemonade-hq/lemonation: ^1.148.0 → ^1.149.2 - @lemonade-hq/maschema-schema: 2.13.0 → 2.17.0 - @lemonade-hq/maschema-schema-secure: 2.13.0 → 2.17.0 - @lemonade-hq/maschema-utils: 2.13.0 → 2.17.0 - @lemonade-hq/maschema-validations: 2.13.0 → 2.17.0 - @lemonade-hq/nuiza: ^5.98.8 → ^5.101.6 - @lemonade-hq/prettier-config-backend: ^2.2.1 → ^2.10.0 - @lemonade-hq/prettier-config-base: ^2.2.1 → ^2.10.0 - @lemonade-hq/prettier-config-frontend: ^2.2.1 → ^2.10.0 - @lemonade-hq/ts-helpers: ^1.132.0 → ^1.139.1 - @mdx-js/react: ^3.0.0 → ^3.1.0 - @nx/cypress: 19.4.0 → 21.3.11 - @nx/eslint-plugin: 19.4.0 → 21.3.11 - @nx/js: 19.4.0 → 21.3.11 - @nx/linter: 19.4.0 → 19.8.4 - @nx/playwright: ^19.4.0 → ^21.3.11 - @nx/react: 19.4.0 → 21.3.11 - @nx/storybook: 19.4.0 → 21.3.11 - @nx/vite: 19.4.0 → 21.3.11 - @nx/web: 19.4.0 → 21.3.11 - @nx/workspace: 19.4.0 → 21.3.11 - @playwright/test: ^1.49.1 → ^1.54.2 - @radix-ui/react-accordion: ^1.1.2 → ^1.2.12 - @radix-ui/react-checkbox: ^1.0.4 → ^1.3.3 - @radix-ui/react-collapsible: ^1.0.3 → ^1.1.12 - @radix-ui/react-dialog: ^1.0.5 → ^1.1.15 - @radix-ui/react-dropdown-menu: ^2.0.6 → ^2.1.16 - @radix-ui/react-popover: ^1.0.7 → ^1.1.15 - @radix-ui/react-radio-group: ^1.1.3 → ^1.3.8 - @radix-ui/react-select: ^2.0.0 → ^2.2.6 - @radix-ui/react-separator: ^1.0.3 → ^1.1.7 - @radix-ui/react-slider: ^1.1.2 → ^1.3.6 - @radix-ui/react-switch: ^1.0.3 → ^1.2.6 - @radix-ui/react-toast: ^1.1.5 → ^1.2.15 - @radix-ui/react-tooltip: ^1.0.7 → ^1.2.8 - @radix-ui/react-visually-hidden: ^1.1.0 → ^1.2.3 - @rive-app/react-canvas: ^4.18.8 → ^4.23.1 - @storybook/addon-a11y: ^8.0.2 → ^9.1.2 - @storybook/addon-coverage: ^1.0.1 → ^2.0.0 - @storybook/addon-designs: ^8.0.0 → ^10.0.2 - @storybook/addon-essentials: ^8.0.2 → ^8.6.14 - @storybook/addon-interactions: ^8.4.6 → ^8.6.14 - @storybook/blocks: ^8.0.2 → ^8.6.14 - @storybook/core-common: ^8.0.2 → ^8.6.14 - @storybook/core-server: ^8.0.2 → ^8.6.14 - @storybook/manager-api: ^8.0.2 → ^8.6.14 - @storybook/react: ^8.0.2 → ^9.1.2 - @storybook/react-vite: ^8.0.2 → ^9.1.2 - @storybook/test-runner: ^0.17.0 → ^0.23.0 - @storybook/theming: ^8.0.2 → ^8.6.14 - @swc-node/register: ^1.6.6 → ^1.10.10 - @swc/cli: ~0.1.62 → ~0.7.8 - @swc/core: ~1.4.13 → ~1.13.3 - @swc/helpers: ~0.5.0 → ~0.5.17 - @testing-library/cypress: ^10.0.1 → ^10.0.3 - @testing-library/dom: ^10.4.0 → ^10.4.1 - @testing-library/jest-dom: ^5.16.5 → ^6.7.0 - @testing-library/react: ^16.0.1 → ^16.3.0 - @testing-library/user-event: ^14.4.3 → ^14.6.1 - @types/applepayjs: ^14.0.8 → ^14.0.9 - @types/dompurify: ^3.0.5 → ^3.2.0 - @types/lodash: ^4.17.7 → ^4.17.20 - @types/matter-js: ^0.19.6 → ^0.20.0 - @types/node: 18.14.2 → 24.2.1 - @types/react: 18.3.0 → 19.1.10 - @types/react-dom: 18.3.0 → 19.1.7 - @types/spreedly-iframe-browser: ^1.0.3 → ^1.1.0 - @types/testing-library__jest-dom: ^5.14.8 → ^6.0.0 - @typescript-eslint/eslint-plugin: 7.16.1 → 8.39.1 - @typescript-eslint/parser: 7.16.1 → 8.39.1 - @typescript-eslint/typescript-estree: 7.16.1 → 8.39.1 - @vanilla-extract/css: ^1.15.3 → ^1.17.4 - @vanilla-extract/dynamic: ^2.1.1 → ^2.1.5 - @vanilla-extract/esbuild-plugin: ^2.3.8 → ^2.3.18 - @vanilla-extract/recipes: ^0.5.3 → ^0.5.7 - @vanilla-extract/vite-plugin: ^4.0.12 → ^5.1.1 - @vitejs/plugin-react: ^4.3.1 → ^5.0.0 - @vitest/browser: 1.6.0 → 3.2.4 - @vitest/coverage-istanbul: ^1.6.0 → ^3.2.4 - @vitest/coverage-v8: ^1.6.0 → ^3.2.4 - @vitest/ui: ^1.6.0 → ^3.2.4 - @webpro/nx-tsc: ^0.0.1 → ^0.0.2 - card-validator: ^9.1.0 → ^10.0.3 - clsx: ^2.0.0 → ^2.1.1 - commander: ^11.0.0 → ^14.0.0 - concurrently: ^8.2.1 → ^9.2.0 - crypto-es: ^2.1.0 → ^3.0.0 - cspell: ^6.31.1 → ^9.2.0 - csstype: ^3.1.2 → ^3.1.3 - cypress: ^13.6.4 → ^14.5.4 - dompurify: ^3.1.5 → ^3.2.6 - esbuild: ^0.21.5 → ^0.25.9 - eslint-config-prettier: 9.1.0 → 10.1.8 - eslint-plugin-cypress: ^2.15.2 → ^5.1.0 - eslint-plugin-functional: ^5.0.8 → ^9.0.2 - eslint-plugin-import: 2.29.1 → 2.32.0 - eslint-plugin-jest: ^28.6.0 → ^29.0.1 - eslint-plugin-jsx-a11y: 6.7.1 → 6.10.2 - eslint-plugin-prettier: ^5.1.3 → ^5.5.4 - eslint-plugin-react: 7.34.3 → 7.37.5 - eslint-plugin-react-hooks: 4.6.2 → 5.2.0 - expect-type: ^0.17.3 → ^1.2.2 - framer-motion: ^10.17.0 → ^12.23.12 - hash-wasm: ^4.9.0 → ^4.12.0 - input-otp: ^1.2.4 → ^1.4.2 - jsdom: ^24.1.0 → ^26.1.0 - libphonenumber-js: ^1.11.5 → ^1.12.11 - match-sorter: ^6.3.1 → ^8.1.0 - matter-js: ^0.19.0 → ^0.20.0 - modern-screenshot: ^4.4.39 → ^4.6.5 - npm: 9.5.1 → 11.5.2 - nx: 19.4.0 → 21.3.11 - playwright: ^1.46.0 → ^1.54.2 - prettier: ^3.0.3 → ^3.6.2 - prism-react-renderer: ^2.3.0 → ^2.4.1 - rainbow-sprinkles: ^0.17.2 → ^1.0.0 - react: 18.3.0 → 19.1.1 - react-aria: ^3.37.0 → ^3.42.0 - react-aria-components: ^1.6.0 → ^1.11.0 - react-dom: 18.3.0 → 19.1.1 - react-markdown: ^8.0.7 → ^10.1.0 - react-merge-refs: ^2.1.1 → ^3.0.2 - react-number-format: ^5.4.3 → ^5.4.4 - react-plaid-link: ^3.6.0 → ^4.1.1 - react-router-dom: ^6.8.2 → ^7.8.0 - react-stately: ^3.35.0 → ^3.40.0 - regenerator-runtime: ^0.14.0 → ^0.14.1 - rollup-plugin-visualizer: ^5.14.0 → ^6.0.3 - sirv: ^2.0.3 → ^3.0.1 - styled-components: ^6.1.1 → ^6.1.19 - ts-node: 10.9.1 → 10.9.2 - tslib: ^2.6.0 → ^2.8.1 - type-fest: ^4.14.0 → ^4.41.0 - typescript: ~5.5.2 → ~5.9.2 - vaul: ^0.9.1 → ^1.1.2 - vite-plugin-dts: ~3.8.1 → ~4.5.4 - vite-tsconfig-paths: ^4.3.2 → ^5.1.4 - vitest: ^1.6.0 → ^3.2.4 - wait-on: ^8.0.1 → ^8.0.4