The NPM Supply Chain Attack: Understanding 'Shai-Hulud' and How to Protect Yourself

    The NPM Supply Chain Attack: Understanding 'Shai-Hulud' and How to Protect Yourself

    21/09/2025

    You know that feeling when you npm install and suddenly your project has 1,000+ dependencies even though you didn't add them or your package.json doesn't have them? Yeah, that's not just a performance concern anymore. A sophisticated supply chain attack called "Shai-Hulud" has been wreaking havoc in the npm ecosystem, and it's a wake-up call for all of us to be more careful with our dependencies.

    This isn't just another security scare - it's a real threat that's already compromised hundreds of packages and could be sitting in your node_modules right now. Let me walk you through what's happening and, more importantly, how to protect your projects now and in the future.

    How the 'Shai-Hulud' Attack Works

    Think of this attack as a digital virus that spreads through the npm ecosystem. It's clever, automated, and uses legitimate tools against us. Here's the playbook:

    1. The Phishing Hook

    It all starts with a classic move - phishing emails that look like legitimate npm security alerts. Maintainers of popular packages get tricked into giving up their credentials. You know those emails that make your heart skip a beat? Yeah, those.

    2. Package Compromise

    Once they have access, attackers inject malicious code into the packages. The attack started with @ctrl/tinycolor (a popular color utility), but it's spread like wildfire since then.

    3. The Worm Spreads

    One of the most alarming aspects of this attack is its worm-like behavior. Instead of infecting just a single package, the malware is engineered to automatically re-publish itself into every npm package owned by the compromised maintainer. Here’s how it works: it downloads a target package tarball from the npm registry, bumps the patch version in package.json, and adds a malicious lifecycle hook (like postinstall). The worm copies its own payload into the package (often as bundle.js), then re-publishes the trojanized package back to npm using the maintainer’s credentials. This cycle repeats for every package the maintainer controls, turning each one into a new infection vector. As soon as someone installs any of these packages, the worm executes and continues to propagate—no manual intervention required. Once a single account is compromised, the malware leverages the maintainer’s publishing rights to rapidly and automatically spread across the ecosystem.

    4. Secret Stealing with TruffleHog

    The malware uses TruffleHog (ironically, a legitimate security tool) to:

    • Scan your entire machine for secrets (API keys, tokens, credentials)
    • Validate what it finds to make sure the credentials actually work
    • Send everything to attacker-controlled endpoints (often webhook.site)

    5. GitHub Actions Hijacking

    If it finds GitHub tokens, it goes nuclear:

    • Lists all your repositories (including private ones)
    • Injects malicious GitHub Actions workflows
    • Automatically exfiltrates your code and secrets

    The workflow names are usually something like shai-hulud-workflow.yml - pretty on the nose, right?

    Why This Matters to You

    Remember when you thought "it's just a small utility package, what could go wrong?" Well, this is what could go wrong. Here's the real impact:

    Your CI/CD Pipeline Could Be Compromised

    • Malware runs in your build environment
    • Steals your deployment keys and secrets
    • Could deploy malicious code to production

    Your Source Code Could Be Stolen

    • Private repositories get cloned and made public
    • Intellectual property walks out the door
    • Competitors get access to your codebase

    Your Infrastructure Could Be Hijacked

    • AWS keys, database credentials, API tokens - all gone
    • Attackers could spin up expensive resources on your dime
    • Your production systems could be compromised

    The Domino Effect One compromised package can infect hundreds of others. Your package.json might look innocent, but transitive dependencies could be carrying malware. You can find these in your package-lock.json or yarn.lock file.

    Quick Check: Are You Infected?

    Don't panic, but let's check if you're already compromised. Here's your 5-minute security audit:

    1. Check Your Dependencies

    Check if you have any compromised packages like @ctrl/tinycolor (4.1.1, 4.1.2).

    Refer to the "Impacted packages" table at the end of this blog post to see the full list of affected packages and versions. If you find any of these in your dependencies, take immediate action to remove or update them!

    2. Hunt for Malicious Workflows

    # Check your GitHub Actions find .github/workflows -name "*.yml" -exec grep -l "shai-hulud\|webhook.site" {} \; # Look for suspicious workflow names ls -la .github/workflows/ | grep -v "main\|test\|deploy"

    3. Monitor Your Network

    Check your logs for connections to:

    • webhook.site
    • ngrok.io
    • Other suspicious domains
    # On macOS/Linux, check recent network connections netstat -an | grep -E "(webhook\.site|ngrok\.io)"

    How to Actually Protect Yourself for Future (The Developer's Guide)

    Alright, enough doom and gloom. Here's are some of the things you can do to protect yourself:

    1. Minimize Your Attack Surface

    Try to avoid installing everything under the sun. If you don't need a package or can write it yourself in say 20 lines, then don't install it. Try to get an idea of what package you are installing and if it is a popular package, then check if it is well maintained.

    2. Don't do auto-upgrade patch versions

    We usually enable auto-upgrade patch versions like below in package.json. But it is better to avoid this and use the exact version. Otherwise if there is a vulneable patch, your app will also start using that.

    { "dependencies": { "lodash": "~4.17.21", // Will automatically upgrade to 4.17.22, 4.17.23 etc "react": "~18.2.0" // Will automatically upgrade to 18.2.1, 18.2.2 etc } }

    3. Implement Static Security Testing

    Implement static security checks like npm audit, Snyk etc to check for vulnerabilities in your dependencies in your CI/CD pipeline.

    4. Dynamic Security Testing

    Implement automated and manual security checks for your application if possible before deploying to production.

    5. Vet Dependencies Before Adding

    Before installing anything new:

    # Check package health npm view package-name npm view package-name time npm view package-name maintainers # Look for red flags: # - Last updated > 2 years ago # - Few downloads # - No recent commits

    6. Team Education

    Train your team on dependency security and how to identify malicious packages.

    Make security everyone's job:

    • Regular security training sessions
    • Code review checklists that include dependency reviews
    • "Security champion" program
    • Phishing simulation exercises

    Conclusion

    The open-source ecosystem is amazing, but it's built on trust. That trust is being exploited. We can't stop using packages, but we can be smarter about how we use them.

    Remember: every dependency is a potential attack vector. Treat them like you would treat any other security risk - with respect, caution, and proper controls.

    Stay safe out there, and happy coding! 🚀

    Impacted Packages

    Here's the complete list of packages known to be compromised in the 'Shai-Hulud' attack. Check your package-lock.json and node_modules immediately if you're using any of these change the version to the latest one or one before the compromised version.

    PackageVersions
    @ahmedhfarag/ngx-perfect-scrollbar20.0.20
    @ahmedhfarag/ngx-virtual-scroller4.0.4
    @art-ws/common2.0.28
    @art-ws/config-eslint2.0.4, 2.0.5
    @art-ws/config-ts2.0.7, 2.0.8
    @art-ws/db-context2.0.24
    @art-ws/di2.0.28, 2.0.32
    @art-ws/di-node2.0.13
    @art-ws/eslint1.0.5, 1.0.6
    @art-ws/fastify-http-server2.0.24, 2.0.27
    @art-ws/http-server2.0.21, 2.0.25
    @art-ws/openapi0.1.9, 0.1.12
    @art-ws/package-base1.0.5, 1.0.6
    @art-ws/prettier1.0.5, 1.0.6
    @art-ws/slf2.0.15, 2.0.22
    @art-ws/ssl-info1.0.9, 1.0.10
    @art-ws/web-app1.0.3, 1.0.4
    @crowdstrike/commitlint8.1.1, 8.1.2
    @crowdstrike/falcon-shoelace0.4.1, 0.4.2
    @crowdstrike/foundry-js0.19.1, 0.19.2
    @crowdstrike/glide-core0.34.2, 0.34.3
    @crowdstrike/logscale-dashboard1.205.1, 1.205.2
    @crowdstrike/logscale-file-editor1.205.1, 1.205.2
    @crowdstrike/logscale-parser-edit1.205.1, 1.205.2
    @crowdstrike/logscale-search1.205.1, 1.205.2
    @crowdstrike/tailwind-toucan-base5.0.1, 5.0.2
    @ctrl/deluge7.2.1, 7.2.2
    @ctrl/golang-template1.4.2, 1.4.3
    @ctrl/magnet-link4.0.3, 4.0.4
    @ctrl/ngx-codemirror7.0.1, 7.0.2
    @ctrl/ngx-csv6.0.1, 6.0.2
    @ctrl/ngx-emoji-mart9.2.1, 9.2.2
    @ctrl/ngx-rightclick4.0.1, 4.0.2
    @ctrl/qbittorrent9.7.1, 9.7.2
    @ctrl/react-adsense2.0.1, 2.0.2
    @ctrl/shared-torrent6.3.1, 6.3.2
    @ctrl/tinycolor4.1.1, 4.1.2
    @ctrl/torrent-file4.1.1, 4.1.2
    @ctrl/transmission7.3.1
    @ctrl/ts-base324.0.1, 4.0.2
    @hestjs/core0.2.1
    @hestjs/cqrs0.1.6
    @hestjs/demo0.1.2
    @hestjs/eslint-config0.1.2
    @hestjs/logger0.1.6
    @hestjs/scalar0.1.7
    @hestjs/validation0.1.6
    @nativescript-community/arraybuffers1.1.6, 1.1.7, 1.1.8
    @nativescript-community/gesturehandler2.0.35
    @nativescript-community/perms3.0.5, 3.0.6, 3.0.7, 3.0.8
    @nativescript-community/sqlite3.5.2, 3.5.3, 3.5.4, 3.5.5
    @nativescript-community/text1.6.9, 1.6.10, 1.6.11, 1.6.12
    @nativescript-community/typeorm0.2.30, 0.2.31, 0.2.32, 0.2.33
    @nativescript-community/ui-collectionview6.0.6
    @nativescript-community/ui-document-picker1.1.27, 1.1.28
    @nativescript-community/ui-drawer0.1.30
    @nativescript-community/ui-image4.5.6
    @nativescript-community/ui-label1.3.35, 1.3.36, 1.3.37
    @nativescript-community/ui-material-bottom-navigation7.2.72, 7.2.73, 7.2.74, 7.2.75
    @nativescript-community/ui-material-bottomsheet7.2.72
    @nativescript-community/ui-material-core7.2.72, 7.2.73, 7.2.74, 7.2.75
    @nativescript-community/ui-material-core-tabs7.2.72, 7.2.73, 7.2.74, 7.2.75
    @nativescript-community/ui-material-ripple7.2.72, 7.2.73, 7.2.74, 7.2.75
    @nativescript-community/ui-material-tabs7.2.72, 7.2.73, 7.2.74, 7.2.75
    @nativescript-community/ui-pager14.1.36, 14.1.37, 14.1.38
    @nativescript-community/ui-pulltorefresh2.5.4, 2.5.5, 2.5.6, 2.5.7
    @nexe/config-manager0.1.1
    @nexe/eslint-config0.1.1
    @nexe/logger0.1.3
    @nstudio/angular20.0.4, 20.0.5, 20.0.6
    @nstudio/focus20.0.4, 20.0.5, 20.0.6
    @nstudio/nativescript-checkbox2.0.6, 2.0.7, 2.0.8, 2.0.9
    @nstudio/nativescript-loading-indicator5.0.1, 5.0.2, 5.0.3, 5.0.4
    @nstudio/ui-collectionview5.1.11, 5.1.12, 5.1.13, 5.1.14
    @nstudio/web20.0.4
    @nstudio/web-angular20.0.4
    @nstudio/xplat20.0.5, 20.0.6, 20.0.7
    @nstudio/xplat-utils20.0.5, 20.0.6, 20.0.7
    @operato/board9.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
    @operato/data-grist9.0.29, 9.0.35, 9.0.36, 9.0.37
    @operato/graphql9.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
    @operato/headroom9.0.2, 9.0.35, 9.0.36, 9.0.37
    @operato/help9.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
    @operato/i18n9.0.35, 9.0.36, 9.0.37
    @operato/input9.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
    @operato/layout9.0.35, 9.0.36, 9.0.37
    @operato/popup9.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
    @operato/pull-to-refresh9.0.36, 9.0.37, 9.0.38, 9.0.39, 9.0.40, 9.0.41, 9.0.42
    @operato/shell9.0.22, 9.0.35, 9.0.36, 9.0.37, 9.0.38, 9.0.39
    @operato/styles9.0.2, 9.0.35, 9.0.36, 9.0.37
    @operato/utils9.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
    @teselagen/bounce-loader0.3.16, 0.3.17
    @teselagen/liquibase-tools0.4.1
    @teselagen/range-utils0.3.14, 0.3.15
    @teselagen/react-list0.8.19, 0.8.20
    @teselagen/react-table6.10.19
    @thangved/callback-window1.1.4
    @things-factory/attachment-base9.0.43, 9.0.44, 9.0.45, 9.0.46, 9.0.47, 9.0.48, 9.0.49, 9.0.50
    @things-factory/auth-base9.0.43, 9.0.44, 9.0.45
    @things-factory/email-base9.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
    @things-factory/env9.0.42, 9.0.43, 9.0.44, 9.0.45
    @things-factory/integration-base9.0.43, 9.0.44, 9.0.45
    @things-factory/integration-marketplace9.0.43, 9.0.44, 9.0.45
    @things-factory/shell9.0.43, 9.0.44, 9.0.45
    @tnf-dev/api1.0.8
    @tnf-dev/core1.0.8
    @tnf-dev/js1.0.8
    @tnf-dev/mui1.0.8
    @tnf-dev/react1.0.8
    @ui-ux-gang/devextreme-angular-rpk24.1.7
    @yoobic/design-system6.5.17
    @yoobic/jpeg-camera-es61.0.13
    @yoobic/yobi8.7.53
    airchief0.3.1
    airpilot0.8.8
    angulartics214.1.1, 14.1.2
    browser-webdriver-downloader3.0.8
    capacitor-notificationhandler0.0.2, 0.0.3
    capacitor-plugin-healthapp0.0.2, 0.0.3
    capacitor-plugin-ihealth1.1.8, 1.1.9
    capacitor-plugin-vonage1.0.2, 1.0.3
    capacitorandroidpermissions0.0.4, 0.0.5
    config-cordova0.8.5
    cordova-plugin-voxeet21.0.24
    cordova-voxeet1.0.32
    create-hest-app0.1.9
    db-evo1.1.4, 1.1.5
    devextreme-angular-rpk21.2.8
    ember-browser-services5.0.2, 5.0.3
    ember-headless-form1.1.2, 1.1.3
    ember-headless-form-yup1.0.1
    ember-headless-table2.1.5, 2.1.6
    ember-url-hash-polyfill1.0.12, 1.0.13
    ember-velcro2.2.1, 2.2.2
    encounter-playground0.0.2, 0.0.3, 0.0.4, 0.0.5
    eslint-config-crowdstrike11.0.2, 11.0.3
    eslint-config-crowdstrike-node4.0.3, 4.0.4
    eslint-config-teselagen6.1.7
    globalize-rpk1.7.4
    graphql-sequelize-teselagen5.3.8
    html-to-base64-image1.0.2
    json-rules-engine-simplified0.2.1
    jumpgate0.0.2
    koa2-swagger-ui5.11.1, 5.11.2
    mcfly-semantic-release1.3.1
    mcp-knowledge-base0.0.2
    mcp-knowledge-graph1.2.1
    mobioffice-cli1.0.3
    monorepo-next13.0.1, 13.0.2
    mstate-angular0.4.4
    mstate-cli0.4.7
    mstate-dev-react1.1.1
    mstate-react1.6.5
    ng2-file-upload7.0.2, 7.0.3, 8.0.1, 8.0.2, 8.0.3, 9.0.1
    ngx-bootstrap18.1.4, 19.0.3, 19.0.4, 20.0.3, 20.0.4, 20.0.5
    ngx-color10.0.1, 10.0.2
    ngx-toastr19.0.1, 19.0.2
    ngx-trend8.0.1
    ngx-ws1.1.5, 1.1.6
    oradm-to-gql35.0.14, 35.0.15
    oradm-to-sqlz1.1.2
    ove-auto-annotate0.0.9
    pm2-gelf-json1.0.4, 1.0.5
    printjs-rpk1.6.1
    react-complaint-image0.0.32
    react-jsonschema-form-conditionals0.3.18
    remark-preset-lint-crowdstrike4.0.1, 4.0.2
    rxnt-authentication0.0.3, 0.0.4, 0.0.5, 0.0.6
    rxnt-healthchecks-nestjs1.0.2, 1.0.3, 1.0.4, 1.0.5
    rxnt-kue1.0.4, 1.0.5, 1.0.6, 1.0.7
    swc-plugin-component-annotate1.9.1, 1.9.2
    tbssnch1.0.2
    teselagen-interval-tree1.1.2
    tg-client-query-builder2.14.4, 2.14.5
    tg-redbird1.3.1
    tg-seq-gen1.0.9, 1.0.10
    thangved-react-grid1.0.3
    ts-gaussian3.0.5, 3.0.6
    ts-imports1.0.1, 1.0.2
    tvi-cli0.1.5
    ve-bamreader0.2.6
    ve-editor1.0.1
    verror-extra6.0.1
    voip-callkit1.0.2, 1.0.3
    wdio-web-reporter0.1.3
    yargs-help-output5.0.3
    yoo-styles6.0.326

    To stay updated with the latest updates in Software Engineering, follow us on LinkedIn and Medium.

    Summarise

    Transform Your Learning

    Get instant AI-powered summaries of YouTube videos and websites. Save time while enhancing your learning experience.

    Instant video summaries
    Smart insights extraction
    Channel tracking