Join our Newsletter — 33% off our NHI Course
Home› Guides› Malicious Package and Dependency Confusion Guide
Guide Software & AI Supply Chain

Malicious Package and Dependency Confusion Guide

← All guides
By Lalit Choda, NHI Mgmt Group Updated 17 October 2026 14 min read
On this page

Every npm install or pip install runs code chosen by a public registry, and attackers have learned to make that choice for you. Typosquats, dependency confusion, hijacked maintainer accounts and self-spreading worms such as Shai-Hulud exploit the same gap: package managers trust names, versions and install scripts far more than they should. This guide explains each technique at a defensive level, the registry, resolver and install-time settings that stop them, how to detect a bad package that got through, and how to respond when a laptop or CI runner has executed one. It is written for application security, platform and build teams, and the security operations staff who own the response.

Key takeaways

  • Control where names resolve. Dependency confusion needs a resolver that can see an internal and a public name at once; scoped packages, a single internal index and reserved public names close it.
  • Install time is execution time. npm lifecycle scripts and Python source builds run with the user's or runner's privileges, so block them by default and allow only the packages that need them.
  • Slow down new versions. A minimum release age of a few days, plus committed lockfiles with integrity hashes, keeps freshly hijacked releases out of your builds while they are being found.
  • Provenance proves origin, not innocence. Trusted publishing and Sigstore-backed attestations make account takeover harder and tampering visible, but a legitimate pipeline can still publish malicious code.
  • Every malicious install is a credential incident. Modern payloads steal npm, GitHub, cloud and SSH credentials first, so response means rotating the non-human identities the machine could reach.

How malicious packages reach your builds

MITRE ATT&CK tracks this as T1195.001 Compromise Software Dependencies and Development Tools (Initial Access). The techniques fall into two families: tricking you into installing the wrong package, and poisoning the right one.

TechniqueHow it worksExamplePrimary control
Typosquatting and brandjackingA name one keystroke or separator away from a popular package, often with a copied READMERoutine on npm and PyPIProxy policy or allowlist; name similarity checks
Dependency confusionYour internal package name published publicly with a higher version; a resolver that sees both picks the public oneBirsan 2021; PyTorch torchtriton 2022Scopes, single internal index, reserved names
SlopsquattingAttackers register package names that AI coding assistants invent205,474 unique hallucinated names across 16 models (Spracklen et al.)Add dependencies only through a proxy that rejects unknown or brand-new names
StarjackingA package links to a popular, unrelated repository so the registry shows borrowed starsNon-unique repository links on 3.03% of PyPI and 7.23% of npm packages (Checkmarx, 2022)Ignore popularity metrics; check the repository builds the package
Maintainer account takeoverPhished, reused or leaked publishing credentials push a new version of a trusted packageua-parser-js 2021, axios 2026Trusted publishing, phishing-resistant MFA, cooldowns
Malicious install scriptsA preinstall or postinstall hook or Python build step runs the payload before any code is importedeslint-scope 2018Scripts off by default with an allowlist; wheels only
Self-propagating wormsStolen publishing tokens are used to republish every package the victim can reachShai-Hulud 2025, Mini Shai-Hulud and ChainDrop 2026Short-lived publishing credentials, cooldowns, fast revocation

CISA's September 2025 alert describes Shai-Hulud scanning environments for GitHub personal access tokens and AWS, Google Cloud and Azure keys, publishing them to a public GitHub repository named "Shai-Hulud", then logging into npm as the victim to inject code into other packages and publish them. GitHub removed more than 500 compromised packages in that wave. See the Shai-Hulud first wave and the later Shai-Hulud campaign that reached 25,000+ GitHub repositories.

Dependency confusion: why it works and how to close it

Alex Birsan's February 2021 research showed that when a package exists on both an internal and a public index, pip run with --extra-index-url installs whichever has the higher version. He found the weakness in more than 35 organisations across npm, PyPI and RubyGems, including Apple, Microsoft, PayPal and Shopify. Internal names leaked through manifests, JavaScript bundles and public repositories, so assume yours are known.

In the PyTorch torchtriton attack, PyTorch's advisory explains that "Since the PyPI index takes precedence", a malicious torchtriton on PyPI replaced the nightly index copy between 25 and 30 December 2022. Its binary read environment variables, $HOME/.ssh/*, .gitconfig and the first 1,000 files in the home directory and exfiltrated them over DNS. PyTorch renamed the dependency pytorch-triton and registered a placeholder on PyPI.

  • npm: publish internal packages under an organisation scope (@yourorg/name), map the scope to the internal registry in a committed .npmrc (@yourorg:registry=...) and register the scope publicly so nobody else owns it.
  • Python: never mix an internal index with --extra-index-url pointing at PyPI. Use one --index-url for an internal proxy that serves internal names first and refuses public lookups for them.
  • NuGet, Maven and Gradle: reserve your package ID prefix on nuget.org, use group IDs under a domain you own, and resolve through one repository manager with upstream sources rather than several feeds side by side.
  • Everywhere: publish placeholders for internal names that must stay unscoped, and alert when a public package appears matching an internal name.

Registry architecture: proxies, namespaces and allowlists

The most effective structural control is that builds and developer machines resolve packages from one internal registry, never directly from the internet. That repository manager becomes where you enforce policy, cache known-good versions and see what was installed where.

ModelBehaviourStrengthTrade-off
Open proxyProxies and caches any public package on demandSingle choke point, logs, cache for incident lookupLets a new malicious package through on first request
Proxy with policy (dependency firewall)Adds blocking rules: malicious package feeds, minimum age, install script presence, name similarityStops most opportunistic attacks with little developer frictionNeeds a maintained block feed and an exception process
Curated allowlistOnly approved packages and versions are availableStrongest against typosquats and slopsquatsReview queue becomes a bottleneck and invites workarounds
Vendored or frozen mirrorDependencies copied into the repository or a snapshotReproducible, offline buildsSecurity fixes lag unless updates are scheduled

Decision guidance: a small team without a repository manager still gets most of the benefit from scoped names, committed lockfiles, a release cooldown and scripts disabled by default, adding a hosted proxy later. Larger organisations should run a proxy with policy for developers and a stricter allowlist for production pipelines, because a build runner usually holds more valuable credentials than a laptop. Either way, block CI egress to public registries so a stray configuration file cannot bypass the proxy, and make sure you can purge a version from the cache: CISA told Shai-Hulud responders to search artifact repositories for cached copies.

Install-time controls: lockfiles, hashes, cooldowns and scripts

Hijacked releases are often reported within days. CISA's Shai-Hulud advice was to pin to versions published before 16 September 2025, the day after the worm was first reported, which a modest cooldown would already have enforced.

Controlnpmpnpmpip
Lockfile with integritypackage-lock.json integrity hashes; npm ci in CICommitted pnpm-lock.yaml, frozen in CIPinned requirements with --hash; --require-hashes forces hash-checking mode
Release cooldownmin-release-age (days) or before (date)minimumReleaseAge in minutes (documented default 1440; 10080 for a week), with minimumReleaseAgeExcludePackage age rule at the proxy
Install scriptsignore-scripts=true in .npmrcDependency postinstall off by default since v10; approve packages with allowBuilds; avoid dangerouslyAllowAllBuilds--only-binary :all: refuses source distributions
Trust checksnpm audit signatures (npm 8.15.0+) verifies registry signatures and attestationstrustPolicy: no-downgrade; blockExoticSubdeps blocks git and tarball transitive dependenciesVerify attestations where projects publish them
  • Cooldown length: three to seven days is a practical range. Keep a logged exception path so a cooldown never delays a fix for an actively exploited flaw.
  • Update bots: make Dependabot or Renovate respect the same minimum age and never auto-merge widely used packages. The axios npm compromise spread in minutes partly through auto-merging bots.
  • Script exceptions: ignore-scripts breaks packages that compile native code, so turn scripts off globally and approve the few that need them, rather than leaving them on everywhere.
  • Lockfile review: a pull request that adds a new transitive dependency, git URL or tarball URL deserves a human look.

Trust signals: trusted publishing, provenance and their limits

For packages you publish, the fix for stolen tokens is to stop having long-lived ones. npm trusted publishing uses OpenID Connect from GitHub Actions, GitLab CI/CD and CircleCI cloud runners, needs npm CLI 11.5.1 and Node 22.14.0 or later, and adds provenance automatically for GitHub and GitLab publishes. npm then recommends "Require two-factor authentication and disallow tokens" on the package. PyPI's equivalent mints a project-scoped token "only valid for 15 minutes from time of creation". After Shai-Hulud, GitHub announced npm publishing would narrow to local publishing with mandatory 2FA, granular tokens limited to seven days, and trusted publishing, deprecating classic tokens and TOTP in favour of FIDO. Leftover tokens keep causing incidents: see the Ultralytics PyPI compromise and the LiteLLM PyPI breach.

For packages you consume, provenance shows which repository and workflow built a version, recorded in Sigstore's public transparency log. npm's documentation is explicit that it "does not guarantee the package has no malicious code".

  • Watch for downgrades: a package that always shipped with provenance and suddenly publishes without it is a strong takeover signal; pnpm's trustPolicy automates the check.
  • Ignore vanity metrics: registries do not verify stars, downloads or linked repositories, which is what starjacking abuses.
  • Check AI suggestions: across 576,000 generated code samples, Spracklen et al. found average hallucinated package rates of at least 5.2% for commercial and 21.7% for open source models. Developers and coding agents should add dependencies only through the proxy. See the AI Coding Agents Security Guide.

See the SLSA and Build Provenance Guide for producing and verifying attestations.

Detecting malicious packages that got through

  • Malicious package feeds: the OpenSSF malicious-packages repository publishes reports in OSV format (MAL- identifiers) and works closely with OpenSSF Package Analysis. Feed it into proxy blocking and SCA so a reported package is blocked and past installs are flagged.
  • Queryable inventory: CISA's first Shai-Hulud instruction was to check lockfiles, including nested dependencies. Keep SBOMs or lockfile snapshots per build so you can answer "who installed version X, and when" in minutes. See the Open Source Security Guide.
  • Runner and endpoint telemetry: alert when node, npm, python or pip spawn shells, read ~/.npmrc, ~/.aws/credentials or ~/.ssh, or connect anywhere other than the proxy. CISA advised blocking outbound traffic to webhook.site domains.
  • Source control signals: new public repositories, new workflow files and unusual secret reads. The Nx s1ngularity attack even drove locally installed AI command-line tools to hunt for secrets.

Metrics that show it is working

  • Proxy coverage: share of package downloads from CI and developer machines served by the internal registry (target 100% for CI).
  • Exposed internal names: unscoped internal names without a public placeholder (target zero).
  • Cooldown compliance: share of production builds where every resolved version is older than the minimum age, excluding logged exceptions.
  • Time to block: hours from a MAL- report to the package being blocked and affected builds identified.
  • Publishing tokens: long-lived registry tokens remaining for packages you publish (target zero).

Where identity fits: credentials on developer machines and CI

The package is the delivery; the objective is the publishing tokens, GitHub personal access tokens, cloud keys, SSH keys and AI API keys in environment variables, dotfiles and CI secrets. The eslint-scope compromise stole npm tokens in 2018, and the ChainDrop npm worm took CI and cloud credentials across 400+ packages in hours. Reduce what an install can steal: keep publishing tokens off laptops, use workload identity federation instead of static cloud keys, and keep secrets out of the build steps that install dependencies. See the CI/CD Pipeline Identity Security Guide and the Secrets Management Guide.

Response when a malicious package was installed

  1. Scope: query lockfiles, SBOMs and proxy logs for the affected versions, including transitive use, and list every laptop, runner and image that installed them.
  2. Contain: block the versions at the proxy, purge caches, pin to the last good release, rebuild images and isolate machines that ran the payload.
  3. Rotate: treat every credential readable on those machines as stolen, starting with anything holding publish or write rights. Follow the Leaked Credential Response Playbook.
  4. Hunt: review audit logs for use of those credentials since first install: new package versions, repositories, workflows or cloud API calls.
  5. Harden: close the gap that let it in and record the incident against the metrics above.

Implementing the controls

  1. Inventory: list ecosystems, registry configuration files (.npmrc, pip.conf, nuget.config, settings.xml) and internal package names. Every --extra-index-url and unscoped internal name is confusion exposure.
  2. Protect names: scope internal npm packages, reserve scopes and prefixes publicly, and publish placeholders.
  3. Proxy in observe mode: route CI through the internal registry with logging only for two to four weeks and review what policy would have blocked.
  4. Pilot ring: enable blocking, a three-day cooldown and scripts off by default for one or two teams; build the script allowlist and the security-fix exception process.
  5. Enforce: require lockfiles and strict installs, block CI egress to public registries, then extend to developer machines through managed configuration. Move your own packages to trusted publishing.
  6. Rollback: keep proxy policy in version control so a bad rule reverts in one change, and use expiring, logged per-project exceptions instead of switching policy off.

Common failure patterns

  • Optional proxy: some pipelines still reach public registries directly, so policy protects only the builds that needed it least.
  • Package removed, credentials not rotated: stolen publish tokens are what let worms keep republishing, as with Mini Shai-Hulud and CanisterSprawl.
  • Former maintainers keep publish rights: the Ledger Connect Kit compromise came through a former employee's unrevoked npm access.
  • Extensions and AI tooling out of scope: GlassWorm spread through VS Code extensions, and the TrapDoor campaign aimed malicious packages at AI coding assistants.
  • Trusting popularity: high download counts persuade reviewers to skip checks, while worms target the most depended-on packages.

Framework mapping

FrameworkReferenceRelevance
MITRE ATT&CKT1195.001; M1013, M1033, M1051, M1016Compromised dependencies; developer guidance, limit software installation, controlled updates, scanning
NIST SSDF (SP 800-218)PW.4Acquire and maintain well-secured third-party components
NIST CSF 2.0GV.SC, ID.RA-09Supply chain risk management; software authenticity and integrity assessed before use
NIST SP 800-53 Rev. 5SR-3, SR-4, SR-11, CM-7(5), SI-7Supply chain controls, provenance, component authenticity, software allowlisting, integrity
CIS Controls v8.12.5, 16.4, 16.5Allowlist authorised software; inventory and trusted third-party components
ISO/IEC 27001:2022A.5.21, A.8.19, A.8.28ICT supply chain; software installation; secure coding with external libraries

Practitioner checklist

  • Route all CI package resolution through one internal registry and block CI egress to public registries.
  • Remove every --extra-index-url that mixes internal and public Python indexes.
  • Scope internal npm packages, reserve scopes and prefixes publicly, and publish placeholders.
  • Commit lockfiles with integrity hashes and use npm ci, frozen lockfiles or --require-hashes.
  • Apply a three to seven day minimum release age in package managers, the proxy and update bots.
  • Disable dependency install scripts by default and keep a reviewed allowlist.
  • Feed OpenSSF malicious package data into proxy blocking and SCA alerts.
  • Move your packages to trusted publishing, disallow token publishing and require phishing-resistant MFA for maintainers.
  • Rehearse the response: scope, contain, rotate every reachable credential, hunt, harden.

Standards and references

Related NHI Mgmt Group resources: Open Source Security Guide · SLSA and Build Provenance Guide · CI/CD Pipeline Identity Security Guide · Secrets Management Guide · Leaked Credential Response Playbook

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 17 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org