AI Coding Agents Removed the Last Human Auditor. That's Your Supply-Chain Problem Now.
Agentic code generation quietly moved dependency-vetting from engineers to machines that don't vet at all. The fix isn't fleeing a language or a registry. It's caging what your agents run.
Ask an engineer which packages shipped in last week's feature and you used to get an answer. Ask now, when an agent wrote the code, and you often get a shrug. The agent added the dependency. The package manager pulled its dependencies. Nobody read any of it, because reading it was never part of the workflow the tool was sold on. That is the uncomfortable centre of AI coding agent supply chain security.
It helps to be precise about what 'an agent did it' means, because three separate mechanisms hide inside that phrase. The model-driven tool decides a library is needed and writes the import. Your package manager then expands that one name into a tree of transitive dependencies you never chose. The editor plugin runs the install, and often the test suite, inside your development environment as a side effect of 'just trying it'. A human picking a library at least glances at the repo, the download count, the last commit. The resolver glances at nothing, and the plugin runs whatever the resolver produced. Packages three layers down get exactly the scrutiny that chain gives them, which is none.
Not every agent behaves this way, and the qualifier matters. Many still stop and ask before each shell command, and vendors document these approval controls: Cursor's agent has an auto-run setting that executes allow-listed commands without confirmation, and Anthropic's Claude Code ships an auto-accept mode and permission allow-lists alongside its default prompts. The documented fact is that these permissionless modes exist and are the ones sold on speed. How widely teams leave them switched on is not something any vendor publishes, so treat the claim that human review drops to zero as argument rather than measurement: it follows from how the modes work, not from a survey.
What happens when the package no one reviewed is malicious?
You get a failure mode that has already happened, and the count is climbing. Sonatype's 2024 State of the Software Supply Chain logged more than 512,000 malicious open-source packages in a single year, a 156% rise on the year before. That is a count, not a measured probability, but the direction is not in doubt: the more hostile packages sit in the registries, the more likely any given resolve is to touch one, even if nobody can put a firm number on the odds. Where would such a package hide? Endor Labs' dependency research found that roughly 95% of vulnerable dependencies are transitive, the indirect kind no developer selects by hand. Those are vulnerable packages, not malicious ones, so the figure measures where flaws sit, not where attacks land. But the two share a hiding place: if accidental flaws already cluster in the layers an agent resolves silently, that is exactly where planted code would sit unread too.
The documented incident makes it tangible. Security researchers at Snyk described a developer testing a Cursor editor integration who pulled a poisoned copy of a popular Python package as a transitive dependency. The malicious code wrote a hidden startup file that ran on import. What exposed it was almost comic: the payload spawned Python subprocesses recursively until the machine ran out of memory and crashed. Detection was an accident of resource exhaustion, not a control that fired.
Follow the mechanism and the lesson is uncomfortable. Nobody had to be careless. The ordinary behaviour of the toolchain was enough: the agent asked for a package, the resolver added something that package depended on, the integration ran it. No person chose the malicious code. Three automated steps handled it end to end, and none of them audits.
Fleeing the registry is the wrong reflex
The instinct after an incident like this is to distrust the ecosystem: blame the repository, retreat to a language with a 'safer' reputation. That misreads where the exposure sits. It was never in a registry's brand. It sat in unaudited code several dependencies deep, and every public ecosystem has that. Sonatype's malicious-package counts span npm, PyPI and the rest, so switching registries changes which brand you distrust while the unread code stays on your build server.
How do you actually cage a coding agent?
Three controls do most of the work, and none of them is exotic or hypothetical. Each aims at the same target: stop trying to decide whether a package is trustworthy, and constrain what an untrusted one can do.
First, default-deny egress on the build. A build agent needs a package index and perhaps a git host. It does not need your cloud metadata endpoint, your internal network, or an arbitrary server abroad. On GitHub Actions this is a deployed control today through StepSecurity's Harden-Runner, which blocks outbound traffic by default and takes an allow-list:
# .github/workflows: block egress, allow only what the build needs
- uses: step-security/harden-runner@v2
with:
egress-policy: block
allowed-endpoints: >
pypi.org:443
files.pythonhosted.org:443
github.com:443
A payload that tries to exfiltrate an SSH key now has nowhere to send it, and Harden-Runner records the blocked call so the attempt surfaces in the run log. You have constrained what the package can do without ruling on whether it is safe.
Second, install from a committed lockfile with hashes, and refuse anything that does not match. Pinning a top-level version is not enough, because the danger lives in the transitive tree. A lockfile records every resolved package and its hash, so a swapped or poisoned version cannot arrive silently. The commands already exist: npm ci installs strictly from a committed package-lock.json, pip install --require-hashes -r requirements.txt refuses any download whose hash is missing or wrong, and adding --ignore-scripts on npm blocks the install-time code execution many attacks rely on. With hashes pinned, a bad copy stops being a silent fetch and becomes a diff a human, or a scanner, reviews before anything runs:
# package-lock.json, surfaced in the pull request
"node_modules/log-helper": {
- "version": "2.4.0",
- "integrity": "sha512-6b1e2c..."
+ "version": "2.4.1",
+ "integrity": "sha512-00d4af..."
Without the lockfile the resolver just fetches the new version and the change is invisible. With it, the mutation is a red line waiting for review instead of a silent install.
Third, scan every dependency change automatically, and fail the build when it finds something it cannot justify. This is where the tooling has caught up. OSV-Scanner checks lockfiles against a public vulnerability database, and services like Socket inspect each added package for install scripts, obfuscated code and unexpected network access on the pull request itself. Wire one into the same sandbox and run it on every lockfile change:
# .github/workflows: fail the PR on a known-bad or newly added dependency
- uses: google/osv-scanner-action@v2
with:
scan-args: "--lockfile=package-lock.json"
Layering a model on top is where I think the economics finally tilt towards the defender, though this part is argument, not a benchmark: the same models that install without reading can also read a dependency diff faster than a person, flag a startup hook, and trace an exfiltration path. Used that way, the attacker automates the attack and you automate the auditor. The scanners above are the deployable core; the model is an accelerant on top, not a substitute for the sandbox and the lockfile.
We push the same discipline into every consulting engagement, and it runs through the wider analysis we publish: keep the human in the loop where judgement matters, and fence the machine where it does not.
The agents are not going back in the box, and they should not. But a team that installs what it never reads has automated away its own last line of defence. The answer is not to trust the packages more. It is to give the build somewhere safe to run, a lockfile that makes tampering visible, and a scanner that reads every change so no dependency reaches production unread.
Questions people ask
Are AI coding agents more dangerous than a human developer installing packages?
It is the same class of supply-chain attack, but the exposure is larger in the auto-approve and background-install modes teams enable for speed, where the agent resolves and runs transitive dependencies with little or no human glancing at any of them, so a compromise can propagate before anyone notices.
Should I switch programming languages after a dependency compromise?
No. Every public ecosystem carries unaudited transitive code, and Endor Labs' research puts roughly 95% of vulnerable dependencies in that transitive layer, so changing language moves which brand you distrust rather than solving anything. Pin versions with a hashed lockfile, verify provenance, and isolate execution instead.
Can an AI agent help defend against malicious packages rather than just cause the risk?
Yes, and the deployable core is not exotic: run builds in a default-deny sandbox such as Harden-Runner, and put an automated scanner like OSV-Scanner or Socket on every dependency diff to flag install scripts, obfuscation and unexpected network calls. A model can triage the flagged diffs faster than a person, but treat that as an accelerant on top of the scanner and sandbox, not a replacement for them.
Related
- On Ubuntu 26.04 LTS, the coreutils Your Build Depends On Isn't GNU Anymore
- The Sovereignty Premium: Why Sovereign AI Solutions for Enterprise Are Winning on Access, Not Speed
- Trade-Secret Cases Are Won Years Before Anyone Resigns. Ask Faccenda Chicken.
- Security & Trust
Written by an AI editorial persona of Abyshire's proprietary editorial system and reviewed by our team.