Last active
September 22, 2025 06:06
-
-
Save cgrothaus/d421a38a713cf40c2e51933901afc341 to your computer and use it in GitHub Desktop.
Script to check for compromised npm packages (npm supply chain attack of September 2025)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/zsh | |
| # | |
| # Script to check for compromised npm packages in monorepos using pnpm, npm, or yarn. | |
| # - Detects package manager from root lock file and checks all sub-projects. | |
| # - Properly handles transitive dependencies. | |
| # - Hardcoded list of compromised packages and versions. | |
| # | |
| # Usage: Run this script from the root of your monorepo with `zsh check-compromised-npm-packages.zsh` | |
| # | |
| # Background: news on supply chain attacks in npm packages of September 2025 | |
| # 1. | |
| # - https://www.aikido.dev/blog/npm-debug-and-chalk-packages-compromised | |
| # - https://news.ycombinator.com/item?id=45169794 | |
| # 2. | |
| # - https://socket.dev/blog/tinycolor-supply-chain-attack-affects-40-packages | |
| # 3. | |
| # - https://www.ox.security/blog/npm-2-0-hack-40-npm-packages-hit-in-major-supply-chain-attack/ | |
| # - https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-again | |
| # 4. | |
| # - https://socket.dev/blog/ongoing-supply-chain-attack-targets-crowdstrike-npm-packages | |
| # List of compromised packages and versions | |
| # Format: package:version | |
| compromised=( | |
| # first attack compromised packages: | |
| "@duckdb/node-api:1.3.3" | |
| "@duckdb/node-bindings:1.3.3" | |
| "ansi-regex:6.2.1" | |
| "ansi-styles:6.2.2" | |
| "backslash:0.2.1" | |
| "chalk-template:1.1.1" | |
| "chalk:5.6.1" | |
| "color-convert:3.1.1" | |
| "color-name:2.0.1" | |
| "color-string:2.1.1" | |
| "color:5.0.1" | |
| "debug:4.4.2" | |
| "duckdb:1.3.3" | |
| "error-ex:1.3.3" | |
| "has-ansi:6.0.1" | |
| "is-arrayish:0.3.3" | |
| "simple-swizzle:0.2.3" | |
| "slice-ansi:7.1.1" | |
| "strip-ansi:7.1.1" | |
| "supports-color:10.2.1" | |
| "supports-hyperlinks:4.1.1" | |
| "wrap-ansi:9.0.1" | |
| # second attack compromised packages: | |
| "@ctrl/deluge:7.2.2" | |
| "@ctrl/golang-template:1.4.3" | |
| "@ctrl/magnet-link:4.0.4" | |
| "@ctrl/ngx-codemirror:7.0.2" | |
| "@ctrl/ngx-csv:6.0.2" | |
| "@ctrl/ngx-emoji-mart:9.2.2" | |
| "@ctrl/ngx-rightclick:4.0.2" | |
| "@ctrl/qbittorrent:9.7.2" | |
| "@ctrl/react-adsense:2.0.2" | |
| "@ctrl/shared-torrent:6.3.2" | |
| "@ctrl/tinycolor:4.1.1" | |
| "@ctrl/tinycolor:4.1.2" | |
| "@ctrl/torrent-file:4.1.2" | |
| "@ctrl/transmission:7.3.1" | |
| "@ctrl/ts-base32:4.0.2" | |
| "@nativescript-community/gesturehandler:2.0.35" | |
| "@nativescript-community/sentry:4.6.43" | |
| "@nativescript-community/text:1.6.13" | |
| "@nativescript-community/ui-collectionview:6.0.6" | |
| "@nativescript-community/ui-drawer:0.1.30" | |
| "@nativescript-community/ui-image:4.5.6" | |
| "@nativescript-community/ui-material-bottomsheet:7.2.72" | |
| "@nativescript-community/ui-material-core-tabs:7.2.76" | |
| "@nativescript-community/ui-material-core:7.2.76" | |
| "angulartics2:14.1.2" | |
| "encounter-playground:0.0.5" | |
| "json-rules-engine-simplified:0.2.1" | |
| "json-rules-engine-simplified:0.2.4" | |
| "koa2-swagger-ui:5.11.1" | |
| "koa2-swagger-ui:5.11.2" | |
| "ngx-color:10.0.2" | |
| "ngx-toastr:19.0.2" | |
| "ngx-trend:8.0.1" | |
| "react-complaint-image:0.0.35" | |
| "react-jsonschema-form-conditionals:0.3.21" | |
| "react-jsonschema-form-extras:1.0.4" | |
| "rxnt-authentication:0.0.6" | |
| "rxnt-healthchecks-nestjs:1.0.5" | |
| "rxnt-kue:1.0.7" | |
| "swc-plugin-component-annotate:1.9.2" | |
| "ts-gaussian:3.0.6" | |
| # third attack compromised packages: | |
| "@ahmedhfarag/ngx-perfect-scrollbar:20.0.20" | |
| "@ahmedhfarag/ngx-virtual-scroller:4.0.4" | |
| "@art-ws/common:2.0.28" | |
| "@art-ws/config-eslint:2.0.4" | |
| "@art-ws/config-eslint:2.0.5" | |
| "@art-ws/config-ts:2.0.7" | |
| "@art-ws/config-ts:2.0.8" | |
| "@art-ws/db-context:2.0.24" | |
| "@art-ws/di:2.0.28" | |
| "@art-ws/di:2.0.32" | |
| "@art-ws/di-node:2.0.13" | |
| "@art-ws/eslint:1.0.5" | |
| "@art-ws/eslint:1.0.6" | |
| "@art-ws/fastify-http-server:2.0.24" | |
| "@art-ws/fastify-http-server:2.0.27" | |
| "@art-ws/http-server:2.0.21" | |
| "@art-ws/http-server:2.0.25" | |
| "@art-ws/openapi:0.1.9" | |
| "@art-ws/openapi:0.1.12" | |
| "@art-ws/package-base:1.0.5" | |
| "@art-ws/package-base:1.0.6" | |
| "@art-ws/prettier:1.0.5" | |
| "@art-ws/prettier:1.0.6" | |
| "@art-ws/slf:2.0.15" | |
| "@art-ws/slf:2.0.22" | |
| "@art-ws/ssl-info:1.0.9" | |
| "@art-ws/ssl-info:1.0.10" | |
| "@art-ws/web-app:1.0.3" | |
| "@art-ws/web-app:1.0.4" | |
| "@crowdstrike/commitlint:8.1.1" | |
| "@crowdstrike/commitlint:8.1.2" | |
| "@crowdstrike/falcon-shoelace:0.4.1" | |
| "@crowdstrike/falcon-shoelace:0.4.2" | |
| "@crowdstrike/foundry-js:0.19.1" | |
| "@crowdstrike/foundry-js:0.19.2" | |
| "@crowdstrike/glide-core:0.34.2" | |
| "@crowdstrike/glide-core:0.34.3" | |
| "@crowdstrike/logscale-dashboard:1.205.1" | |
| "@crowdstrike/logscale-dashboard:1.205.2" | |
| "@crowdstrike/logscale-file-editor:1.205.1" | |
| "@crowdstrike/logscale-file-editor:1.205.2" | |
| "@crowdstrike/logscale-parser-edit:1.205.1" | |
| "@crowdstrike/logscale-parser-edit:1.205.2" | |
| "@crowdstrike/logscale-search:1.205.1" | |
| "@crowdstrike/logscale-search:1.205.2" | |
| "@crowdstrike/tailwind-toucan-base:5.0.1" | |
| "@crowdstrike/tailwind-toucan-base:5.0.2" | |
| "@ctrl/deluge:7.2.1" | |
| "@ctrl/golang-template:1.4.2" | |
| "@ctrl/magnet-link:4.0.3" | |
| "@ctrl/ngx-codemirror:7.0.1" | |
| "@ctrl/ngx-csv:6.0.1" | |
| "@ctrl/ngx-emoji-mart:9.2.1" | |
| "@ctrl/ngx-rightclick:4.0.1" | |
| "@ctrl/qbittorrent:9.7.1" | |
| "@ctrl/react-adsense:2.0.1" | |
| "@ctrl/shared-torrent:6.3.1" | |
| "@ctrl/torrent-file:4.1.1" | |
| "@ctrl/ts-base32:4.0.1" | |
| "@hestjs/core:0.2.1" | |
| "@hestjs/cqrs:0.1.6" | |
| "@hestjs/demo:0.1.2" | |
| "@hestjs/eslint-config:0.1.2" | |
| "@hestjs/logger:0.1.6" | |
| "@hestjs/scalar:0.1.7" | |
| "@hestjs/validation:0.1.6" | |
| "@nativescript-community/arraybuffers:1.1.6" | |
| "@nativescript-community/arraybuffers:1.1.7" | |
| "@nativescript-community/arraybuffers:1.1.8" | |
| "@nativescript-community/perms:3.0.5" | |
| "@nativescript-community/perms:3.0.6" | |
| "@nativescript-community/perms:3.0.7" | |
| "@nativescript-community/perms:3.0.8" | |
| "@nativescript-community/perms:3.0.9" | |
| "@nativescript-community/sqlite:3.5.2" | |
| "@nativescript-community/sqlite:3.5.3" | |
| "@nativescript-community/sqlite:3.5.4" | |
| "@nativescript-community/sqlite:3.5.5" | |
| "@nativescript-community/text:1.6.9" | |
| "@nativescript-community/text:1.6.10" | |
| "@nativescript-community/text:1.6.11" | |
| "@nativescript-community/text:1.6.12" | |
| "@nativescript-community/typeorm:0.2.30" | |
| "@nativescript-community/typeorm:0.2.31" | |
| "@nativescript-community/typeorm:0.2.32" | |
| "@nativescript-community/typeorm:0.2.33" | |
| "@nativescript-community/ui-document-picker:1.1.27" | |
| "@nativescript-community/ui-document-picker:1.1.28" | |
| "@nativescript-community/ui-label:1.3.35" | |
| "@nativescript-community/ui-label:1.3.36" | |
| "@nativescript-community/ui-label:1.3.37" | |
| "@nativescript-community/ui-material-bottom-navigation:7.2.72" | |
| "@nativescript-community/ui-material-bottom-navigation:7.2.73" | |
| "@nativescript-community/ui-material-bottom-navigation:7.2.74" | |
| "@nativescript-community/ui-material-bottom-navigation:7.2.75" | |
| "@nativescript-community/ui-material-core:7.2.72" | |
| "@nativescript-community/ui-material-core:7.2.73" | |
| "@nativescript-community/ui-material-core:7.2.74" | |
| "@nativescript-community/ui-material-core:7.2.75" | |
| "@nativescript-community/ui-material-core-tabs:7.2.72" | |
| "@nativescript-community/ui-material-core-tabs:7.2.73" | |
| "@nativescript-community/ui-material-core-tabs:7.2.74" | |
| "@nativescript-community/ui-material-core-tabs:7.2.75" | |
| "@nativescript-community/ui-material-ripple:7.2.72" | |
| "@nativescript-community/ui-material-ripple:7.2.73" | |
| "@nativescript-community/ui-material-ripple:7.2.74" | |
| "@nativescript-community/ui-material-ripple:7.2.75" | |
| "@nativescript-community/ui-material-tabs:7.2.72" | |
| "@nativescript-community/ui-material-tabs:7.2.73" | |
| "@nativescript-community/ui-material-tabs:7.2.74" | |
| "@nativescript-community/ui-material-tabs:7.2.75" | |
| "@nativescript-community/ui-pager:14.1.36" | |
| "@nativescript-community/ui-pager:14.1.37" | |
| "@nativescript-community/ui-pager:14.1.38" | |
| "@nativescript-community/ui-pager:14.1.35" | |
| "@nativescript-community/ui-pulltorefresh:2.5.4" | |
| "@nativescript-community/ui-pulltorefresh:2.5.5" | |
| "@nativescript-community/ui-pulltorefresh:2.5.6" | |
| "@nativescript-community/ui-pulltorefresh:2.5.7" | |
| "@nexe/config-manager:0.1.1" | |
| "@nexe/eslint-config:0.1.1" | |
| "@nexe/logger:0.1.3" | |
| "@nstudio/angular:20.0.4" | |
| "@nstudio/angular:20.0.5" | |
| "@nstudio/angular:20.0.6" | |
| "@nstudio/focus:20.0.4" | |
| "@nstudio/focus:20.0.5" | |
| "@nstudio/focus:20.0.6" | |
| "@nstudio/nativescript-checkbox:2.0.6" | |
| "@nstudio/nativescript-checkbox:2.0.7" | |
| "@nstudio/nativescript-checkbox:2.0.8" | |
| "@nstudio/nativescript-checkbox:2.0.9" | |
| "@nstudio/nativescript-loading-indicator:5.0.1" | |
| "@nstudio/nativescript-loading-indicator:5.0.2" | |
| "@nstudio/nativescript-loading-indicator:5.0.3" | |
| "@nstudio/nativescript-loading-indicator:5.0.4" | |
| "@nstudio/ui-collectionview:5.1.11" | |
| "@nstudio/ui-collectionview:5.1.12" | |
| "@nstudio/ui-collectionview:5.1.13" | |
| "@nstudio/ui-collectionview:5.1.14" | |
| "@nstudio/web:20.0.4" | |
| "@nstudio/web-angular:20.0.4" | |
| "@nstudio/xplat:20.0.5" | |
| "@nstudio/xplat:20.0.6" | |
| "@nstudio/xplat:20.0.7" | |
| "@nstudio/xplat:20.0.4" | |
| "@nstudio/xplat-utils:20.0.5" | |
| "@nstudio/xplat-utils:20.0.6" | |
| "@nstudio/xplat-utils:20.0.7" | |
| "@nstudio/xplat-utils:20.0.4" | |
| "@operato/board:9.0.36" | |
| "@operato/board:9.0.37" | |
| "@operato/board:9.0.38" | |
| "@operato/board:9.0.39" | |
| "@operato/board:9.0.40" | |
| "@operato/board:9.0.41" | |
| "@operato/board:9.0.42" | |
| "@operato/board:9.0.43" | |
| "@operato/board:9.0.44" | |
| "@operato/board:9.0.45" | |
| "@operato/board:9.0.46" | |
| "@operato/data-grist:9.0.29" | |
| "@operato/data-grist:9.0.35" | |
| "@operato/data-grist:9.0.36" | |
| "@operato/data-grist:9.0.37" | |
| "@operato/graphql:9.0.22" | |
| "@operato/graphql:9.0.35" | |
| "@operato/graphql:9.0.36" | |
| "@operato/graphql:9.0.37" | |
| "@operato/graphql:9.0.38" | |
| "@operato/graphql:9.0.39" | |
| "@operato/graphql:9.0.40" | |
| "@operato/graphql:9.0.41" | |
| "@operato/graphql:9.0.42" | |
| "@operato/graphql:9.0.43" | |
| "@operato/graphql:9.0.44" | |
| "@operato/graphql:9.0.45" | |
| "@operato/graphql:9.0.46" | |
| "@operato/headroom:9.0.2" | |
| "@operato/headroom:9.0.35" | |
| "@operato/headroom:9.0.36" | |
| "@operato/headroom:9.0.37" | |
| "@operato/help:9.0.35" | |
| "@operato/help:9.0.36" | |
| "@operato/help:9.0.37" | |
| "@operato/help:9.0.38" | |
| "@operato/help:9.0.39" | |
| "@operato/help:9.0.40" | |
| "@operato/help:9.0.41" | |
| "@operato/help:9.0.42" | |
| "@operato/help:9.0.43" | |
| "@operato/help:9.0.44" | |
| "@operato/help:9.0.45" | |
| "@operato/help:9.0.46" | |
| "@operato/i18n:9.0.35" | |
| "@operato/i18n:9.0.36" | |
| "@operato/i18n:9.0.37" | |
| "@operato/input:9.0.27" | |
| "@operato/input:9.0.35" | |
| "@operato/input:9.0.36" | |
| "@operato/input:9.0.37" | |
| "@operato/input:9.0.38" | |
| "@operato/input:9.0.39" | |
| "@operato/input:9.0.40" | |
| "@operato/input:9.0.41" | |
| "@operato/input:9.0.42" | |
| "@operato/input:9.0.43" | |
| "@operato/input:9.0.44" | |
| "@operato/input:9.0.45" | |
| "@operato/input:9.0.46" | |
| "@operato/layout:9.0.35" | |
| "@operato/layout:9.0.36" | |
| "@operato/layout:9.0.37" | |
| "@operato/popup:9.0.22" | |
| "@operato/popup:9.0.35" | |
| "@operato/popup:9.0.36" | |
| "@operato/popup:9.0.37" | |
| "@operato/popup:9.0.38" | |
| "@operato/popup:9.0.39" | |
| "@operato/popup:9.0.40" | |
| "@operato/popup:9.0.41" | |
| "@operato/popup:9.0.42" | |
| "@operato/popup:9.0.43" | |
| "@operato/popup:9.0.44" | |
| "@operato/popup:9.0.45" | |
| "@operato/popup:9.0.46" | |
| "@operato/pull-to-refresh:9.0.36" | |
| "@operato/pull-to-refresh:9.0.37" | |
| "@operato/pull-to-refresh:9.0.38" | |
| "@operato/pull-to-refresh:9.0.39" | |
| "@operato/pull-to-refresh:9.0.40" | |
| "@operato/pull-to-refresh:9.0.41" | |
| "@operato/pull-to-refresh:9.0.42" | |
| "@operato/shell:9.0.22" | |
| "@operato/shell:9.0.35" | |
| "@operato/shell:9.0.36" | |
| "@operato/shell:9.0.37" | |
| "@operato/shell:9.0.38" | |
| "@operato/shell:9.0.39" | |
| "@operato/styles:9.0.2" | |
| "@operato/styles:9.0.35" | |
| "@operato/styles:9.0.36" | |
| "@operato/styles:9.0.37" | |
| "@operato/utils:9.0.22" | |
| "@operato/utils:9.0.35" | |
| "@operato/utils:9.0.36" | |
| "@operato/utils:9.0.37" | |
| "@operato/utils:9.0.38" | |
| "@operato/utils:9.0.39" | |
| "@operato/utils:9.0.40" | |
| "@operato/utils:9.0.41" | |
| "@operato/utils:9.0.42" | |
| "@operato/utils:9.0.43" | |
| "@operato/utils:9.0.44" | |
| "@operato/utils:9.0.45" | |
| "@operato/utils:9.0.46" | |
| "@teselagen/bounce-loader:0.3.16" | |
| "@teselagen/bounce-loader:0.3.17" | |
| "@teselagen/liquibase-tools:0.4.1" | |
| "@teselagen/range-utils:0.3.14" | |
| "@teselagen/range-utils:0.3.15" | |
| "@teselagen/react-list:0.8.19" | |
| "@teselagen/react-list:0.8.20" | |
| "@teselagen/react-table:6.10.19" | |
| "@teselagen/react-table:6.10.21" | |
| "@thangved/callback-window:1.1.4" | |
| "@things-factory/attachment-base:9.0.43" | |
| "@things-factory/attachment-base:9.0.44" | |
| "@things-factory/attachment-base:9.0.45" | |
| "@things-factory/attachment-base:9.0.46" | |
| "@things-factory/attachment-base:9.0.47" | |
| "@things-factory/attachment-base:9.0.48" | |
| "@things-factory/attachment-base:9.0.49" | |
| "@things-factory/attachment-base:9.0.50" | |
| "@things-factory/auth-base:9.0.43" | |
| "@things-factory/auth-base:9.0.44" | |
| "@things-factory/auth-base:9.0.45" | |
| "@things-factory/email-base:9.0.42" | |
| "@things-factory/email-base:9.0.43" | |
| "@things-factory/email-base:9.0.44" | |
| "@things-factory/email-base:9.0.45" | |
| "@things-factory/email-base:9.0.46" | |
| "@things-factory/email-base:9.0.47" | |
| "@things-factory/email-base:9.0.48" | |
| "@things-factory/email-base:9.0.49" | |
| "@things-factory/email-base:9.0.50" | |
| "@things-factory/email-base:9.0.51" | |
| "@things-factory/email-base:9.0.52" | |
| "@things-factory/email-base:9.0.53" | |
| "@things-factory/email-base:9.0.54" | |
| "@things-factory/env:9.0.42" | |
| "@things-factory/env:9.0.43" | |
| "@things-factory/env:9.0.44" | |
| "@things-factory/env:9.0.45" | |
| "@things-factory/integration-base:9.0.43" | |
| "@things-factory/integration-base:9.0.44" | |
| "@things-factory/integration-base:9.0.45" | |
| "@things-factory/integration-marketplace:9.0.43" | |
| "@things-factory/integration-marketplace:9.0.44" | |
| "@things-factory/integration-marketplace:9.0.45" | |
| "@things-factory/shell:9.0.43" | |
| "@things-factory/shell:9.0.44" | |
| "@things-factory/shell:9.0.45" | |
| "@tnf-dev/api:1.0.8" | |
| "@tnf-dev/core:1.0.8" | |
| "@tnf-dev/js:1.0.8" | |
| "@tnf-dev/mui:1.0.8" | |
| "@tnf-dev/react:1.0.8" | |
| "@ui-ux-gang/devextreme-angular-rpk:24.1.7" | |
| "@yoobic/design-system:6.5.17" | |
| "@yoobic/jpeg-camera-es6:1.0.13" | |
| "@yoobic/yobi:8.7.53" | |
| "airchief:0.3.1" | |
| "airpilot:0.8.8" | |
| "angulartics2:14.1.1" | |
| "browser-webdriver-downloader:3.0.8" | |
| "capacitor-notificationhandler:0.0.2" | |
| "capacitor-notificationhandler:0.0.3" | |
| "capacitor-plugin-healthapp:0.0.2" | |
| "capacitor-plugin-healthapp:0.0.3" | |
| "capacitor-plugin-ihealth:1.1.8" | |
| "capacitor-plugin-ihealth:1.1.9" | |
| "capacitor-plugin-vonage:1.0.2" | |
| "capacitor-plugin-vonage:1.0.3" | |
| "capacitorandroidpermissions:0.0.4" | |
| "capacitorandroidpermissions:0.0.5" | |
| "config-cordova:0.8.5" | |
| "cordova-plugin-voxeet2:1.0.24" | |
| "cordova-voxeet:1.0.32" | |
| "create-hest-app:0.1.9" | |
| "db-evo:1.1.4" | |
| "db-evo:1.1.5" | |
| "devextreme-angular-rpk:21.2.8" | |
| "ember-browser-services:5.0.2" | |
| "ember-browser-services:5.0.3" | |
| "ember-headless-form:1.1.2" | |
| "ember-headless-form:1.1.3" | |
| "ember-headless-form-yup:1.0.1" | |
| "ember-headless-table:2.1.5" | |
| "ember-headless-table:2.1.6" | |
| "ember-url-hash-polyfill:1.0.12" | |
| "ember-url-hash-polyfill:1.0.13" | |
| "ember-velcro:2.2.1" | |
| "ember-velcro:2.2.2" | |
| "encounter-playground:0.0.2" | |
| "encounter-playground:0.0.3" | |
| "encounter-playground:0.0.4" | |
| "eslint-config-crowdstrike:11.0.2" | |
| "eslint-config-crowdstrike:11.0.3" | |
| "eslint-config-crowdstrike-node:4.0.3" | |
| "eslint-config-crowdstrike-node:4.0.4" | |
| "eslint-config-teselagen:6.1.7" | |
| "globalize-rpk:1.7.4" | |
| "graphql-sequelize-teselagen:5.3.8" | |
| "html-to-base64-image:1.0.2" | |
| "json-rules-engine-simplified:0.2.3" | |
| "json-rules-engine-simplified:0.2.2" | |
| "jumpgate:0.0.2" | |
| "mcfly-semantic-release:1.3.1" | |
| "mcp-knowledge-base:0.0.2" | |
| "mcp-knowledge-graph:1.2.1" | |
| "mobioffice-cli:1.0.3" | |
| "monorepo-next:13.0.1" | |
| "monorepo-next:13.0.2" | |
| "mstate-angular:0.4.4" | |
| "mstate-cli:0.4.7" | |
| "mstate-dev-react:1.1.1" | |
| "mstate-react:1.6.5" | |
| "ng2-file-upload:7.0.2" | |
| "ng2-file-upload:7.0.3" | |
| "ng2-file-upload:8.0.1" | |
| "ng2-file-upload:8.0.2" | |
| "ng2-file-upload:8.0.3" | |
| "ng2-file-upload:9.0.1" | |
| "ngx-bootstrap:18.1.4" | |
| "ngx-bootstrap:19.0.3" | |
| "ngx-bootstrap:19.0.4" | |
| "ngx-bootstrap:20.0.3" | |
| "ngx-bootstrap:20.0.4" | |
| "ngx-bootstrap:20.0.5" | |
| "ngx-bootstrap:20.0.6" | |
| "ngx-color:10.0.1" | |
| "ngx-toastr:19.0.1" | |
| "ngx-ws:1.1.5" | |
| "ngx-ws:1.1.6" | |
| "oradm-to-gql:35.0.14" | |
| "oradm-to-gql:35.0.15" | |
| "oradm-to-sqlz:1.1.2" | |
| "oradm-to-sqlz:1.1.3" | |
| "oradm-to-sqlz:1.1.4" | |
| "ove-auto-annotate:0.0.9" | |
| "pm2-gelf-json:1.0.4" | |
| "pm2-gelf-json:1.0.5" | |
| "printjs-rpk:1.6.1" | |
| "react-complaint-image:0.0.32" | |
| "react-complaint-image:0.0.33" | |
| "react-complaint-image:0.0.34" | |
| "react-jsonschema-form-conditionals:0.3.18" | |
| "react-jsonschema-form-conditionals:0.3.19" | |
| "react-jsonschema-form-conditionals:0.3.20" | |
| "remark-preset-lint-crowdstrike:4.0.1" | |
| "remark-preset-lint-crowdstrike:4.0.2" | |
| "rxnt-authentication:0.0.3" | |
| "rxnt-authentication:0.0.4" | |
| "rxnt-authentication:0.0.5" | |
| "rxnt-healthchecks-nestjs:1.0.2" | |
| "rxnt-healthchecks-nestjs:1.0.3" | |
| "rxnt-healthchecks-nestjs:1.0.4" | |
| "rxnt-kue:1.0.4" | |
| "rxnt-kue:1.0.5" | |
| "rxnt-kue:1.0.6" | |
| "swc-plugin-component-annotate:1.9.1" | |
| "tbssnch:1.0.2" | |
| "teselagen-interval-tree:1.1.2" | |
| "tg-client-query-builder:2.14.4" | |
| "tg-client-query-builder:2.14.5" | |
| "tg-redbird:1.3.1" | |
| "tg-seq-gen:1.0.9" | |
| "tg-seq-gen:1.0.10" | |
| "thangved-react-grid:1.0.3" | |
| "ts-gaussian:3.0.5" | |
| "ts-imports:1.0.1" | |
| "ts-imports:1.0.2" | |
| "tvi-cli:0.1.5" | |
| "ve-bamreader:0.2.6" | |
| "ve-editor:1.0.1" | |
| "verror-extra:6.0.1" | |
| "voip-callkit:1.0.2" | |
| "voip-callkit:1.0.3" | |
| "wdio-web-reporter:0.1.3" | |
| "yargs-help-output:5.0.3" | |
| "yoo-styles:6.0.326" | |
| # fourth article compromised packages: | |
| "@operato/board:9.0.35" | |
| "@operato/board:9.0.47" | |
| "@operato/board:9.0.48" | |
| "@operato/board:9.0.49" | |
| "@operato/board:9.0.50" | |
| "@operato/board:9.0.51" | |
| "@operato/graphql:9.0.47" | |
| "@operato/graphql:9.0.48" | |
| "@operato/graphql:9.0.49" | |
| "@operato/graphql:9.0.50" | |
| "@operato/graphql:9.0.51" | |
| "@operato/help:9.0.47" | |
| "@operato/help:9.0.48" | |
| "@operato/help:9.0.49" | |
| "@operato/help:9.0.50" | |
| "@operato/help:9.0.51" | |
| "@operato/input:9.0.47" | |
| "@operato/input:9.0.48" | |
| "@operato/popup:9.0.47" | |
| "@operato/popup:9.0.48" | |
| "@operato/popup:9.0.49" | |
| "@operato/popup:9.0.50" | |
| "@operato/popup:9.0.51" | |
| "@operato/pull-to-refresh:9.0.35" | |
| "@operato/pull-to-refresh:9.0.43" | |
| "@operato/pull-to-refresh:9.0.44" | |
| "@operato/pull-to-refresh:9.0.45" | |
| "@operato/pull-to-refresh:9.0.46" | |
| "@operato/pull-to-refresh:9.0.47" | |
| "@operato/utils:9.0.47" | |
| "@operato/utils:9.0.48" | |
| "@operato/utils:9.0.49" | |
| "@operato/utils:9.0.50" | |
| "@operato/utils:9.0.51" | |
| "@rxap/ngx-bootstrap:19.0.3" | |
| "@rxap/ngx-bootstrap:19.0.4" | |
| "@teriyakibomb/ember-velcro:2.2.1" | |
| "@teselagen/bio-parsers:0.4.30" | |
| "@teselagen/file-utils:0.3.22" | |
| "@teselagen/ove:0.7.40" | |
| "@teselagen/react-table:6.10.20" | |
| "@teselagen/react-table:6.10.22" | |
| "@teselagen/sequence-utils:0.3.34" | |
| "@teselagen/ui:0.9.10" | |
| "@things-factory/attachment-base:9.0.42" | |
| "@things-factory/attachment-base:9.0.51" | |
| "@things-factory/attachment-base:9.0.52" | |
| "@things-factory/attachment-base:9.0.53" | |
| "@things-factory/attachment-base:9.0.54" | |
| "@things-factory/attachment-base:9.0.55" | |
| "@things-factory/auth-base:9.0.42" | |
| "@things-factory/email-base:9.0.55" | |
| "@things-factory/email-base:9.0.56" | |
| "@things-factory/email-base:9.0.57" | |
| "@things-factory/email-base:9.0.58" | |
| "@things-factory/email-base:9.0.59" | |
| "@things-factory/integration-base:9.0.42" | |
| "@things-factory/shell:9.0.42" | |
| "another-shai:1.0.1" | |
| "eslint-config-teselagen:6.1.8" | |
| "graphql-sequelize-teselagen:5.3.9" | |
| "ove-auto-annotate:0.0.10" | |
| "react-jsonschema-rxnt-extras:0.4.9" | |
| "tg-redbird:1.3.2" | |
| "ve-bamreader:0.2.7" | |
| "ve-editor:1.0.2" | |
| ) | |
| # Store the original directory | |
| original_dir=$(pwd) | |
| # Detect package manager from root lock file | |
| if [ -f "pnpm-lock.yaml" ]; then | |
| pkgmgr="pnpm" | |
| elif [ -f "package-lock.json" ]; then | |
| pkgmgr="npm" | |
| elif [ -f "yarn.lock" ]; then | |
| pkgmgr="yarn" | |
| else | |
| echo "No recognized lockfile found in root directory (pnpm, npm, or yarn)." | |
| echo "This script expects to be run from the monorepo root." | |
| exit 1 | |
| fi | |
| echo "Detected package manager: $pkgmgr" | |
| # Find all package.json files excluding node_modules | |
| echo "Finding sub-projects..." | |
| subprojects=() | |
| while IFS= read -r -d '' file; do | |
| # Get the directory containing the package.json | |
| dir=$(dirname "$file") | |
| # Skip if it's in node_modules or if it's the root package.json and there are other subprojects | |
| if [[ "$dir" != *"/node_modules"* && "$dir" != *"/node_modules" ]]; then | |
| subprojects+=("$dir") | |
| fi | |
| done < <(find . -name "package.json" -type f -print0) | |
| if [ ${#subprojects[@]} -eq 0 ]; then | |
| echo "No package.json files found." | |
| exit 1 | |
| fi | |
| echo "Found ${#subprojects[@]} sub-project(s):" | |
| for project in "${subprojects[@]}"; do | |
| echo " $project" | |
| done | |
| echo "" | |
| # Check for jq | |
| if command -v jq >/dev/null 2>&1; then | |
| use_jq=1 | |
| else | |
| use_jq=0 | |
| echo "β οΈ Warning: jq not found. Falling back to basic text parsing. Install jq for more reliable results." | |
| fi | |
| # Global counters | |
| total_affected=() | |
| total_checked=0 | |
| total_found=0 | |
| total_projects_affected=0 | |
| # Function to check dependencies for a single project | |
| check_project() { | |
| local project_dir="$1" | |
| local project_affected=() | |
| local project_found=0 | |
| echo "Checking project: $project_dir" | |
| # Change to project directory | |
| cd "$original_dir/$project_dir" || { | |
| echo " Error: Cannot access directory $project_dir" | |
| return 1 | |
| } | |
| # Get dependency list as JSON with proper error handling | |
| echo " Fetching dependency list..." | |
| local jsonout | |
| case $pkgmgr in | |
| "pnpm") | |
| jsonout=$(pnpm list --depth=Infinity --json 2>/dev/null) | |
| ;; | |
| "npm") | |
| jsonout=$(npm ls --all --json 2>/dev/null) | |
| ;; | |
| "yarn") | |
| jsonout=$(yarn list --json 2>/dev/null) | |
| ;; | |
| esac | |
| # Validate that we got valid output | |
| if [ $? -ne 0 ] || [ -z "$jsonout" ]; then | |
| echo " β οΈ Warning: Failed to get dependency list from $pkgmgr in $project_dir" | |
| echo " This might be normal if dependencies are not installed for this sub-project." | |
| return 0 | |
| fi | |
| # Basic JSON validation | |
| if ! echo "$jsonout" | jq empty >/dev/null 2>&1; then | |
| if [ $use_jq -eq 1 ]; then | |
| echo " β οΈ Warning: Package manager returned invalid JSON for $project_dir" | |
| return 0 | |
| fi | |
| # If no jq, we'll continue with text parsing but warn | |
| echo " β οΈ Warning: Cannot validate JSON format without jq" | |
| fi | |
| for entry in $compromised; do | |
| pkg=${entry%%:*} | |
| ver=${entry##*:} | |
| echo " Checking $pkg@$ver..." | |
| if [ $use_jq -eq 1 ]; then | |
| # Use jq to search for package and version - handle different JSON formats | |
| match="" | |
| case $pkgmgr in | |
| "npm") | |
| # FIXED: npm format with recursive search through nested dependencies | |
| match=$(echo "$jsonout" | jq -r --arg pkg "$pkg" --arg ver "$ver" ' | |
| def find_package($pkg; $ver): | |
| if .dependencies then | |
| (.dependencies | to_entries[] | | |
| if .key == $pkg and .value.version == $ver then "found" | |
| else (.value | find_package($pkg; $ver)) | |
| end | |
| ) | |
| else empty | |
| end; | |
| find_package($pkg; $ver)' 2>/dev/null | head -1) | |
| ;; | |
| "pnpm") | |
| # pnpm format: recursive search through nested dependencies | |
| match=$(echo "$jsonout" | jq -r --arg pkg "$pkg" --arg ver "$ver" ' | |
| def find_package($pkg; $ver): | |
| if .dependencies then | |
| (.dependencies | to_entries[] | | |
| if .key == $pkg and .value.version == $ver then "found" | |
| else (.value | find_package($pkg; $ver)) | |
| end | |
| ) | |
| else empty | |
| end; | |
| .[0] | find_package($pkg; $ver)' 2>/dev/null | head -1) | |
| ;; | |
| "yarn") | |
| # yarn format: {data: {trees: [{name: "package@version"}]}} | |
| match=$(echo "$jsonout" | jq -r --arg pkg "$pkg" --arg ver "$ver" ' | |
| .data.trees[]?.name // empty | | |
| select(. == ($pkg + "@" + $ver)) | | |
| "found"' 2>/dev/null | head -1) | |
| ;; | |
| esac | |
| if [[ "$match" == "found" ]]; then | |
| echo " π¨ Found $pkg@$ver: WARNING - Project is affected!" | |
| project_affected+=("$project_dir: $pkg@$ver") | |
| project_found=$((project_found+1)) | |
| else | |
| # Check if package is present but version does not match | |
| present="" | |
| case $pkgmgr in | |
| "npm") | |
| # FIXED: Also check for different versions recursively | |
| present=$(echo "$jsonout" | jq -r --arg pkg "$pkg" ' | |
| def find_package_version($pkg): | |
| if .dependencies then | |
| (.dependencies | to_entries[] | | |
| if .key == $pkg then .value.version | |
| else (.value | find_package_version($pkg)) | |
| end | |
| ) | |
| else empty | |
| end; | |
| find_package_version($pkg)' 2>/dev/null | head -1) | |
| ;; | |
| "pnpm") | |
| present=$(echo "$jsonout" | jq -r --arg pkg "$pkg" ' | |
| def find_package_version($pkg): | |
| if .dependencies then | |
| (.dependencies | to_entries[] | | |
| if .key == $pkg then .value.version | |
| else (.value | find_package_version($pkg)) | |
| end | |
| ) | |
| else empty | |
| end; | |
| .[0] | find_package_version($pkg)' 2>/dev/null | head -1) | |
| ;; | |
| "yarn") | |
| present=$(echo "$jsonout" | jq -r --arg pkg "$pkg" ' | |
| .data.trees[]?.name // empty | | |
| select(startswith($pkg + "@")) | | |
| split("@")[1]' 2>/dev/null | head -1) | |
| ;; | |
| esac | |
| if [[ -n "$present" && "$present" != "null" ]]; then | |
| echo " β Found $pkg@$present: Not affected (version does not match)" | |
| else | |
| echo " βͺ $pkg not found in dependencies." | |
| fi | |
| fi | |
| else | |
| # Fallback parsing without jq - handle different JSON formats | |
| found_version="" | |
| case $pkgmgr in | |
| "npm"|"pnpm") | |
| # npm/pnpm format: look for "packageName": {"version": "x.x.x"} | |
| # This fallback still has the original limitation for npm | |
| pkg_pattern="\"$pkg\"[[:space:]]*:[[:space:]]*{" | |
| if echo "$jsonout" | grep -q "$pkg_pattern"; then | |
| # Extract version from the package entry using more compatible awk | |
| found_version=$(echo "$jsonout" | awk -v pkg="$pkg" ' | |
| BEGIN { in_pkg = 0 } | |
| # Look for the package name | |
| $0 ~ "\"" pkg "\"[[:space:]]*:[[:space:]]*{" { | |
| in_pkg = 1 | |
| next | |
| } | |
| # When we are in the package block, look for version | |
| in_pkg && /"version"[[:space:]]*:[[:space:]]*"/ { | |
| # Use gsub and string manipulation instead of match with array | |
| line = $0 | |
| gsub(/.*"version"[[:space:]]*:[[:space:]]*"/, "", line) | |
| gsub(/".*/, "", line) | |
| if (line) { | |
| print line | |
| exit | |
| } | |
| } | |
| # End of package block | |
| in_pkg && /^[[:space:]]*}/ { | |
| in_pkg = 0 | |
| } | |
| ') | |
| fi | |
| ;; | |
| "yarn") | |
| # yarn format: look for "package@version" in name fields | |
| name_pattern="\"name\"[[:space:]]*:[[:space:]]*\"$pkg@" | |
| if echo "$jsonout" | grep -q "$name_pattern"; then | |
| found_version=$(echo "$jsonout" | grep "$name_pattern" | head -n1 | \ | |
| sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"'$pkg'@\([^"]*\)".*/\1/p') | |
| fi | |
| ;; | |
| esac | |
| if [[ "$found_version" == "$ver" ]]; then | |
| echo " π¨ Found $pkg@$ver: WARNING - Project is affected!" | |
| project_affected+=("$project_dir: $pkg@$ver") | |
| project_found=$((project_found+1)) | |
| elif [[ -n "$found_version" ]]; then | |
| echo " β Found $pkg@$found_version: Not affected (version does not match)" | |
| else | |
| echo " βͺ $pkg not found in dependencies." | |
| fi | |
| fi | |
| done | |
| # Update global counters | |
| if [ $project_found -gt 0 ]; then | |
| total_projects_affected=$((total_projects_affected+1)) | |
| total_affected+=("${project_affected[@]}") | |
| fi | |
| total_found=$((total_found+project_found)) | |
| if [ $project_found -gt 0 ]; then | |
| echo " π¨ Project summary: $project_found compromised package(s) found" | |
| else | |
| echo " β Project summary: No compromised packages found" | |
| fi | |
| echo "" | |
| # Return to original directory | |
| cd "$original_dir" | |
| } | |
| # Check each sub-project | |
| for project in "${subprojects[@]}"; do | |
| check_project "$project" | |
| total_checked=$((total_checked+1)) | |
| done | |
| echo "\nπ Overall Summary:" | |
| echo "Checked $total_checked sub-project(s)." | |
| echo "Projects affected: $total_projects_affected" | |
| echo "Total compromised packages found: $total_found" | |
| if [ $total_found -gt 0 ]; then | |
| echo "\nπ¨ Detailed findings:" | |
| for finding in "${total_affected[@]}"; do | |
| echo " $finding" | |
| done | |
| echo "\nβ οΈ Action required: Review and update the affected packages." | |
| exit 1 | |
| else | |
| echo "β No compromised packages found in any sub-project." | |
| exit 0 | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment