AI Coding Tools Pull In Malicious Dependencies

Developer reviewing terminal output on a laptop while installing code dependencies in a dark workspace

You asked Claude Code for a quick feature, it ran npm install on three packages you've never heard of, and you merged without reading the lockfile diff. That's how most AI-assisted projects now grow dependencies: fast, unreviewed, and from names the model suggested. This guide covers how malicious packages reach your repo through AI tools, what recent attacks looked like, and a concrete hardening setup you can apply this week.

Why AI coding tools change the dependency risk

AI coding tools raise supply-chain risk in two ways: they add more dependencies per feature than a human would, and they suggest package names from memory, sometimes names that don't exist. Both shrink the time between "model had an idea" and "unknown code runs on your machine or in CI."

The sprawl part is a workflow problem. When a human adds a dependency, there's friction: search, read the README, check the repo. An agent removes that friction. It resolves an error by installing something, and the install executes with your credentials, on your laptop or your CI runner.

Chainguard's CISO Quincy Castro argues that velocity has outpaced governance and controls. One caveat worth stating: that framing comes from a VentureBeat article labeled "Presented by Chainguard", which is sponsored content from a vendor that sells in this space. The argument is plausible, but the hard evidence in this post comes from independent research and incident reports.

For a solo founder or a team of five, the practical translation is simple. Your threat model used to be "will I pick a bad package?" Now it also includes "will my agent pick one, and will I notice?"

Slopsquatting: when the model invents a package name

Slopsquatting is an attack where someone registers a package name that AI models tend to hallucinate, then waits for a developer or agent to install it. The names are fabricated by the model, so no legitimate package exists until the attacker creates one.

The scale in the research is large. A USENIX Security 2025 study generated 2.23 million code samples across 16 models in Python and JavaScript. According to the Cloud Security Alliance summary, 440,445 of those samples (19.7%) contained at least one hallucinated package name, covering 205,474 unique fabricated names. Note that sample-count figures for this study vary between secondary sources, so I'm using the CSA's numbers with attribution.

Two things make this exploitable rather than merely embarrassing:

The names are predictable. In the same CSA note, when identical prompts were re-run ten times, 43% of hallucinated names reappeared on every run. An attacker doesn't need to guess; they can run the prompts themselves and register the repeat offenders.

Newer models haven't fixed it. A 2026 replication on five frontier models released between October 2025 and March 2026 found the hallucination range narrowed to 4.62%–6.10%. The authors still conclude the threat remains economically viable for attackers. Lower isn't zero, and at agent scale a few percent of installs is a lot of installs.

There's also direct evidence that this isn't theoretical. After review by PyPI Security and Socket, 53 hallucinated names shared across five frontier models (41 on PyPI, 12 on npm) were still unregistered and available to anyone. The models tested were Claude Sonnet 4.6, Claude Haiku 4.5, GPT-5.4-mini, Gemini 2.5 Pro and DeepSeek V3.2. That cross-model overlap matters: if several models invent the same name, one registration catches users of all of them.

The 2026 npm worm wave: what actually happened

The more immediate threat is not hallucination but compromised real packages. Through 2026, the Shai-Hulud family of npm worms hit well-known packages, and the later variants specifically targeted AI coding agent configuration. Here's the timeline from primary and security-vendor reporting.

Date Incident What matters
May 11, 2026 Mini Shai-Hulud (TeamPCP) peak Reported by CSA and Unit 42 as 373 malicious versions across 169 npm packages plus two PyPI packages; other vendors count differently
May 19, 2026 Wave via compromised maintainer account atool Hundreds of versions published within about an hour; Unit 42 counts 639 versions across 323 packages
June 1, 2026 Red Hat Cloud Services namespace At least 32 package releases infected, roughly 80,000 weekly downloads, payload ran via a preinstall hook
August 6, 2026 "ChainDrop" variant Singapore CSA advisory: over 1,300 npm package versions, 2 billion combined monthly downloads, including keyv, cacheable and flat-cache
October 8, 2026 tensorlake 0.5.144 Credential stealer that writes agent and editor config into reachable repos

Exact counts differ between vendors for the May waves and the Red Hat incident, so treat the numbers above as attributed figures, not a settled tally. The pattern is consistent regardless: hundreds to over a thousand malicious versions, executing at install time.

Three details deserve attention.

Install scripts are the execution path. The Red Hat packages ran their payload through a preinstall hook during npm install. You don't have to import the package or run your app. Installing is enough.

The worm targets AI agent config for persistence. The CSA describes Mini Shai-Hulud as the first recorded supply chain attack to use AI coding agent config files, such as Claude Code's .claude/settings.json, for persistence. The tensorlake variant steals credentials and writes .claude/settings.json and .vscode/tasks.json into reachable repos so it re-runs when the project is opened in Claude Code or VS Code. Cleaning node_modules isn't enough if the config files keep re-triggering it.

Provenance badges are not a safety guarantee. Microsoft's analysis of the compromised @antv packages says the payload ran during npm install and forged SLSA provenance attestations through Sigstore. A "built with provenance" badge tells you where a package claims to come from. When the publisher account itself is compromised, the badge can lie.

Control 1: pin everything and install from the lockfile

The cheapest, highest-value control is making installs deterministic: exact versions, hashes in a lockfile, and CI that refuses to deviate. A floating ^1.2.3 range means a compromised patch release can land on your next install without anyone touching your code.

npm ci installs the dependency graph from package-lock.json and fails when the lockfile doesn't match package.json, which is why it's recommended for CI and deployments over npm install (per this explainer; verify against the official npm docs for your npm version).

# CI and deploys: deterministic, fails on lockfile drift
npm ci

# Also skip install scripts, the execution path in the incidents above
npm ci --ignore-scripts

Set it as a project default so nobody (human or agent) has to remember:

# .npmrc
ignore-scripts=true
save-exact=true

ignore-scripts=true will break the few packages that legitimately need a build step (native modules, some CLIs). Handle those explicitly:

# Run only the scripts you've reviewed and decided you need
npm rebuild sharp

For Python, use hash-pinned requirements so a swapped artifact fails the install:

pip-compile --generate-hashes requirements.in -o requirements.txt
pip install --require-hashes -r requirements.txt

Be honest about the limit: pinning stops new malicious versions from entering silently. It doesn't help if the version you pinned was already compromised when you pinned it. That's what the next controls address.

Control 2: put a gate between the agent and the registry

Agents should propose dependencies, not apply them. The goal is a human-visible checkpoint on any change to manifests or lockfiles, plus automated checks that run before merge.

Claude Code's official security docs state that commands that fetch web content (curl, wget) are not auto-approved by default, that you can block commands with permissions.deny, and that a sandboxed bash tool with filesystem and network isolation is available via /sandbox. Use those. A starting .claude/settings.json for a project that shouldn't install things unattended:

{
  "permissions": {
    "deny": [
      "Bash(npm install:*)",
      "Bash(npm i:*)",
      "Bash(pip install:*)",
      "Bash(curl:*)",
      "Bash(wget:*)"
    ],
    "ask": [
      "Bash(npm ci:*)"
    ]
  }
}

Check the current permissions syntax in the docs before copying; the schema evolves. The intent matters more than the exact strings: installs become something you approve, with the package name in front of you.

Because the 2026 worms write to .claude/settings.json and .vscode/tasks.json, treat those files like code that needs review:

# In CI: fail the build if agent or editor config changed without review
git diff --name-only origin/main...HEAD | grep -E '^\.(claude|vscode)/' \
  && echo "Agent/editor config changed: require manual approval" && exit 1 || true

A CODEOWNERS entry for .claude/, .vscode/, package.json, and lockfiles gets you the same effect through your normal review flow.

Finally, a quick pre-install sanity check for any new package an agent suggests:

# Does it exist, how old is it, who publishes it?
npm view <package> time.created maintainers repository.url

A package created last week with one maintainer and no repository link, suggested by a model for a common task, is the slopsquatting signature.

Control 3: generate an SBOM and diff it on every PR

A software bill of materials (SBOM) is an inventory of what's actually in your build. Its value in an AI-assisted workflow is the diff: you see exactly which components a pull request added, including transitive ones you'd never read in package.json.

CISA released its updated Minimum Elements for SBOM on July 29, 2026, building on the 2021 NTIA baseline. New minimum elements include Component Hash Algorithm, Component License, SBOM Tool Name and SBOM Generation Context. I couldn't confirm from a primary source whether these are binding for private companies, so treat them as a useful checklist for what a good SBOM contains, and check the CISA page if you have contractual or regulatory obligations.

A minimal CI step using open-source tooling (Syft is one option; swap in your preferred generator):

# .github/workflows/sbom.yml
name: sbom-diff
on: [pull_request]
jobs:
  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.base_ref }}
          path: base
      - name: Install syft
        run: curl -sSfL raw.githubusercontent.com | sh -s -- -b /usr/local/bin
      - name: Generate SBOMs
        run: |
          syft dir:. -o cyclonedx-json > head.json
          syft dir:base -o cyclonedx-json > base.json
      - name: Show added components
        run: |
          jq -r '.components[] | "\(.name)@\(.version)"' base.json | sort > base.txt
          jq -r '.components[] | "\(.name)@\(.version)"' head.json | sort > head.txt
          comm -13 base.txt head.txt | tee added.txt
          test ! -s added.txt || echo "::warning::New components added, review required"

Pin that curl | sh installer to a specific release in your real pipeline; piping a moving branch into a shell is the exact pattern this post warns about. The point of the sketch is the diff output: a PR that "adds a date formatter" and brings 40 new transitive components should get a second look.

Control 4: shrink the blast radius when something gets through

Assume one bad package will eventually execute. What it can steal and reach determines whether that's an annoying afternoon or a company-ending incident. The worms above were credential stealers, targeting CI/CD secrets and developer tokens.

Practical limits that cost little:

  • Short-lived, scoped CI credentials. Use OIDC-issued tokens for deploys instead of long-lived keys in repo secrets.
  • Separate install from deploy. Run dependency installation in a job with no production secrets and no write access to registries.
  • No publish tokens on dev machines. If your npm publish token sits in ~/.npmrc next to an agent running installs, a worm can republish your own packages. That's how these worms propagate.
  • Egress control in CI. If your build only needs the registry and your artifact store, block the rest.
  • Run agents in the sandbox. Claude Code's /sandbox network and filesystem isolation exists for exactly this; using it for dependency-touching tasks is cheap insurance.

After any suspected install, rotate credentials first, then clean. The persistence mechanism in the tensorlake case means checking .claude/settings.json and .vscode/tasks.json in every repo the machine touched, not just the one where you ran the install.

A weekly routine that takes 20 minutes

Controls decay without a routine. This is the lightweight version I'd give a solo operator or a five-person team.

  1. Review the lockfile diff of anything an agent touched. Look at new top-level names first. Anything you didn't ask for gets questioned.
  2. Run npm audit and your language's equivalent, but don't treat a clean result as proof of safety. Advisory databases lag behind worms that publish hundreds of versions in an hour.
  3. Check agent config in version control. git log -p -- .claude .vscode shows whether anything changed unexpectedly.
  4. Delay new versions. Many teams adopt a cooldown (don't install versions younger than a few days). Dependabot and Renovate both support minimum release age settings; check the Renovate minimum release age docs and your tool's current docs for the option name. A short waiting period reduces exposure, because some of these attacks lasted only a few hours, and a cooldown gives researchers and scanning tools time to spot malicious code.
  5. Prune. Remove dependencies nobody can justify. Every package you don't have is one you can't be compromised through.

None of this requires buying anything. The expensive failure mode is skipping it because the agent made everything feel effortless.

Where this fits in my own work

I build AI automations for small teams, and unvetted dependencies pulled in by AI coding tools are a risk I take seriously in my own setups. The pattern worth watching for is usually the same: an agent has install rights, lockfiles aren't enforced in CI, and nobody diffs what changed.

If you run Claude Code or agent pipelines, the setup I'd recommend is the one described above: pinned dependencies with enforced lockfile installs, permission rules that put package installs behind approval, SBOM generation and diffing on pull requests, and review gates on agent config files like .claude/settings.json.


Work with BizFlowAI

If you'd rather have this built for you, that's what we do: production AI automation for solo founders and small teams — agents, integrations, and document pipelines that actually ship.

Request a free discovery call — 30 minutes, we map the highest-ROI automation in your workflow. No pitch deck, just engineering.

More guides like this on the BizFlowAI blog.

Frequently asked questions

What is slopsquatting and how does it affect AI-assisted development?

Slopsquatting is an attack where someone registers a package name that AI models tend to hallucinate, then waits for a developer or coding agent to install it. Because the names are invented by the model, no legitimate package exists until the attacker creates one. A USENIX Security 2025 study of 2.23 million code samples found 19.7% contained at least one hallucinated package name. Because many of these names repeat across runs and across models, attackers can predict and register them.

How do malicious npm packages get into my project through Claude Code or other AI agents?

AI agents often install packages to fix errors or add features, and those installs run with your credentials on your laptop or CI runner. Agents add more dependencies per feature than humans do and may suggest names from memory, including nonexistent ones. Compromised real packages are the bigger risk: in the 2026 Shai-Hulud npm worm wave, payloads ran through install-time hooks like preinstall, so simply running npm install was enough. If the change is merged without reading the lockfile diff, the malicious version stays in your repo.

Can a malicious npm package persist through Claude Code or VS Code config files?

Yes. Security researchers reported Shai-Hulud variants that write agent and editor config files such as .claude/settings.json and .vscode/tasks.json into reachable repositories. Because these files can trigger commands when the project is opened in Claude Code or VS Code, the malware can re-run even after you delete node_modules. After any suspected compromise, you should audit and revert these config files, rotate exposed credentials, and review repo changes. Cleaning dependencies alone is not sufficient.

Should I use npm ci or npm install in CI to avoid supply chain attacks?

Use npm ci in CI and deployments. It installs exactly what is in package-lock.json and fails if the lockfile does not match package.json, so a compromised new version cannot slip in silently through a floating version range. Adding --ignore-scripts also blocks install-time scripts, the execution path used in recent npm worm incidents. Note that pinning does not protect you if the version you locked was already compromised, so combine it with review and scanning.

Does a provenance or SLSA badge on an npm package mean it is safe to install?

No. A provenance badge only shows where a package claims to have been built. Microsoft's analysis of the compromised @antv npm packages found the payload forged SLSA provenance attestations through Sigstore. When a publisher account or build pipeline is compromised, the attestation can be misleading. Treat provenance as one signal alongside version pinning, disabled install scripts, lockfile review and dependency scanning.