[Agent Tools](https://tools.openkrill.app/) / [Blog](https://tools.openkrill.app/blog)

# How to use Claude for dependency audits: 6 prompts on a real lockfile

We gave Claude Code one context file, one free vulnerability server and the lockfile of our own monorepo. In 6 prompts and about 11 minutes of Claude time, it checked 724 package versions and found 9 with known advisories. Then it wrote a fix ticket a teammate can follow. None of the 9 is exploited in the wild. One fix is blocked by a library we cannot move yet, and Claude found that before anyone started on it. Every prompt, output and number below comes from that run.

By OpenKrill. Published 2026-10-25. The run took place on 2026-10-24 between 05:58 and 06:15 UTC, on a copy of our own repository as it stood that morning. The gaps between prompts were our own hand checks. We used Claude Code 2.1.302 with Claude Opus 5.5 on a subscription plan, so no usage was billed. Claude Code's own estimate for the session was 3.07 US dollars. Our local Claude Code setup also routed some web page reads in prompts 2 to 6 to a second, smaller model, DeepSeek V4.1 Flash. That model's share of the estimate was 0.11 US dollars. Vulnerability data came from OSV.dev, CISA KEV and FIRST EPSS, as of 2026-10-23.

## The short answer

A scanner gives you a list. An audit gives you an order: what to fix today, what can wait, what is blocked, and who has to do what. Claude can do the second job well, but only when it reads three things first: your rules, the real versions in your lockfile, and live advisory data. Give it those, keep it read-only, and ask one narrow question per prompt.

| Prompt | What it answered | Time |
| --- | --- | --- |
| 1. Triage the lockfile | 9 vulnerable versions out of 724, none exploited, zod first | 5 min 45 s |
| 2. Check reachability | Probably not reachable: both stranger-facing zod arrays have a size limit | 1 min 15 s |
| 3. Plan the upgrades | 3 lockfile refreshes, 1 pin in 7 files, 1 mixed, 1 blocked | 1 min 5 s |
| 4. Check the runtimes | Nothing in production past end of life. Server jobs use an unpinned Node.js | 1 min 8 s |
| 5. Gate a new package | Do not add jsonwebtoken@8.5.1. The repo already signs with WebCrypto | 44 s |
| 6. Write and check the ticket | A ticket of about 245 words, with 3 untraced items named | 40 s |

Times are Claude Code's own durations for each prompt. The first prompt is slow because it reads the lockfile and makes the vulnerability calls. The rest reuse what it already read.

## Why most dependency audits with AI go wrong

The usual way is to paste a scanner report into a chat and ask "what should I fix?" You get a confident answer that sorts by severity and tells you to upgrade everything. That answer has three problems.

- It does not know which code faces the internet, so a test tool and your public API get the same urgency.
- It does not know your versions unless it reads them, so it guesses from memory, and model memory is months old.
- It does not know your rules, so it cannot tell a major upgrade that needs a plan from a patch you can merge before lunch.

The fix is better input, not a better prompt. Before you ask a single question, you write down what Claude cannot know, and you connect it to data it cannot remember.

## Set up Claude so it already knows your repo

You do this once. We did it in Claude Code, which reads a file called `CLAUDE.md` at the root of the repository before every session. In the Claude apps, a Project with instructions does the same job.

### Step 1: write the context file

This is the exact file we used. Each section is short on purpose. Claude needs the facts that change its answer, not a tour of the codebase.

```
# Dependency context

Read this before you answer any question about packages, versions or vulnerabilities in this repo.

## Stack
- One npm workspaces monorepo: apps/*, packages/* and workers/*, with one root package-lock.json.
- TypeScript everywhere. Tests run with vitest.
- apps/api and apps/site run on Cloudflare Workers. Jobs under ops/ run on Node.js on an Ubuntu server.

## What faces the internet
- apps/api: public MCP servers and HTTP APIs. It parses input from strangers.
- apps/site: public web pages served by a Worker.
- Test and build tools (vitest, wrangler, tsx, esbuild) never run in production.

## Patch rules
- malicious or exploited: fix today.
- likely (EPSS 0.1 or more): fix this week.
- routine, in production code: next weekly batch.
- routine, dev tools only: monthly batch.
- A major version bump needs a note on what breaks before anyone starts it.

## How to answer
- Read versions from package-lock.json, never from memory.
- Give package@version and file:line for every claim.
- Say when you could not check something, and why.
- Never edit package.json or package-lock.json. Only report.
```

Two sections do most of the work. "What faces the internet" lets Claude rank the same bug differently in your API and in your test runner. "Patch rules" turns a list of advisories into a calendar. The priority words match what the vulnerability server returns, so Claude can map one to the other without guessing.

### Step 2: connect live vulnerability data

Claude does not know which advisories were published this week. We connected our free [CVE Risk Check](https://tools.openkrill.app/integrations/cve/claude) server, which takes no key and returns the full result. In Claude Code it is one line:

```
claude mcp add --transport http cve https://cve.openkrill.app/mcp
```

Its `triage_dependencies` tool takes a lockfile or a list of exact versions. For each version it reads advisories from OSV.dev, checks the CISA list of vulnerabilities exploited in the wild, and adds the FIRST EPSS score. EPSS is the chance that a flaw is exploited in the next 30 days. The tool then sorts each version into malicious, exploited, likely or routine, and gives the lowest version that fixes it.

Any source of live advisories works for this guide, as long as it returns exact versions and the fix version. What matters is that Claude reads data from today instead of recalling data from its training.

### Step 3: keep Claude read-only

An audit should not change anything. We ran every prompt headless, on a throwaway copy of the repository, with the edit tools switched off. This is the command we used for each prompt:

```
claude -p "$(cat prompt.txt)" --output-format json \
  --setting-sources project --strict-mcp-config --mcp-config mcp.json \
  --allowedTools "Read" "Grep" "Glob" "WebFetch" "mcp__cve" \
    "Bash(node:*)" "Bash(jq:*)" "Bash(rg:*)" \
  --disallowedTools "Edit" "Write" "NotebookEdit" \
  --resume "$SESSION_ID"
```

`--resume` keeps one session across the 6 prompts, so later prompts reuse what the first one read. The JSON output records the time, the turns and the cost of each prompt, which is where the numbers in this post come from. You can run the same prompts in an ordinary Claude Code chat. The headless form just makes the run easy to repeat.

## Prompt 1: triage the whole lockfile

We started with the plainest prompt we could write:

```
Use the cve server's triage_dependencies tool on package-lock.json and tell me what to fix first.
```

Our lockfile has 781 installed packages, and the tool checks at most 300 in one call. When we sent the whole file to the tool ourselves, the answer said so in plain words:

```
"summary": "Checked 300 packages: 2 have known vulnerabilities (3 advisories): 2 routine. Fix first: @vitest/mocker, zod.",
"truncated": true,
"message": "Only the first 300 of 781 packages were checked."
```

A quick reading stops there and reports 2 findings. Claude did not. It removed duplicate versions, which left 724, and sent them in 3 calls of up to 300 each. Two of its shell commands were blocked by our allow list, because one wrote a file and one used grep. It went on with the tools it was allowed to use. Its report started like this:

```
Nothing in the lockfile is malicious, exploited or likely. All 7 vulnerable package versions are routine,
so none of this is a fix-today or fix-this-week job under the patch rules. Fix first: zod in apps/api,
because that app parses input from strangers.

How I ran it: the lockfile has 724 unique package versions (...)
I sent all 724 in three calls, so none were left out, skipped or not triaged.
```

Then it gave a table for the weekly batch, with the lockfile line for each version:

| Package | Advisories | EPSS, highest | Fix |
| --- | --- | --- | --- |
| zod@4.4.3 | CVE-2023-54404, high | 0.35% | 4.6.6 |
| undici@7.30.0 | 3 CVEs, high | 0.34% | 7.30.1 |
| undici@8.11.2 | the same 3 | 0.34% | 8.12.2 |
| ip-address@10.7.2 | CVE-2026-111997, moderate | 0.25% | 10.7.4 |
| fast-uri@3.1.8 | 3 CVEs, low | 0.25% | 3.1.9 |

It also put 3 dev-only versions in the monthly batch: @vitest/mocker@4.1.11, @vitest/mocker@5.0.2 and body-parser@2.3.0. And it flagged a second zod problem that needs a decision: 3 nested copies of zod@3.25.76 under the `@x402` payment libraries, where the only fix is zod 4.

### What we checked by hand

The opening line says 7 vulnerable versions. The tables below it list 9: the 5 above, the 3 dev-only ones and zod@3.25.76. We sent the same 724 versions to the tool ourselves, in 3 batches, and got 9 vulnerable versions and 18 advisories. Counted per version, the 18 advisories carry 17 CVE ids, because one fast-uri advisory has no CVE id yet. The tables were right and the summary line was wrong.

Claude's details were correct, and we checked every lockfile line it cited. But the one sentence a busy reader would quote was off by 2. Count the table yourself before you repeat the headline number.

## Prompt 2: find out if the bug can reach you

A vulnerable version in your lockfile is not the same as a vulnerable app. The zod advisory is about a stranger sending a huge collection to a schema. It is not clear whether a size limit on the schema stops it, so we wanted to know where our limits are. So we asked Claude to read the advisory and then read our code:

```
Take the zod finding (CVE-2023-54404). First read the advisory GHSA-mrhx-c7xc-m3w5 on github.com/advisories
and say in 3 lines what input triggers it and which zod versions are affected.
Then search apps/api/src for zod schemas that parse input from outside callers: z.array, z.set, z.map and z.record.
For each one, say file:line, whether the input comes from a stranger (a tool argument or an HTTP body),
and whether it has a size limit (.max or a length check before parsing).
End with one line: reachable, not reachable, or not sure, and why.
```

Claude found only 2 zod collections in our API code that take input from strangers. Both are arguments of one public tool, and both have a size limit:

| Schema | Input from | Size limit |
| --- | --- | --- |
| `palette: z.array(hex)` | a tool argument | `.max(8)` |
| `formats: z.array(z.enum(...))` | a tool argument | `.max(4)` |

It gave the file and line for each one. It also found the line that caps request bodies at 65,536 bytes before zod sees them. It said a server setting can raise that cap, and that it did not check the deployed value or the MCP endpoint. Every other collection it found checks our own output or parses files from the repository at startup.

Its verdict was "not sure", not "safe". It gave two reasons. The advisory's title and text disagree on whether `.max()` protects you. And it had searched only the API folder, while other workspace packages also define schemas. We opened all 3 cited lines and they say exactly what Claude quoted.

This is the answer you want from an audit. It narrows the risk with evidence, says what it did not search, and does not pretend to be sure. The tool itself warns that it cannot tell whether your code reaches the vulnerable code, and Claude kept to that.

## Prompt 3: plan the upgrades, not just the versions

"Upgrade zod to 4.6.6" sounds like one step. In a monorepo it can be one step, seven steps, or impossible. So we asked where each version comes from:

```
Now make the upgrade plan for the production findings.
For each one, find which package.json asks for it and what range it allows,
so we know if a lockfile refresh is enough or a package.json change is needed.
For the three zod@3.25.76 copies under @x402/*: look up the newest @x402/core, @x402/evm and @x402/extensions
on registry.npmjs.org and say which zod range each newest version asks for. Say if moving them would remove zod 3.
Give one table: package, from, to, change needed (lockfile only, package.json, or blocked), and the test command to run after.
Use only what you read. Mark anything you did not read as not checked.
```

The plan came back in one table. Its opening line said 4 of the 6 fixes were lockfile only, but the table shows 3. That was the second count Claude got wrong, so read the table, not the summary.

| Package | From | To | Change needed |
| --- | --- | --- | --- |
| zod | 4.4.3 | 4.6.6 | package.json: an exact pin in 7 files |
| zod, under @x402 | 3.25.76 | 4.6.6 | blocked |
| undici | 7.30.0 | 7.30.1 | package.json in one package, a lockfile refresh for the other |
| undici | 8.11.2 | 8.12.2 | lockfile only |
| ip-address | 10.7.2 | 10.7.4 | lockfile only |
| fast-uri | 3.1.8 | 3.1.9 | lockfile only |

The blocked row matters most. Our app already uses the newest @x402 release, 2.28.0, and that release still asks for zod `^3.24.2`. Claude read this from registry.npmjs.org, and we read the same value there afterwards. So no amount of upgrading on our side removes zod 3 today. The choices are to wait for the library, or to force zod 4 onto it. Forcing it is a major change, and our rules say a major change needs a plan first.

Claude also found two things we had not asked about. Three of the packages that pin zod have no test script, only a type check. And a dev tool asks for an undici version the lockfile does not contain. It marked both as side notes, not as tasks.

## Prompt 4: check the runtimes, not only the packages

A clean lockfile does not help if Node.js itself has stopped getting security fixes. We asked Claude to find every runtime version the repository names, and to compare each with the public end-of-life dates:

```
Check the runtimes, not just the packages.
Find which Node.js and Python versions this repo says it runs on: engines fields, .nvmrc, Dockerfiles, CI files,
systemd units under ops/, and wrangler compatibility_date in apps/api and apps/site.
Then read https://endoflife.date/api/nodejs.json and https://endoflife.date/api/python.json and say for each version found:
its end-of-life date, and whether it is past that date today.
If the repo does not say a version, write "not pinned" and the file you checked.
```

Nothing we pin is past end of life. Node.js 24 runs to 2028-04-30, Node.js 22 to 2027-04-30, and Python 3.12 to 2028-10-31, by endoflife.date. We read the same dates there afterwards.

The useful finding was a gap, not a date. Claude reported 90 lines that start `/usr/bin/node` in our server units, and 4 that start `/usr/bin/python3`. We recounted and found 86 node lines in 85 unit files, so the count was off again, but the point stands. The python3 count of 4 was right. Their version is whatever the server has installed, and the repository does not say which. Claude could not check that from the code, said so, and gave the 3 commands to run on the server. It also noted that 2 of our hackathon projects still allow Node.js 20, which reached end of life on 2026-04-30.

## Prompt 5: check a package before you add it

The cheapest vulnerability to fix is the one you never install. Tutorials and older answers often name an exact version, so we tested that case:

```
A tutorial tells us to add jsonwebtoken@8.5.1 to apps/api to sign API keys.
Before anyone runs npm install, check that exact version with the cve server's triage_dependencies tool
(pass it as one package, not a manifest).
Then say: install it, install a different version (which one and why), or do not add it.
Also say if apps/api already has a package that does the same job, with file:line.
```

The answer was "don't add it", with four reasons. The version has 3 known advisories, and the fix is version 9, a major jump. The package is built for Node.js, while our API runs on Cloudflare Workers. And our API keys do not need signed tokens at all. Claude found, with file and line, that the repository already makes API keys from 24 random bytes. It also found several places that already sign and check with the built-in WebCrypto API.

A scanner can't tell you that. The best fix here was not to add a dependency, and only something that reads your code can see it.

## Prompt 6: write the ticket, then check every number in it

An audit ends with work someone else can pick up. We asked for a short ticket, and then asked Claude to check its own numbers:

```
Write the ticket for this week's dependency batch, for a teammate who did not see this chat.
Keep it under 250 words: what to change, in what order, the test to run after each step, and what is blocked and why.
Then check every version number, CVE id and file:line in the ticket against what the tools and files returned in this chat.
Under the ticket, list each number you could not trace to a tool result or a file you read. If there are none, say so.
```

The ticket had 3 numbered steps, a blocked item and a "not this batch" line, in about 245 words by Claude's count. Each step named the files to change and the test command to run after it.

The check under the ticket is the part to copy. Claude traced every version, CVE id and file line to a tool result or a file it had read. Then it named 3 things that did not come from a tool. They were the date, an `npm update` command it had never run, and test commands it had put together but not run. The ticket already marked the update command as untested.

Asking for the untraced items is how you catch the kind of slip we found in prompt 1. It does not make Claude perfect, but it moves the guesses into a list where a person can see them.

## How this compares to npm audit

An expert will ask why not just run `npm audit`. After the run, we ran it on the same copy of the repository:

```
npm audit --package-lock-only --json
```

It reported 17 vulnerable packages, 11 of them high. Only 6 of those have an advisory of their own: the same 6 packages Claude ranked. The other 11 are parents, such as wrangler and vitest, listed because they depend on one of the 6. It also counts by package name, so it does not show that undici sits in the lockfile at 2 versions.

For zod, its suggested fix was to install `@x402/evm` at version 0.0.1, marked as a major change. That is a downgrade from the 2.28.0 release we use, not a fix. Claude's plan instead said the zod 3 copies are blocked until the library moves. npm audit is a good scanner, and it flagged the same 6 packages. What it cannot do is read your code, your rules or the registry to say which fix is real.

## Run them in this order

Do not start with all 6. This is the order we would use on a new repository:

1. This week: write the context file and run prompt 1. Count the table yourself.
2. Next week: run prompts 2 and 3 on whatever prompt 1 put first.
3. The week after: run prompt 4 once, and fix any gaps in what your repository pins.
4. From then on: run prompt 5 before every new dependency, and prompts 1, 3 and 6 once a week.

Each time an answer surprises you, add the fact behind it to the context file. After our run, we would add one line: the @x402 libraries hold zod at version 3 until they release a fix. Next week, Claude starts from that instead of finding it again.

The weekly run is the same headless command on a schedule, such as a cron job or a CI job on Monday morning. Keep the human step. Claude does the reading and the first draft, and a person approves the upgrades.

## What this does not do

- It does not prove that your code is safe. Prompt 2 narrowed the risk, but "not sure" was the honest answer.
- It matches exact versions against public advisories. A flaw nobody has reported yet will not show up.
- Five CVEs came back with no CVSS score, because NVD's request limit stopped the lookups. Claude said so, did not invent a score, and offered to ask again.
- The zod CVE has a 2023 id but was published on 2026-10-01, and NVD still showed it as awaiting analysis. Claude noted both.
- It does not do the upgrades. We have not merged this batch yet. The ticket is in our own queue.
- Claude is fast but not exact. Three counts in our run were wrong, so a person reads every output before it goes anywhere.

## If you would rather have it done for you

Our [Dependency report](https://tools.openkrill.app/devdeps) is the same check, done for you from one lockfile. It costs 99 US dollars per repository, or 49 US dollars a month for a weekly report. The linked page says what you get and how payment works.

If you want to try it yourself first, the context file above and the free server are all you need. Start with prompt 1 this week.

## Frequently asked questions

### Can Claude audit a lockfile with more than 300 packages?

Yes. In our run, Claude split 724 package versions into 3 calls of up to 300 each without being told to. The tool's own answer says when a list was cut short, so check that its truncated field is false, or that the batches add up to your total.

### Can Claude tell whether a vulnerability is reachable in my code?

It can narrow the question, not settle it. In our run, it read the advisory, found the 2 schemas that take outside input, and showed both had size limits. It still answered "not sure", because the advisory was unclear and it had not searched every package.

### How much did the run cost?

No usage was billed, because we used a Claude subscription. Claude Code estimated the session at 3.07 US dollars, for 6 prompts and about 11 minutes of Claude time. The vulnerability server takes no key and has no charge.

## Sources

- [OSV.dev](https://osv.dev/): the advisory data behind each match, through the GitHub Advisory Database, read 2026-10-24.
- [GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5): the zod advisory for CVE-2023-54404, read 2026-10-24.
- [CISA Known Exploited Vulnerabilities catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) and [FIRST EPSS](https://www.first.org/epss/): exploitation data, as of 2026-10-23.
- [registry.npmjs.org, @x402/core](https://registry.npmjs.org/@x402/core/latest): version 2.28.0 and its zod range `^3.24.2`, read 2026-10-24.
- [endoflife.date for Node.js](https://endoflife.date/nodejs) and [endoflife.date for Python](https://endoflife.date/python): end-of-life dates, read 2026-10-24.
- [Claude Code memory docs](https://code.claude.com/docs/en/memory): how `CLAUDE.md` is read at the start of each session.
