Back to blog

Building a Better npm Dependency Auditor with Conductor

Alex Devlin Alex Devlin Content Engineer
Last updated: · 8 mins read

3 possible outcomes for the question of "Should we adopt this package?" - automatic approval, human review, or automatic rejection

This article talks about mixed API calls executed in parallel through a single Conductor workflow, with its read-only parts also exposed as MCP tools - all without needing to install any SDKs or additional dependencies!

The Issues with npm audit

The Node Package Manager (npm) runs the world’s largest software registry, and that popularity makes it a target. Security problems come up regularly, from typosquatted packages that steal CI/CD secrets to incidents involving widely used packages.

npm’s built-in safeguard, npm audit, is a useful but limited tool. It can only run on packages that have already been installed, and its output is notoriously noisy. Its warnings about transitive dependencies often come with little context about whether they matter or how to fix them.

A better audit would run before anything is installed, separate real risks from noise, and explain how to fix the ones that matter.

Building a Better Auditor

I built a custom Conductor workflow that utilizes multiple APIs to retrieve contextual information about any public npm package, including its dependencies and disclosed vulnerabilities, for a more holistic security audit of each requested package. It can be found on my personal GitHub repository here - it may be moved to the public Conductor OSS awesome-conductor-apps repo in the future.

The read-only child workflows are also exposed as Model Context Protocol (MCP) tools, so any agent or CI/CD pipeline can call them.

How They Compare

npm auditconductor-npm-dependency-auditor
When it runsAfter a package has been downloaded, since it audits what’s in a defined project.Before you download or adopt anything, without needing a defined project, since the workflow takes the package name and version as inputs.
What you getA list of advisories. It fails a CI job on any vulnerability (or any above --audit-level), but every other judgment is up to the project maintainers.A clear final decision that includes full reasoning, an AI summary, and a recommended version update where applicable.
Advisory dataThe npm registry’s advisory feed, which only comes from the GitHub Advisory Database.Multiple sources including OSV.dev, which combines several databases (such as GitHub’s and OpenSSF’s lists of malicious packages).
Transitive dependenciesShows each vulnerable dependency’s path and severity. npm audit fix only updates within the version ranges its parents allow; anything else needs --force, which can install breaking changes.Checks every dependency, then works out the lowest version that clears every advisory, for the package and each dependency. Flags fixes that need a new major version or don’t exist, and the AI suggests npm overrides.
LicensesNot checked.Checks every dependency’s license against an optional allowlist; unknown licenses are escalated for human review.
DeprecationNot reported. npm install does print deprecation warnings, but they’re easy to miss in the install log.Explicitly flags deprecated versions and deprecated packages. Wholly deprecated packages are rejected automatically, and any deprecation blocks automatic approval.
Project health signalsNone considered.OpenSSF Scorecard, GitHub stars, GitHub open issues, total downloads, release date, and DeepWiki’s maintenance notes.
Build provenanceChecked by a separate command, npm audit signatures.Reads attestations from deps.dev, then flags an unverified build - or one built from a different repository - to the AI.
Community reportsNot checked.Hacker News stories about the package’s security, marked as posted before or after the release.
Record of the decisionNone by default.Every decision is filed as a GitHub issue for historical tracking.

How It Works

External Sources

  • The npm registry has public APIs that allow the workflow to retrieve metadata about any public npm package, including its latest version and download counts.
  • OSV.dev is an open vulnerability database that aggregates known vulnerabilities in npm packages (among other ecosystems) from a variety of sources.
  • deps.dev is a Google-owned service that has dependency graphs for public npm packages.
  • DeepWiki has AI-powered documentation for public GitHub repositories - including many that host npm packages.

The workflow also searches through stories on Hacker News that connect the package in question to a security problem, for an added layer of human context. This is particularly useful for tracking down past incidents, such as the 2021 hijack of ua-parser-js. As an added bonus, the HN search API does not need any authentication, so we can use it without setting up any API keys or tokens.

Each source is reached through one of Conductor’s built-in tasks, so there’s no client code to write: gRPC tasks for deps.dev, HTTP tasks for npm, OSV.dev, and Hacker News, and a Call MCP Tool task for DeepWiki.

Workflow Hierarchy

The auditor is made up of four nested workflows, structured so that each one can also be run on its own:

dependency_adoption_review
├─ assess_npm_package
│ └─ scan_npm_package
└─ record_dependency_decision

Dependency Adoption Review Workflow

The top-level workflow takes a package name and an exact version. It runs assess_npm_package to reach a decision, then record_dependency_decision to act on it.

The dependency_adoption_review workflow: two sub-workflow tasks, assess_npm_package followed by record_dependency_decision.
dependency_adoption_review only coordinates two sub-workflows: one assesses the package, and the other records the decision to adopt it (or not).

Assess npm Package Workflow

This is the primary heavy-lifting workflow, also exposed as the POST_assess MCP tool. It gathers evidence in parallel from deps.dev, OSV.dev, npm, DeepWiki, and Hacker News, then an LLM gives its verdict and fixed rules decide the outcome.

The gather_evidence fork in the assess_npm_package workflow: six parallel branches (a Hacker News search and its summary, the scan_npm_package sub-workflow, DeepWiki over MCP, the npm latest version and download counts over HTTP, and deps.dev project data over gRPC) that meet at join_evidence. (opens the full-size image in a new tab)
assess_npm_package gathers evidence in six parallel branches over gRPC, HTTP, and MCP. Click the diagram to see the full workflow.

Scan npm Package Workflow

This child of assess_npm_package is the other workflow exposed as an MCP tool, POST_scan. It collects security facts only, with no LLM: advisories, licenses, and deprecation for the package and its whole dependency tree.

scan_npm_package loops through every dependency on deps.dev, then asks OSV.dev for each advisory's severity and fix. Click the diagram to see the full workflow.

Record Dependency Decision Workflow

This workflow acts on the assessment’s decision. If there is any uncertainty, a human review is started; otherwise, the package is approved or rejected automatically. Every outcome is then filed as a GitHub issue.

The route_decision switch in the record_dependency_decision workflow: a human_review task for the default case, and tasks that record AUTO_REJECT and AUTO_APPROVE decisions, before the branches rejoin. (opens the full-size image in a new tab)
record_dependency_decision approves, rejects, or asks a person, then files a GitHub issue for every outcome. Click the diagram to see the full workflow.

MCP Tool Creation and Exposure

Both assess_npm_package and scan_npm_package are exposed as MCP tools through the Orkes MCP Gateway so that they can be easily integrated into any existing CI/CD pipeline or agentic workflow without installing any SDKs or deploying any workers. They are read-only: an agent can use them to check a package, but they never install anything or file an issue.

Pictured below is a sample workflow called npm_upgrade_planner_agent, which shows how to use these workflows in a single agentic workflow: it calls scan_npm_package as an MCP tool on each package version it considers, and runs assess_npm_package as a direct sub-workflow integration for each version it recommends, all in parallel.

To run this workflow, follow the instructions in this README file to configure your own Orkes account and confirm that the MCP tools are available.

The npm_upgrade_planner_agent workflow. discover_npm_tools lists the MCP Gateway's tools and prepare_agent_context checks that the scan tool is available. Inside the agent_loop, next_action picks a step and route_step either asks the LLM to propose versions (propose_versions) or calls the POST_scan MCP tool (call_npm_tool), then record_result and save_state keep track of the results. After the loop, check_recommendations and assess_if_needed run an assess_npm_package sub-workflow for each recommended version in a dynamic fork (assess_recommended_versions, then join_assessments). Finally, summarize_assessments, write_plan, write_plan_summary and finish_plan write the upgrade plan, and post_plan_to_github files it as a GitHub issue.
npm_upgrade_planner_agent loops through a series of steps picked by code: scan a version with the POST_scan MCP tool, ask the LLM to propose versions, or stop. It then assesses every recommended version in parallel and writes an upgrade plan, which it also posts as a GitHub issue.

Why Scans Go Through MCP but Assessments Don’t

The npm_upgrade_planner_agent calls scan_npm_package through the read-only POST_scan MCP tool because scans are fast: most finish in a few seconds, and even Strapi’s 1,321 dependencies took under 30 seconds in most of my test runs.

It doesn’t use POST_assess, because in Orkes Developer Edition, the MCP Gateway ends any tool call after about 60 seconds with an HTTP 504, even if the route sets a longer timeout. assess_npm_package can take close to or over 60 seconds, mostly while the LLM reasons over the evidence. The assessment keeps running after the call fails, so a retry starts a second assessment and pays for a second LLM call, which wastes compute and tokens.

Instead, the agent runs assess_npm_package as a sub-workflow for each recommended version, all in parallel through a dynamic fork. Sub-workflows have no gateway time limit, so there is no risk of a timeout, even for a package with many dependencies like Strapi. POST_assess is still available for MCP clients outside of Conductor for those who want to use it on smaller packages or don’t mind the risk of a timeout. You can try it by using a curl command, as shown in the README file of the mcp/ directory:

Terminal window
curl -X POST --max-time 700 "<your_mcp_tool_remote_endpoint_here>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your_key_here>" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "POST_assess", "arguments": {"packageName": "@strapi/strapi", "packageVersion": "5.58.0"}}}'

How Decisions Get Made

Every route comes from apply_routing_rules, an Inline task in assess_npm_package that runs right after the LLM gives its verdict. The rules are fixed JavaScript, so the route never depends on how the LLM words its answer.

Flowchart of apply_routing_rules. The inputs are the evidence pack and the AI's verdict. Step 1: if the whole package is deprecated, it's listed as malicious, or it has a CRITICAL advisory with no fixed version, it's auto-rejected. Step 2: if the AI says AVOID and this version is deprecated or its own high or critical advisories have no single fix, it's auto-rejected. Step 3: if all five approval rules pass (the verdict is ADOPT, every license is allowed and recognized, there are no known advisories, the security evidence is complete, and it isn't deprecated), it's auto-approved; otherwise it goes to human review.
apply_routing_rules checks for a rejection first, then for an approval. Anything that is neither goes to a person.

Try It, Then Extend It

Before changing anything, run the auditor as-is. You’ll need an Orkes Conductor cluster (the free Developer Edition works), an OpenAI API key, and a GitHub account; the repo’s setup steps cover the rest. Then run dependency_adoption_review with the files in sample-inputs/ to see all three outcomes: axios@1.20.0 should be approved automatically, express@4.17.1 waits for your review under Executions → Human Tasks, and request@2.88.2 is rejected because the whole package is deprecated.

Orkes Conductor Developer Edition

Your first agentic workflow is 5 minutes away. Everything you need is already there.

Start Building

Once you get the initial auditor working, here are some ideas for enhancements to make it even better:

  • Add more community sources. Hacker News is the only social site checked today, and not every problem gets reported there. Each new site - such as Reddit or Bluesky - can be one more HTTP task in the gather_evidence fork. Note that most sites require an API key, which you’ll need to add to your Orkes Conductor cluster’s secrets.
  • Judge packages by how they’ll be used. npm’s package.json already separates dependencies, which ship with your app, from devDependencies, such as test runners and bundlers, which only run on developer machines and in CI. A flaw that lets crafted input crash a server is serious in the first group, but often unreachable in the second. An optional intendedUse input could let the LLM and routing rules weigh that context.
  • Review a whole project at once. Accept a package-lock.json, which pins the exact version of every dependency, and review each direct dependency in parallel with a dynamic fork, as npm_upgrade_planner_agent does. Then file a single GitHub issue for the whole project containing all the decisions, so that human reviewers can easily follow them.
  • Expose the full review as an approval tool. Publish dependency_adoption_review as a third MCP tool that answers “may I install this?”. The gateway’s Wait Until Tasks setting lets it reply once the assessment is done, and an idempotency key such as <package>@<version> stops retries from filing duplicate issues.

Closing Thoughts

Even as a first version, this auditor meets the goals set out at the start of this article. It checks a package before anything is installed, separates real risks from noise, and explains how to fix the ones that matter, down to the lowest version that clears every advisory.

Just as important, the AI advises, but code and people make the decision. The LLM’s verdict is only one input to apply_routing_rules, and any case the rules can’t settle goes to a human reviewer. That’s the main trust point: an LLM can take part in a security decision, summarizing evidence that a person would otherwise have to dig up, without becoming its point of failure.

Running on Conductor also makes the auditor dependable. If a step fails, the run can be retried from that step without gathering the evidence again. It also scales with the package: a single scan pages through all 1,321 of Strapi’s dependencies, and the agent assesses every version it recommends in parallel.

And it’s built once, but called many ways. assess_npm_package runs on its own, as a sub-workflow of dependency_adoption_review or npm_upgrade_planner_agent, or as the POST_assess MCP tool. However it’s called, it makes the same API calls, gathers the same evidence, and applies the same rules, so a coding agent and a person reviewing a package are held to the same standard.

Finally, the pattern goes well beyond npm. Gather evidence in parallel → summarize it with an LLM → let fixed rules decide → send unclear cases to a person → record the outcome: that flow fits almost any decision where a wrong “yes” is costly, such as vetting a new third-party vendor or approving a new marketing campaign.