Pre-Install Dependency Check: Gate a Package Before It Installs
Most dependency controls run after the package is already on disk. The scanner reads the lockfile in CI, the pull request check runs after the commit, and the SBOM is generated after the build. By then the install script has executed, the developer has moved on, and the version sits in a branch that someone has to unwind.
A pre-install dependency check moves the decision to the install command itself. Before npm install, pip install or cargo add resolves anything, one exact version is assessed: known vulnerabilities, malicious-package intelligence, license policy and release age. The verdict comes back in seconds, and a rejected version is never fetched.
This matters more now that coding agents run install commands too. An agent that picks a library from its training data and runs the install does not read your security policy first. The gate has to sit on the command, for humans and agents alike.
Why the install command is the cheapest place to stop a bad package
Every later control pays a higher price for the same decision. A PR guardrail catches the version after a developer has written code against it. A CI scan catches it after the branch has been pushed. A production scanner catches it after it has shipped.
Each step adds people, context switches and a revert.
The install command is different in three ways:
- Nothing has happened yet. No code depends on the package. No install script has run. A reject costs the developer one retry with a different version.
- The input is small and exact. A package name, an exact version and an ecosystem. That is enough to look up advisories, malicious-package evidence, license and publish date without reading a single project file.
- The person who chose the package is still looking at it. A reject with a reason and an alternative lands while the choice is fresh, not three days later in a bot comment on a pull request.
Install-time checks also close a window that lockfile scanning cannot. A malicious version can be published, installed and pulled from the registry within hours. SLSA's threat model separates the choice of a dependency from the integrity of the artifact: both need a check, and the install command is where the choice is made.
What a pre-install dependency check has to answer
One exact version, five questions. If any of them is unanswered, the check should say so rather than pass the package.
- Does this exact version carry a known vulnerability? Not the package in general. The version. A range like
^4.17.0cannot be checked because it does not name one. - Is there malicious-package intelligence on it? Typosquats, hijacked maintainer accounts and install-script payloads are supply-chain advisories, not CVEs. The check needs both feeds.
- Does its license pass your policy? A copyleft license in a shipped product is far cheaper to refuse at install than to remove after a release.
- How old is the release? A version published yesterday has not been through anyone's detection pipeline. A minimum release age turns that observation into a rule.
- Does the name exist in the public registry? This catches typos and package names an agent invented. An invented name that an attacker later registers is a known supply-chain path.
Two more properties separate a useful gate from a noisy one. First, the check must distinguish "no known issue" from "no data". Missing scanner results, a missing license or a missing publish date are a reason to review, not a pass.
Second, a local policy must not override a central rejection. A developer can tighten the rules on a laptop. They should not be able to loosen them.
Three verdicts, and what to do with each
A check that returns only pass or fail forces every unknown into one of two buckets. Three verdicts work better.
- Reject. A known vulnerability, malicious intelligence or a policy violation on this exact version. Pick another version and check that one. A good check suggests a candidate version and assesses it too, so the retry is already covered.
- Review. Something the check could not confirm: scanner coverage, license or publish date is missing. Read the reasons before adding the package. Treat it as an unknown, not a pass.
- Consider. No known issue on this exact version. It is advice, not an approval. The package will pull in transitive dependencies and may run install scripts, and neither has been assessed yet.
A clean verdict covers the package version itself. It does not cover what the package resolves to, what its install scripts do, or which registry the lockfile points at. The pre-install check is the first gate. The guardrail on the lockfile at the pull request is the second.
Coding agents install packages too
A coding agent adds dependencies the same way a developer does: it decides a library is needed, writes the install command and runs it. It does not pause to check an advisory database, and it will run a versionless install that resolves to whatever the registry serves today.
Telling the agent to be careful in a system prompt is not a control. A gate for coding agents needs four properties:
- It runs on every action, and the agent cannot skip it. A tool the agent may call is advice. A hook the agent host runs before every shell command is a control.
- It fails open on its own errors. If the check cannot reach the service, times out or is not signed in, the action should run with a notice that it was not checked. A gate that halts the agent when the gate is down gets uninstalled within a week.
- It sends the minimum. The package name, exact version and ecosystem are enough. The prompt, the full command line and file contents should never leave the machine.
- It tells the agent what to do instead. A deny that names the finding and an alternative version turns a stop into a retry. A bare "denied" turns it into a workaround.
Versionless installs need a special rule. npm install lodash gives the gate nothing to check. The right response is to assess a recommended exact version and hand the agent the exact command to run.
The gate also has to see manifest edits. An agent that writes a dependency straight into package.json or pyproject.toml and then runs npm ci or poetry install never issues a direct install command. Either the manifest change or the project install has to be checked, one direct dependency at a time.
What the pre-install check does not cover
Know the blind spots before you rely on the gate.
- Lockfile installs. npm ci installs exactly what the lockfile says. A version that reached the lockfile through an update bot or a merged branch has already passed the install moment. The check on the pull request is the control there.
- Transitive dependencies. The check assesses the version you named. What it pulls in is assessed after resolution, by the lockfile scan.
- Install scripts and registry origin. A package that is clean by advisory data can still run a postinstall script, and a project override can redirect a name to a different source. Those are findings for the PR gate and the SBOM, not for the install check.
- Anything that is not an exact version. Ranges, tags, URLs, paths and Git references cannot be assessed. The gate should let them through with a notice and ask for an exact version next time.
The complement to all four is a minimum release age enforced by the package manager itself. npm, pnpm, Yarn, Bun, uv, pip and Deno can each refuse a version younger than a window you set, in a config file committed to the repository root. That setting applies to everyone who clones the repository and to CI, with no server call at all. The Package-Manager Cooldown reference lists the setting and the minimum manager version for each, with one caveat: an older manager version ignores the setting silently, so pin the manager too.
Running the pre-install dependency check with Heeler
Heeler runs one assessment at three entry points: the CLI for a developer or a script, MCP for an assistant, and agent hooks for a coding agent's install commands. Each sends only the package name, version and ecosystem.
From the CLI
The Heeler CLI command dependency-check assesses one exact version before you add it. It returns reject, review or consider, with the evidence, the unknowns and the policy source, and it also assesses Heeler's recommended alternative version when that differs from the one you asked about.
heelercli dependency-check lodash 4.17.21 --package-ecosystem npm
Versions must be exact, and a GitHub Action needs its full 40-character commit SHA. --fail-on reject,review makes the command exit 1 on either verdict, which is how you wire it into a pre-commit or CI script. --format json returns the full assessment.
The local license and minimum-age policy in .heeler.yaml applies on top of the server result, and a permissive local policy never weakens a server rejection. The CLI command reference lists every flag.
From an MCP client
The Heeler MCP server exposes the same check as the package tool with action: "risk". An assistant passes exact coordinates and gets back known vulnerabilities, malicious-package evidence, your license policy, release age and a candidate version. Missing evidence is reported as unknown, and a candidate version is not an approval to install. A separate sbom_assessment tool accepts a CycloneDX 1.6 SBOM and reports the known CVEs in it, so an assistant can assess a whole manifest after resolution.
Gating a coding agent's install with Heeler agent hooks
Agent hooks connect Heeler to the native hook system of the coding agent, so the check runs before each shell command and file edit without the agent choosing to call it. One install per machine or project:
heelercli hook claude install --block
Replace claude with the hook name for your agent. Claude Code, Codex CLI, Cursor, GitHub Copilot CLI, Gemini CLI, OpenCode and nine others are supported.
Notify is the default mode: the install runs and Heeler reports what it found. --block denies an install or manifest edit when Heeler has a complete, positive finding, and tells the agent the reason and the recommended version so it can retry. A versionless install gets the exact, checked command to run instead.
Two properties of the hook matter most to a security team. A Heeler failure never blocks the agent: if the service is unreachable, the CLI is not signed in or the check times out, the action runs with a notice that it was not checked. The hook sends only the package name, exact version, ecosystem, repository identity and base branch; the prompt, the full command line and file contents stay on the machine.
The check covers project installs such as npm ci and edits to package.json, pyproject.toml, pom.xml and the other manifests. heelercli hook claude doctor confirms the install. The Agent Hooks docs cover each agent, scope and mode.
The install gate is the first layer. PR Guardrails evaluate the lockfile on every pull request, including a Dependency Version Minimum Age guardrail that catches a too-new version that reached the lockfile by any path.
What to do this week
- Decide the verdict policy. Reject blocks. Decide whether review blocks too. In a CI script it should. For an agent in Notify mode, report it and move on.
- Put the check on the human path. Document
heelercli dependency-checkas the step before adding a package, and add it to the pre-commit or CI script with--fail-on. - Put the check on the agent path. Install the hook for every coding agent in use. Start in Notify, read what it reports for two weeks, then move to Block.
- Set a minimum release age in the package manager. Commit the config file to each repository root, pin the manager version so the setting is not silently ignored, and use the same window in the PR guardrail and in Auto-fix.
- Make exact versions the norm. Ranges, tags and Git references pass through the gate unchecked. Pin, and let automated remediation handle the upgrades.
- Keep the second gate on. Transitive dependencies, install scripts and lockfile-only changes are caught at the pull request, not at install. Confirm that guardrail is enabled.
FAQ
What is a pre-install dependency check?
A pre-install dependency check assesses one exact package version for known vulnerabilities, malicious-package intelligence, license policy and release age before the package manager fetches it. It runs at the install command, so a rejected version is never downloaded and no code is written against it.
Does a clean result mean the package is safe to install?
No. A clean result means the checks found no known issue on that exact version. Transitive dependencies, install scripts and registry origin are assessed after resolution, by the lockfile scan on the pull request.
How is this different from a package-manager cooldown?
A cooldown is one rule, minimum release age, enforced locally by npm, pnpm, Yarn, Bun, uv, pip or Deno with no server call. A pre-install dependency check adds vulnerability, malicious-package and license evidence on the exact version. Use both: the cooldown costs nothing, and the check answers the questions the cooldown cannot.
Can a coding agent bypass the hook?
The hook runs in the agent host before every shell command and file edit, so the agent does not choose whether to call it. A command the hook cannot parse, such as an install inside a pipe or subshell, runs with a notice rather than silently. Install heelercli in a directory the agent cannot write to, because the first heelercli on the agent's PATH is the one that runs.
What happens when the check service is unreachable?
The install runs, with a notice that says it was not checked and why. A gate that blocks on its own outage gets removed, so the hooks deny only on a complete, positive finding.
Heeler runs the same dependency assessment from the CLI, from MCP and from the hooks of fifteen coding agents, with PR Guardrails behind it for everything that resolves after the install. See how the check fits the way your developers and their agents add packages. Get a demo


.jpg)

