Dependency scanner blocks CI on unfixed vulnerability: triage and unblock safely
For developers dealing with CI/CD pipelines that fail on a dependency vulnerability with no upstream patch yet. This runbook shows how to confirm whether the finding is real, reachable, and policy-relevant, then apply the least-risk unblock: upgrade path correction, transitive override, scope reduction, or a time-boxed exception with audit trail.
TL;DR — Your build is usually failing for one of four reasons: the vulnerable package is pulled in transitively when a safe version can be forced, the scanner is flagging a dev/test-only dependency under a production policy, the finding is unreachable/not actually shipped, or your policy has no exception path for "no fix available" cases. The fastest safe unblock is: identify the exact package path, confirm whether it lands in the production artifact, then either override/remove it or add a time-boxed ignore with a ticket and expiry. Reading time: ~6 min
The scenario
It is Tuesday afternoon, you merge what should be a routine patch release, and CI goes red on the security stage. The scanner says a transitive package has a high-severity CVE, but the advisory also says "no fix available". Product wants the deploy out today, the dependency is three levels deep, and your pipeline is configured to fail on high/critical findings. You need to decide quickly whether to remediate, suppress, or change policy without hand-waving your way past audit later.
Symptoms
- CI job exits non-zero during dependency scan, typically with output like:
✗ High severity vulnerability found in package xyz@1.2.3
CVE-2026-12345 - No fix available
Introduced through: app > a@4.0.0 > b@2.1.0 > xyz@1.2.3
Remediation: None available
Tested 842 dependencies for known issues, found 1 issue
Process completed with exit code 1
- npm/yarn/pnpm pipelines often show one of:
npm audit --audit-level=high
# npm ERR! code 1
# 1 high severity vulnerability
yarn audit --level high
# ✖ 1 vulnerability found - Packages audited: 801
# Exited with code 1
- Python pipelines often fail on:
pip-audit -r requirements.txt
# Found 1 known vulnerability in 1 package
# Name Version ID Fix Versions
# xyz 1.2.3 GHSA-.... None
# exit code: 1
- Java/Maven/Gradle scanners show a vulnerable transitive path and fail the build threshold:
[ERROR] Failed to execute goal ...: CVSS 7.5 >= failBuildOnCVSS 7.0
[ERROR] org.example:xyz:jar:1.2.3 vulnerable, no fixed version available
- The advisory page or scanner output explicitly says one of:
No fix availableFixed in: nonePatched versions: <none>Upgrade path not available
- If you inspect the built artifact/container, the vulnerable package may not actually be present, or may only exist in test/dev tooling.
Likely causes
| Cause | How common | Quick check |
|---|---|---|
| Vulnerable package is transitive, and your direct dependency range is preventing a safe subdependency resolution | Very common | ```bash\nnpm ls xyz |
| Scanner is evaluating dev/test dependencies or source manifests instead of the production artifact | Common | ```bash\nnpm audit --omit=dev |
| Finding is real, but the vulnerable code path is unreachable/not shipped in your runtime | Common | ```bash\ndocker run --rm your-image sh -lc 'npm ls xyz |
| Policy is configured to fail on severity alone, with no exception workflow for "no fix available" | Common | ```bash\ngrep -RInE 'audit-level |
| Advisory is a false positive or mismatched package/ecosystem/version due to stale DB/cache | Less common | bash\nrm -rf ~/.cache/*audit* ~/.m2/repository/org/owasp ~/.gradle/caches && rerun the scanner with DB update enabled\n |
Step-by-step diagnosis
- Check whether the advisory truly has no fixed version.
# Use your scanner output first, then confirm on the advisory source it links to.
# If the tool supports JSON, capture it:
npm audit --json > audit.json || true
pip-audit -r requirements.txt -f json > audit.json || true
This is your problem if the advisory metadata says fixAvailable: false, Patched versions: <none>, or equivalent. If there is a fixed version, jump to Fixes → Transitive dependency can be overridden or upgraded.
- Find the exact dependency path that introduces the package.
npm ls xyz
yarn why xyz
pnpm why xyz
mvn -q dependency:tree -Dincludes=com.example:xyz
./gradlew dependencyInsight --dependency xyz --configuration runtimeClasspath
pipdeptree -r -p xyz
This is your problem if the package is not direct and appears under one or more parents you control. Jump to Fixes → Transitive dependency can be overridden or upgraded.
- Confirm whether the vulnerable package is in the production/runtime artifact, not just in source or dev tooling.
# Node container example
docker run --rm your-image sh -lc 'node -e "try{console.log(require.resolve(\"xyz\"))}catch(e){process.exit(1)}"'
# Java example
jar tf build/libs/app.jar | grep -i 'xyz\|BOOT-INF/lib'
# Python example
docker run --rm your-image python -c "import importlib.util; print(importlib.util.find_spec('xyz'))"
This is your problem if the package is absent from the runtime image/jar or only present in test/dev layers. Jump to Fixes → Scanner is checking dev/test dependencies instead of runtime or Fixes → Vulnerability is unreachable or not shipped.
- Check scanner scope and failure threshold in CI.
grep -RInE 'npm audit|yarn audit|pnpm audit|pip-audit|dependency-check|grype|trivy|--severity|audit-level|failBuildOnCVSS|--fail-on' .github .gitlab-ci.yml Jenkinsfile .
This is your problem if the command scans manifests/lockfiles without production filters, or fails on severity with no ignore/allowlist mechanism. Jump to Fixes → Scanner is checking dev/test dependencies instead of runtime or Fixes → Policy needs a time-boxed exception.
- Refresh caches and rerun once before suppressing.
⚠️ Do not delete shared build caches on a busy CI runner unless you know the impact; it can slow every pipeline and, on self-hosted runners, affect other jobs.
# local examples
rm -rf ~/.npm/_cacache ~/.cache/pip-audit ~/.m2/repository/org/owasp ~/.gradle/caches
# then rerun your scanner with DB update/default refresh
This is your problem if the finding disappears or changes advisory/version mapping after refresh. Jump to Fixes → Advisory is false positive or stale database.
Fixes
Transitive dependency can be overridden or upgraded
If a parent package allows a newer safe subdependency, force it.
Node.js:
{
"overrides": {
"parent-package>xyz": "1.2.9",
"xyz": "1.2.9"
}
}
npm install
npm ls xyz
Yarn:
{
"resolutions": {
"xyz": "1.2.9"
}
}
yarn install --mode=skip-build
yarn why xyz
pnpm:
{
"pnpm": {
"overrides": {
"xyz": "1.2.9"
}
}
}
Java/Maven:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>xyz</artifactId>
<version>1.2.9</version>
</dependency>
</dependencies>
</dependencyManagement>
mvn -q dependency:tree -Dincludes=com.example:xyz
If no safe version exists for xyz, upgrade or replace the parent that pulls it in.
Verify it worked:
# rerun scanner and confirm xyz version/path changed or disappeared
Scanner is checking dev/test dependencies instead of runtime
Scope the scan to what you actually ship.
Node.js:
npm audit --omit=dev --audit-level=high
# or install prod-only before scanning
npm ci --omit=dev
Python:
pip-audit -r requirements-prod.txt
Maven:
mvn -q dependency:tree -Dscope=runtime
Container-first approach if your scanner supports images:
docker build -t app:scan .
# then scan the image rather than the source manifest
Trade-off: manifest scanning catches issues earlier; image/runtime scanning reduces noise but can miss dormant code that later becomes shipped.
Verify it worked:
# scanner report no longer includes test/dev-only packages and CI exits 0 or only reports runtime findings
Vulnerability is unreachable or not shipped
If the package is present but the vulnerable feature is not used, document reachability and add a time-boxed ignore. Do not just mute it forever.
Generic ignore file pattern used by many scanners/tools:
<suppressions>
<suppress>
<notes>JIRA-1234: xyz@1.2.3 vulnerable in CLI parser only; app does not invoke parser. Review by 2026-12-31.</notes>
<packageUrl regex="true">^pkg:npm/xyz@1\.2\.3$</packageUrl>
<cve>CVE-2026-12345</cve>
</suppress>
</suppressions>
Or CI flag examples where supported:
# examples vary by tool; prefer advisory+package+expiry, not severity-wide ignores
scanner --ignore CVE-2026-12345 --package xyz@1.2.3
Include in the ticket: package path, exploit preconditions, why your app does not satisfy them, and review date.
Verify it worked:
# CI passes, and the report shows the finding as ignored/suppressed with a reason and expiry reference
Policy needs a time-boxed exception
Add an explicit exception path for no fix available findings instead of disabling the gate.
Example CI shell guard:
set -euo pipefail
scanner --format json > scan.json || true
jq '.vulnerabilities[] | select(.severity=="high" or .severity=="critical")' scan.json > highs.json
jq '.vulnerabilities[] | select((.fixVersion==null or .fixVersion=="" or .fixVersion==false) and (.id=="CVE-2026-12345"))' scan.json > allowed.json
if [ -s highs.json ] && ! diff -q <(jq -S . highs.json) <(jq -S . allowed.json) >/dev/null; then
echo "Blocking: non-allowlisted high/critical vulnerabilities found"
exit 1
fi
Store allowlist entries in version control with owner and expiry:
security_exceptions:
- id: CVE-2026-12345
package: xyz@1.2.3
reason: "No upstream fix; unreachable code path; tracked in JIRA-1234"
owner: "team-app"
expires: "2026-12-31"
Trade-off: this keeps delivery moving, but exceptions become debt. Expire them aggressively.
Verify it worked:
# pipeline passes only for the specific allowlisted finding; a different high-severity issue still fails the build
Advisory is false positive or stale database
Refresh the scanner DB/cache and pin the scanner version if a regression caused bad matching.
# examples; exact cache paths vary
rm -rf ~/.cache/*audit* ~/.m2/repository/org/owasp ~/.gradle/caches
# rerun scanner with update enabled or without offline mode
If the issue is a scanner bug, pin to a known-good version in CI until fixed:
# example pattern
- run: pip install pip-audit==2.8.0
- run: pip-audit -r requirements.txt
Do not suppress before checking release notes/changelog for known false positives.
Verify it worked:
# same dependency set, refreshed DB, finding disappears or advisory/package mapping changes to the correct package/version
Prevention
- Scan both lockfile and built artifact, and compare results. Fail on runtime findings; warn on source-only findings.
npm audit --json > lockfile-audit.json || true
docker build -t app:ci .
# image scan command here if your tool supports it
- Add a checked-in exception file with owner and expiry, and fail CI on expired exceptions.
jq -e '.security_exceptions[] | select(.expires < "'"$(date +%F)'"'")' security-exceptions.json >/dev/null && {
echo "Expired security exception"; exit 1; } || true
- Pin direct dependencies tightly enough to avoid surprise vulnerable transitive ranges, and review lockfile diffs in PRs.
npm ci
git diff -- package-lock.json yarn.lock pnpm-lock.yaml
- Add dependency-path inspection commands to your standard incident template so triage starts with evidence, not guesswork.
npm ls <pkg>
yarn why <pkg>
pnpm why <pkg>
mvn -q dependency:tree -Dincludes=group:artifact
./gradlew dependencyInsight --dependency <artifact> --configuration runtimeClasspath
- Create a policy rule:
no fix availablerequires one of three outcomes within the same PR — remove package, reduce scan scope to runtime, or add a time-boxed exception with ticket.
pull_request_checklist:
- "If scanner says no fix available: attach dependency path"
- "Confirm package present in runtime artifact"
- "If ignored: add owner, ticket, expiry"
- Alert on newly introduced high/critical findings separately from existing allowlisted debt.
# compare current scan to baseline in CI and fail only on net-new highs
jq -S '.vulnerabilities' current.json > current-vulns.json
jq -S '.vulnerabilities' baseline.json > baseline-vulns.json
diff -u baseline-vulns.json current-vulns.json
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI