This is a supply chain attack that has impacted 199 unique npm packages spanning multiple maintainers. The malware campaign (part of the “Shai-Hulud” attack) has compromised npm packages in a worm-like manner.
The malware affects various packages from different maintainers. Some are public; others belong to popular vendors like CrowdStrike. Altogether, these packages have more than 2 billion downloads per week. Considering their high number of installations, the number of affected packages will only increase in the coming days.
How the attack works
The malicious versions leverage a function (NpmModule.updatePackage) that retrieves a package tarball. Then it alters its package.json, embedding a local script (bundle.js), rebuilding the archive, republishing it, and automating the trojanization of downstream packages.
Malware Analysis
The malware contains a bundle.js script that runs automatically when the package is installed. The script does the following tasks:
- Downloads and runs TruffleHog (a legitimate secret scanner)
- Scans hosts for tokens and cloud credentials
- Validates any discovered developer or CI tokens
- Injects unauthorized GitHub Actions workflows into repositories
- Exfiltrates sensitive data to a hardcoded webhook endpoint
The script combines local scanning with service-specific probing. It looks for environment variables such as GITHUB_TOKEN, NPM_TOKEN, AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY. It validates npm tokens and interacts with GitHub APIs when a token is available. It also attempts cloud metadata discovery that can leak short-lived credentials inside cloud build agents.
Now, let’s talk about the leading cause of the attack, “The Phishing Email”. The maintainer shared that he was compromised via a phishing email coming from su*****@***js.help:
Image Source: aikido
It’s interesting to note that the domain was registered on September 5th, 2025, a few days before the attack. After getting into the news, the maintainer tweeted that he was aware of being compromised and starting to clean up the compromised packages.
Image Source: aikido
Affected Packages and Versions
S. No | Package | Version |
1 | @ahmedhfarag/ngx-perfect-scrollbar | 20.0.20 |
2 | @ahmedhfarag/ngx-virtual-scroller | 4.0.4 |
3 | another-shai | 1.0.1 |
4 | @art-ws/common | 2.0.28 |
5 | @art-ws/config-eslint | 2.0.4, 2.0.5 |
6 | @art-ws/config-ts | 2.0.7, 2.0.8 |
7 | @art-ws/db-context | 2.0.24 |
8 | @art-ws/di-node | 2.0.13 |
9 | @art-ws/di | 2.0.28, 2.0.32 |
10 | @art-ws/eslint | 1.0.5, 1.0.6 |
11 | @art-ws/fastify-http-server | 2.0.24, 2.0.27 |
12 | @art-ws/http-server | 2.0.21, 2.0.25 |
13 | @art-ws/openapi | 0.1.12, 0.1.9 |
14 | @art-ws/package-base | 1.0.5, 1.0.6 |
15 | @art-ws/prettier | 1.0.5, 1.0.6 |
16 | @art-ws/slf | 2.0.15, 2.0.22 |
17 | @art-ws/ssl-info | 1.0.10, 1.0.9 |
18 | @art-ws/web-app | 1.0.3, 1.0.4 |
19 | @crowdstrike/commitlint | 8.1.1, 8.1.2 |
20 | @crowdstrike/falcon-shoelace | 0.4.1, 0.4.2 |
21 | @crowdstrike/foundry-js | 0.19.1, 0.19.2 |
22 | @crowdstrike/glide-core | 0.34.2, 0.34.3 |
23 | @crowdstrike/logscale-dashboard | 1.205.1, 1.205.2 |
24 | @crowdstrike/logscale-file-editor | 1.205.1, 1.205.2 |
25 | @crowdstrike/logscale-parser-edit | 1.205.1, 1.205.2 |
26 | @crowdstrike/logscale-search | 1.205.1, 1.205.2 |
27 | @crowdstrike/tailwind-toucan-base | 5.0.1, 5.0.2 |
28 | @ctrl/deluge | 7.2.1, 7.2.2 |
29 | @ctrl/golang-template | 1.4.2, 1.4.3 |
30 | @ctrl/magnet-link | 4.0.3, 4.0.4 |
31 | @ctrl/ngx-codemirror | 7.0.1, 7.0.2 |
32 | @ctrl/ngx-csv | 6.0.1, 6.0.2 |
33 | @ctrl/ngx-emoji-mart | 9.2.1, 9.2.2 |
34 | @ctrl/ngx-rightclick | 4.0.1, 4.0.2 |
35 | @ctrl/qbittorrent | 9.7.1, 9.7.2 |
36 | @ctrl/react-adsense | 2.0.1, 2.0.2 |
37 | @ctrl/shared-torrent | 6.3.1, 6.3.2 |
38 | @ctrl/tinycolor | 4.1.1, 4.1.2 |
39 | @ctrl/torrent-file | 4.1.1, 4.1.2 |
40 | @ctrl/transmission | 7.3.1 |
41 | @ctrl/ts-base32 | 4.0.1, 4.0.2 |
42 | @hestjs/core | 0.2.1 |
43 | @hestjs/cqrs | 0.1.6 |
44 | @hestjs/demo | 0.1.2 |
45 | @hestjs/eslint-config | 0.1.2 |
46 | @hestjs/logger | 0.1.6 |
47 | @hestjs/scalar | 0.1.7 |
48 | @hestjs/validation | 0.1.6 |
49 | @nativescript-community/arraybuffers | 1.1.6, 1.1.7, 1.1.8 |
50 | @nativescript-community/gesturehandler | 2.0.35 |
51 | @nativescript-community/perms | 3.0.5, 3.0.6, 3.0.7, 3.0.8 |
52 | @nativescript-community/sentry | 4.6.43 |
53 | @nativescript-community/sqlite | 3.5.2, 3.5.3, 3.5.4, 3.5.5 |
54 | @nativescript-community/text | 1.6.9, 1.6.10, 1.6.11, 1.6.12, 1.6.13 |
55 | @nativescript-community/typeorm | 0.2.30, 0.2.31, 0.2.32, 0.2.33 |
56 | @nativescript-community/ui-collectionview | 6.0.6 |
57 | @nativescript-community/ui-document-picker | 1.1.27, 1.1.28 |
58 | @nativescript-community/ui-drawer | 0.1.30 |
59 | @nativescript-community/ui-image | 4.5.6 |
60 | @nativescript-community/ui-label | 1.3.35, 1.3.36, 1.3.37 |
61 | @nativescript-community/ui-material-bottom-navigation | 7.2.72, 7.2.73, 7.2.74, 7.2.75 |
62 | @nativescript-community/ui-material-bottomsheet | 7.2.72 |
63 | @nativescript-community/ui-material-core-tabs | 7.2.72, 7.2.73, 7.2.74, 7.2.75, 7.2.76 |
64 | @nativescript-community/ui-material-core | 7.2.72, 7.2.73, 7.2.74, 7.2.75, 7.2.76 |
65 | @nativescript-community/ui-material-ripple | 7.2.72, 7.2.73, 7.2.74, 7.2.75 |
66 | @nativescript-community/ui-material-tabs | 7.2.72, 7.2.73, 7.2.74, 7.2.75 |
67 | @nativescript-community/ui-pager | 14.1.36, 14.1.37, 14.1.38 |
68 | @nativescript-community/ui-pulltorefresh | 2.5.4, 2.5.5, 2.5.6, 2.5.7 |
69 | @nexe/config-manager | 0.1.1 |
70 | @nexe/eslint-config | 0.1.1 |
71 | @nexe/logger | 0.1.3 |
72 | @nstudio/angular | 20.0.4, 20.0.5, 20.0.6 |
73 | @nstudio/focus | 20.0.4, 20.0.5, 20.0.6 |
74 | @nstudio/nativescript-checkbox | 2.0.6, 2.0.7, 2.0.8, 2.0.9 |
75 | @nstudio/nativescript-loading-indicator | 5.0.1, 5.0.2, 5.0.3, 5.0.4 |
76 | @nstudio/ui-collectionview | 5.1.11, 5.1.12, 5.1.13, 5.1.14 |
77 | @nstudio/web-angular | 20.0.4 |
78 | @nstudio/web | 20.0.4 |
79 | @nstudio/xplat-utils | 20.0.5, 20.0.6, 20.0.7 |
80 | @nstudio/xplat | 20.0.5, 20.0.6, 20.0.7 |
81 | @operato/board | 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46, 9.0.47, 9.0.48, 9.0.49, 9.0.50, 9.0.51 |
82 | @operato/data-grist | 9.0.29, 9.0.35, 9.0.36, 9.0.37 |
83 | @operato/graphql | 9.0.22, 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46 |
84 | @operato/headroom | 9.0.2, 9.0.35, 9.0.36, 9.0.37 |
85 | @operato/help | 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46 |
86 | @operato/i18n | 9.0.35, 9.0.36, 9.0.37 |
87 | @operato/input | 9.0.27, 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46, 9.0.47, 9.0.48 |
88 | @operato/layout | 9.0.35, 9.0.36, 9.0.37 |
89 | @operato/popup | 9.0.22, 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46, 9.0.49 |
90 | @operato/pull-to-refresh | 9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42 |
91 | @operato/shell | 9.0.22, 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39 |
92 | @operato/styles | 9.0.2, 9.0.35, 9.0.36, 9.0.37 |
93 | @operato/utils | 9.0.22, 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46, 9.0.47, 9.0.49, 9.0.50, 9.0.51 |
94 | @teselagen/bio-parsers | 0.4.30 |
95 | @teselagen/bounce-loader | 0.3.16, 0.3.17 |
96 | @teselagen/file-utils | 0.3.22 |
97 | @teselagen/liquibase-tools | 0.4.1 |
98 | @teselagen/ove | 0.7.40 |
99 | @teselagen/range-utils | 0.3.14, 0.3.15 |
100 | @teselagen/react-list | 0.8.19, 0.8.20 |
101 | @teselagen/react-table | 6.10.19, 6.10.20, 6.10.22 |
102 | @teselagen/sequence-utils | 0.3.34 |
103 | @teselagen/ui | 0.9.10 |
104 | @thangved/callback-window | 1.1.4 |
105 | @things-factory/attachment-base | 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46, 9.0.47, 9.0.48, 9.0.49, 9.0.50, 9.0.51, 9.0.52, 9.0.53, 9.0.54, 9.0.55, 9.0.56, 9.0.57, 9.0.58, 9.0.59 |
106 | @things-factory/auth-base | 9.0.42, 9.0.43, 9.0.44, 9.0.45 |
107 | @things-factory/email-base | 9.0.42, 9.0.43, 9.0.44, 9.0.45, 9.0.46, 9.0.47, 9.0.48, 9.0.49, 9.0.50, 9.0.51, 9.0.52, 9.0.53, 9.0.54, 9.0.55, 9.0.56, 9.0.57, 9.0.58, 9.0.59 |
108 | @things-factory/env | 9.0.42, 9.0.43, 9.0.44, 9.0.45 |
109 | @things-factory/integration-base | 9.0.42, 9.0.43, 9.0.44, 9.0.45 |
110 | @things-factory/integration-marketplace | 9.0.43, 9.0.44, 9.0.45 |
111 | @things-factory/shell | 9.0.42, 9.0.43, 9.0.44, 9.0.45 |
112 | @tnf-dev/api | 1.0.8 |
113 | @tnf-dev/core | 1.0.8 |
114 | @tnf-dev/js | 1.0.8 |
115 | @tnf-dev/mui | 1.0.8 |
116 | @tnf-dev/react | 1.0.8 |
117 | @ui-ux-gang/devextreme-angular-rpk | 24.1.7 |
118 | @yoobic/design-system | 6.5.17 |
119 | @yoobic/jpeg-camera-es6 | 1.0.13 |
120 | @yoobic/yobi | 8.7.53 |
121 | airchief | 0.3.1 |
122 | airpilot | 0.8.8 |
123 | angulartics2 | 14.1.1, 14.1.2 |
124 | browser-webdriver-downloader | 3.0.8 |
125 | capacitor-notificationhandler | 0.0.2, 0.0.3 |
126 | capacitor-plugin-healthapp | 0.0.2, 0.0.3 |
127 | capacitor-plugin-ihealth | 1.1.8, 1.1.9 |
128 | capacitor-plugin-vonage | 1.0.2, 1.0.3 |
129 | capacitorandroidpermissions | 0.0.4, 0.0.5 |
130 | config-cordova | 0.8.5 |
131 | cordova-plugin-voxeet2 | 1.0.24 |
132 | cordova-voxeet | 1.0.32 |
133 | create-hest-app | 0.1.9 |
134 | db-evo | 1.1.4, 1.1.5 |
135 | devextreme-angular-rpk | 21.2.8 |
136 | ember-browser-services | 5.0.2, 5.0.3 |
137 | ember-headless-form-yup | 1.0.1 |
138 | ember-headless-form | 1.1.2, 1.1.3 |
139 | ember-headless-table | 2.1.5, 2.1.6 |
140 | ember-url-hash-polyfill | 1.0.12, 1.0.13 |
141 | ember-velcro | 2.2.1, 2.2.2 |
142 | encounter-playground | 0.0.2, 0.0.3, 0.0.4, 0.0.5 |
143 | eslint-config-crowdstrike-node | 4.0.3, 4.0.4 |
144 | eslint-config-crowdstrike | 11.0.2, 11.0.3 |
145 | slint-config-teselagen | 6.1.7 |
146 | eslint-config-teselagen | 6.1.8 |
147 | globalize-rpk | 1.7.4 |
148 | graphql-sequelize-teselagen | 5.3.8, 5.3.9 |
149 | html-to-base64-image | 1.0.2 |
150 | json-rules-engine-simplified | 0.2.1, 0.2.4 |
151 | jumpgate | 0.0.2 |
152 | koa2-swagger-ui | 5.11.1, 5.11.2 |
153 | mcfly-semantic-release | 1.3.1 |
154 | mcp-knowledge-base | 0.0.2 |
155 | mcp-knowledge-graph | 1.2.1 |
156 | mobioffice-cli | 1.0.3 |
157 | monorepo-next | 13.0.1, 13.0.2 |
158 | mstate-angular | 0.4.4 |
159 | mstate-cli | 0.4.7 |
160 | mstate-dev-react | 1.1.1 |
161 | mstate-react | 1.6.5 |
162 | ng2-file-upload | 7.0.2, 7.0.3, 8.0.1, 8.0.2, 8.0.3, 9.0.1 |
163 | ngx-bootstrap | 18.1.4, 19.0.3, 19.0.4, 20.0.3, 20.0.4, 20.0.5 |
164 | ngx-color | 10.0.1, 10.0.2 |
165 | ngx-toastr | 19.0.1, 19.0.2 |
166 | ngx-trend | 8.0.1 |
167 | ngx-ws | 1.1.5, 1.1.6 |
168 | oradm-to-gql | 35.0.14, 35.0.15 |
169 | oradm-to-sqlz | 1.1.2 |
170 | ove-auto-annotate | 0.0.9, 0.0.10 |
171 | pm2-gelf-json | 1.0.4, 1.0.5 |
172 | printjs-rpk | 1.6.1 |
173 | react-complaint-image | 0.0.32, 0.0.35 |
174 | react-jsonschema-form-conditionals | 0.3.18, 0.3.21 |
175 | react-jsonschema-form-extras | 1.0.4 |
176 | react-jsonschema-rxnt-extras | 0.4.9 |
177 | remark-preset-lint-crowdstrike | 4.0.1, 4.0.2 |
178 | rxnt-authentication | 0.0.3, 0.0.4, 0.0.5, 0.0.6 |
179 | rxnt-healthchecks-nestjs | 1.0.2, 1.0.3, 1.0.4, 1.0.5 |
180 | rxnt-kue | 1.0.4, 1.0.5, 1.0.6, 1.0.7 |
181 | swc-plugin-component-annotate | 1.9.1, 1.9.2 |
182 | tbssnch | 1.0.2 |
183 | teselagen-interval-tree | 1.1.2 |
184 | tg-client-query-builder | 2.14.4, 2.14.5 |
185 | tg-redbird | 1.3.1, 1.3.2 |
186 | tg-seq-gen | 1.0.9, 1.0.10 |
187 | thangved-react-grid | 1.0.3 |
188 | ts-gaussian | 3.0.5, 3.0.6 |
189 | ts-imports | 1.0.1, 1.0.2 |
190 | tvi-cli | 0.1.5 |
191 | ve-bamreader | 0.2.6, 0.2.7 |
192 | ve-editor | 1.0.1, 1.0.2 |
193 | verror-extra | 6.0.1 |
194 | voip-callkit | 1.0.2, 1.0.3 |
195 | wdio-web-reporter | 0.1.3 |
196 | yargs-help-output | 5.0.3 |
197 | yoo-styles | 6.0.326 |
198 | @rxap/ngx-bootstrap | 19.0.3, 19.0.4 |
199 | eslint-config-teselagen | 6.1.7 |
Mitigation
There are no patches as of now. Customers are advised to uninstall the affected packages.
Qualys Detection
Qualys customers can scan their devices with QIDs 5005322 and 5005466 to detect vulnerable assets.
Note: QIDs 5005322 and 5005466 are available via SwCA, which needs to be enabled.
Please continue to follow Qualys Threat Protection for more coverage of the latest vulnerabilities.
References
https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised
https://socket.dev/blog/tinycolor-supply-chain-attack-affects-40-packages
https://www.aikido.dev/blog/npm-debug-and-chalk-packages-compromised
https://socket.dev/blog/ongoing-supply-chain-attack-targets-crowdstrike-npm-packages