A vulnerable version in a lock file warrants investigation, but it does not alone show that the package is in the production artifact or that the affected function can run. Check the advisory's exact package and version range, trace how the dependency enters each build, inspect the deployed artifact, and then investigate whether the vulnerable feature has a reachable input and its required conditions. Record the answer per product, release, and environment. If an artifact or caller is missing from the evidence, leave the result unresolved rather than declaring a false positive.
GitHub's dependency graph documentation explains that manifest and lock-file analysis can identify direct and transitive dependencies, while some ecosystems resolve more dependencies at build time. A Dependabot alert identifies a known vulnerable dependency and affected file; the production-use judgment needs further evidence.
Three claims that need separate answers
- Version match: Does this product use the affected package and version described in the advisory? Use the package ecosystem, name, version, and affected conditions, not a similar package name. The OSV schema shows why affected package identities and version ranges are explicit fields.
- Artifact presence: Is that code included in a production image, bundle, function package, mobile binary, or deployed service? A repository can contain several apps and build stages. A dependency used to build an artifact may or may not be present in the artifact.
- Vulnerable behavior: Can a relevant actor or input reach the affected feature under the advisory's conditions? Package inclusion is not the same as calling the vulnerable function; a call is not the same as satisfying its exploit preconditions.
The first two are usually inventory and build questions. The third is a code, configuration, and deployment question. A package may also create risk in CI even if absent from customer-facing production; assess that environment separately rather than losing it under a production-only dismissal.
Illustrative case: a package in the lock file
Illustrative repository: Juniper Analytics. A dependency alert identifies fictional package audit-zip version 2.3.4 in the root lock file. The hypothetical advisory says its archive parser has a flaw when it processes an untrusted archive. These package and advisory names are invented to demonstrate the review; they are not a real vulnerability notice.
The repository produces a customer API image and a separate documentation-job image. The package is pulled in transitively by a report generator used by the documentation job. The security engineer initially hears, “It is dev-only, so it cannot affect production.” That is a hypothesis. Build scripts can copy development dependencies into a runtime layer, bundle code into a single file, or install packages again during image construction. The reviewer checks the actual artifacts and records:
| Check | Evidence in the illustrative case | What it supports |
|---|---|---|
| Advisory identity | Affected package is audit-zip 2.3.4 when the archive parser handles untrusted input. | The lock-file version is in the hypothetical affected range. |
| Dependency path | Root lock file shows report-generator → audit-zip. | Explains why the alert appeared; not proof of deployment. |
| Customer API artifact | Image digest api-D17; final layer inventory and bundle map omit audit-zip and its parser. No runtime install step or dynamic package fetch is configured in the reviewed deployment. | Supports “component not present” for this API image, subject to inventory completeness. |
| Documentation job | Image digest docs-D04 includes audit-zip and invokes the report generator. | Package is used outside the customer API. |
| Documentation input | Job reads repository fixtures; whether an external contribution can alter those fixtures is not yet checked. | CI exposure remains unresolved; “trusted input” is unproved. |
The production API conclusion is bounded to image api-D17 and the inspected deployment. The documentation-job question remains open. If the API image has another layer or plugin download outside the captured inventory, the “not present” conclusion must be revisited.
“Development dependency” describes intent, not the finished product
Separate three environments before making a false-positive decision: the dependency installation used to build, the output distributed to users, and the job that performs the build. Juniper's constructed dependency edge is:
root devDependency: report-generator
-> audit-zip@2.3.4
build workspace: includes report-generator and audit-zip
API artifact api-D17: copies API output only; parser absent in inventory + bundle map
Documentation job docs-D04: includes and calls report-generator
A lock file can retain a dependency that an installation omits. For example, npm's current ci documentation says omitted dependency types remain resolved in the lock file while not being installed on disk. That explains why a development dependency can appear in an alert without appearing in a particular installation. It does not prove what a later build copied or bundled.
Compare two packaging recipes as reasoning artifacts; no build is run here:
| Recipe | Relevant operation | Question answered |
|---|---|---|
| API-only output | Copy selected API output into the final artifact; inspect its inventory and bundle map. | Does this output contain the parser, including bundled or renamed code? |
| Whole-workspace copy | Copy the build workspace, including installed development modules. | A development label alone no longer supports absence from the final artifact. Inspect the copied contents. |
Suppose a packaging refactor changes api-D17's first recipe to the second for api-D18. The old “component absent” conclusion cannot travel with the package name. The exercise inventory for D18 now contains audit-zip; the API has no supplied parser call. The completed decision is present, behavior under investigation, not “definitely exploited” and not “still development-only.” The developer's immediate task is to remove unintended build tooling from the shipped artifact or assess its reachable behavior, then inspect the replacement output.
Finish the build-job question separately
For docs-D04, supply the previously missing workflow facts: external contributors can change archive fixtures; the preview job runs the report generator over those fixtures before maintainer approval; the generator passes their bytes to the affected parser. The job is separate from the API and has no production deployment credentials in this exercise.
That completes input reachability for the documentation job. The hypothetical advisory's affected operation is used on untrusted input, so treat the job as applicable for dependency remediation and review its actual privileges and harmful effect. Do not claim a production API compromise. Conversely, lack of API presence does not justify ignoring a build job that processes contributor-controlled content.
The resulting work order is specific: upgrade or remove the affected parser in the documentation job; retain the API-D17 absence evidence; investigate D18's accidental inclusion separately. An “all clear” dismissal of the root lock-file alert would erase two different results.
Choose the statement your evidence can support
OpenVEX distinguishes component absence, vulnerable-code absence and vulnerable-code execution-path absence. They are different claims. An inventory can support absence only within its completeness limits; a call-path review can support execution-path conclusions only within the reviewed caller/configuration scope. Neither is established by a devDependencies label.
For the actual running-service reconciliation, use the separate guide checking deployed package presence. Here the output is the build-to-product distinction: which artifact contains the component, which build job invokes it, and which alert statement should be rejected or retained.
Keep one advisory from becoming one global verdict
Juniper needs an artifact ledger because the same package identity produces several different work decisions. Use one row per product and release, with the underlying advisory linked once. The current exercise would distinguish API-D17, API-D18 and docs-D04. Do not force all three into one status such as “not used” merely because the alert appeared in one root lock file.
A practical ledger records the build recipe, artifact identity, evidence completeness, package-presence conclusion and next action. For D17, the relevant evidence is the final artifact and bundle map. For D18, the packaging refactor invalidates the absence decision. For docs-D04, the workflow and fixture provenance establish input reachability. These records answer different questions, and a strong result in one row cannot fill a missing artifact in another.
If an upgrade is available and proportionate, the owner need not delay it until every reachability question is settled. Updating or removing an affected dependency can reduce exposure and simplify inventory while investigation continues. Describe the change honestly: it addresses an applicable or potentially applicable package; it does not prove the original production-compromise allegation. Check the replacement artifact and supported application behavior before treating the task as finished.
Avoid losing bundled and renamed copies
An inventory search for the package directory alone can miss code incorporated into a bundle or copied under another path. The exercise uses both an inventory and a bundle map for that reason. In a real review, identify how the build transforms the package and choose evidence that accounts for that transformation. If the mapping is incomplete, narrow the conclusion to the artifacts actually inspected.
The opposite error also occurs: finding a package name in build metadata does not establish that its executable code is shipped. A retained license notice, lock file or source map may refer to components from the build workspace. Treat each match as an inventory lead and identify what bytes or behavior it represents. This avoids turning a text search into either a false assurance or an inflated exposure claim.
A short evidence request for D18 could say: “Supply the final artifact inventory and the packaging operation that copied the workspace. Identify any bundled parser code and whether the deployment can fetch additional packages.” That request is more actionable than asking whether the dependency is generally production or development.
Finally, define the event that invalidates each conclusion. A rebuild with a different recipe, a runtime extension mechanism or a newly enabled parser consumer changes the evidence required. A later advisory may describe a different affected feature in the same package, so the old path review also needs comparison with the new advisory's conditions. Keep the original absence or reachability reasoning available, but do not let the package name become a permanent exemption.
Filled decision for Juniper Analytics
- Product and release: Customer API image api-D17, deployed to production on the recorded release.
- Advisory match: Fictional audit-zip 2.3.4 appears in the root lock file under the documentation report generator.
- Artifact result: The reviewed API image inventory and bundle map show no audit-zip code; the checked build and deployment have no runtime package fetch.
- Decision for this API image: Not affected because the component is absent from this artifact, with the inventory and build evidence attached. Recheck on a new build or packaging change.
- Separate job: The supplied docs-D04 workflow shows contributor-controlled fixtures reaching the affected parser. Remediate that job and assess its privileges; the API-D17 absence decision does not clear it. API-D18 requires a new presence/path decision after its packaging change.
This is a better handoff than “false positive.” Engineering knows which image is cleared and which workflow still needs review. If the package is present but the vulnerable function appears unreachable, follow the code-path reachability guide. For a general disposition record, use the scanner false-positive guide.