AI 編碼智能體新增 npm 套件後,合併分支前檢查套件的 6 條命令
Your Coding Agent Added an npm Package. 6 Commands to Vet It Before You Land the Branch
開發者可用 6 條 npm 命令,在合併編碼智能體的分支前核查新增套件,包括檢視差異、依賴鏈、套件中繼資料、封裝內容及簽章和來源證明。文中指出,受污染的 axios 版本透過 lockfile 中新增的依賴引入安裝腳本,並建議在 `.npmrc` 設定 `ignore-scripts = true` 和 `min-release-age = 7`;npm 12 預設封鎖未獲允許的依賴生命週期腳本。
- Book: AI That Acts
- The series: AI in TypeScript — 5 books, from your first LLM call to agents in production — all five here
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
One line in the package.json diff is a name you have never typed: a small date-parsing library the agent mentions in passing near the end of its summary. The bug is fixed and the tests pass. The lockfile grew by a few hundred lines.
You don't know who publishes that package, when it first appeared, or what it runs when it installs. A USENIX Security 2025 paper generated 576,000 code samples with 16 models and found that at least 5.2% of the packages commercial models suggested did not exist (21.7% for open-source models), which leaves made-up names free for anyone to register. And on March 31, 2026 (UTC), two poisoned axios releases went out to a library with over 100 million weekly downloads. Per Trend Micro, "the only change in both poisoned Axios versions is a single new dependency": plain-crypto-js@4.2.1, published 22 minutes before the first of them, whose postinstall script dropped a remote access trojan.
A new name in the manifest or the lockfile is the thing to stop at. Six commands cover it: git diff, npm ci, npm explain, npm view, npm pack and npm audit signatures. Run them in the agent's worktree for every package it added, before you land its branch. They work on npm 11 and 12, and I mark where 12 differs.
git diff: list every package the branch added
Run this from the agent's worktree, with its branch checked out. The three-dot form of git diff shows only what changed since the branch left main:
git diff --stat main...HEAD
# New names under dependencies or devDependencies
git diff main...HEAD -- package.json
# Lockfile entries the branch added or moved
git diff main...HEAD -- package-lock.json \
| grep -E '^\+ +"node_modules/'
# Any of them that runs code at install time
git diff main...HEAD -- package-lock.json \
| grep -E '^\+ +"hasInstallScript"'
The last grep matters most. In the lockfile, hasInstallScript is "a flag to indicate that the package has a preinstall, install, or postinstall script." If it prints anything, find its entry in the full diff.
Expect the lockfile list to be longer, since one direct dependency brings its own. The axios attack lived there: nothing new in your package.json, one new transitive name in the lock. If the agent hasn't committed yet, drop the dots (git diff main -- package.json) to compare your working tree with main.
On npm 12, also look for a new allowScripts key in the package.json diff. It lists which dependencies may run install scripts, and npm install-scripts approve writes to it. If the agent approved one, the line is there for you to read.
npm ci and npm explain: install without running anything
npm ci --ignore-scripts
npm explain name-from-the-diff
npm ci deletes node_modules and installs exactly what the lockfile says. It "will never write to package.json or package-lock.json," and it exits with an error if the two disagree, which tells you package.json changed without the lockfile being regenerated. ignore-scripts means "npm does not run scripts specified in package.json files," so vetting a package doesn't execute it. npm 12 already blocks unapproved dependency scripts, and this flag also skips any the agent approved.
npm explain prints "the chain of dependencies causing a given package to be installed." For a direct dependency the chain ends at your project. For a transitive one it names the parent: on plain-crypto-js it would have pointed at axios, whose source never imported it.
npm view: check age, owners and source
npm view reads the registry's metadata, and a period selects a child field:
PKG=name-from-the-diff
VER=version-from-the-lockfile
npm view "$PKG" time.created time.modified
npm view "$PKG" "time[$VER]"
npm view "$PKG" maintainers repository.url
npm view "$PKG@$VER" scripts
npm view "$PKG@$VER" dist.attestations
How to read the answers:
-
time.createdandtime[$VER]: a package first published last week, with one or two versions, deserves a slower read. So does a version only minutes old, likeplain-crypto-js@4.2.1when axios started depending on it. -
maintainersandrepository.url: who can publish, and where the code supposedly lives. The repository field is whatever the publisher typed, so open it and look for a tag matching$VER. Trend Micro found "no commit, tag, or release" in the axios repo for either poisoned version. -
scripts: apreinstall,installorpostinstallentry is code that runs on your machine at install time. -
dist.attestations: whether the version has a provenance attestation. The registry record for axios 1.14.0 carries a SLSA provenance statement. The poisoned 1.14.1 "was published manually with a stolen npm access token, with no OIDC binding and no gitHead." Run the command on the previous version too. An attestation that disappears between releases is a reason to stop.
npm pack and npm audit signatures: see what ships
npm pack "$PKG@$VER" --dry-run
npm audit signatures
npm pack accepts a registry name and version, and with dry-run npm will "only report what it would have done," so you see the tarball's files without saving it. The tarball is what lands in node_modules, and it can differ from the repo. A date library has no business shipping a native binary, a minified blob with no source, or a setup.js wired to postinstall, which is the shape of the axios payload.
npm audit signatures checks the registry signature of every installed package and "will also verify the provenance attestations of downloaded packages," so it needs the node_modules from the npm ci step. npm's provenance guide shows the output shape: "1267 packages have verified registry signatures", "74 packages have verified attestations". The signature docs say the CLI errors when packages lack signatures on a registry that provides them, which "could mean an attacker might be trying to circumvent signature verification."
Provenance tells you which repository and build produced a version, which is what the poisoned axios release lacked. Whether that code is safe to run is still your call.
Catch it in the Review Desk before you land
I build Hermes IDE, so weigh this section with that in mind. Hermes runs each agent, such as Claude Code or Codex, in its own terminal on its own hermes/ branch and worktree, which is where the commands above run.
⌘G (Ctrl+Shift+G on Windows and Linux) opens the Review Desk. Per the README, it "shows changes by turn or by file with risk flags." In the by-turn view, the package-lock.json change shows up under the turn that made it.

The Review Desk (⌘G) in By turn view for a Claude Code task in orders-api: two turns (T1: 3 files +69 −1, T2: 1 file +1 −0), turn 1's diff of src/db.ts, the changed files and a commit message drafted from the turns.
Every agent turn is saved, so you can diff or restore that single turn, with undo. Or leave a line comment on the package.json line, which goes back to the agent that wrote it: "use the built-in Date and Intl APIs, no new dependency."
Your own checks can also decide when a task is done, and the worktree recipes doc covers setup per repository. npm test and npm audit signatures are good candidates for those checks. Land then commits, opens a pull request or merges locally into the branch the task started from, and can be undone.
Hermes is free, source-available under BSL 1.1, on macOS, Windows and Linux. The current release is 2.3.4, from October 15, 2026. It isn't affiliated with Anthropic, OpenAI, Nous Research or any agent vendor. Download it or read the code on GitHub.
Stop the next one earlier
All of this happens after the agent ran npm install. On npm 11, dependency install scripts run by default, so a hostile postinstall has already executed by the time you review. Two lines in a committed .npmrc narrow that window:
ignore-scripts=true
min-release-age=7
min-release-age arrived in npm 11.10.0 on February 11, 2026, and installs only versions "available more than the given number of days ago." Seven days would have kept both poisoned axios versions and plain-crypto-js@4.2.1 out of a fresh install. ignore-scripts will break the odd package that compiles native code at install; handle those one at a time.
npm 12, released July 8, 2026, blocks dependency lifecycle scripts by default "unless allowed by the root package's allowScripts policy." That puts the decision in package.json, where the git diff check sees it. While you're there, add one line to your AGENTS.md: ask before adding a dependency.
What does your team do when an agent adds a package nobody asked for? Tell me in the comments.
If this was useful
Every check above asks one question about an agent's action: what did it change, and can you take it back? AI That Acts asks it from the builder's side: tool calling and functions in TypeScript, up to a first working agent that acts within limits you set. If you have watched an agent run npm install and wondered how that tool call is wired, start there.
It is book 3 of AI in TypeScript, five books from your first LLM call to agents in production: the whole series. And if you review agent work every day, Hermes IDE shows each agent's changes turn by turn before you land them.
來源:DEV Community · dev.to
