Dependency Vulnerability Report Template
A template for documenting individual dependency vulnerabilities, including exploitability, impact, remediation plans, and risk acceptance.
Overview
Raw scanner output doesn’t make decisions. When Snyk or Trivy dumps two hundred findings into a JSON file, someone still has to answer the questions that matter: is the vulnerable function reachable in our code? Which service is exposed? Who owns the fix, and by when?
A Dependency Vulnerability Report Template turns one finding into a decision-ready document. It pairs the CVE facts (severity, affected versions, patched versions) with your project’s runtime context (reachability, internet exposure, compensating controls), so a developer can start remediation and a security lead can defend the priority without re-running the scan.
This template is designed for one finding per report: a single CVE in a single project. If you need a consolidated view of an infrastructure-wide scan across many hosts, images, and repos, use the Vulnerability Scan Report Template instead. If you only need a fast go/no-go call during triage, the Dependency Vulnerability Triage Template is lighter — use it first and come back here for the documented record.
When to Use
- After a scheduled or PR-triggered SCA scan produces a finding that needs human review.
- When a high-profile CVE drops and every team must answer “are we affected, and where?” on a deadline.
- Before a release gate or security audit, to show that findings are tracked rather than buried in a scanner dashboard.
- When a customer, vendor, or auditor asks for evidence of how a specific finding was handled.
- To feed findings into a vulnerability management program with named owners and due dates.
When not to use it: for aggregate reporting across many findings and assets: that’s the scan report’s job. And if the vulnerable component isn’t a third-party dependency but your own code, an incident or SAST finding template fits better. One page per finding is the unit that keeps ownership honest.
Prerequisites
- A completed SCA scan from Snyk, OWASP Dependency-Check, Trivy, Grype, or GitHub Advanced Security. Keep the JSON output — it’s the report’s source of truth.
- Access to the affected repository and its dependency manifest (
package-lock.json,requirements.txt,pom.xml,go.sum). - A scoring baseline. CVSS is the starting point; you will extend it with exploit signals in the prioritization step below.
- A remediation SLA agreed with engineering, so “Critical” means the same deadline to every reader.
- An SBOM if the dependency ships in more than one service — it tells you everywhere the package lives, not just where the scanner happened to look.
The Template
Copy the tables into a ticket, an ADR, or a Markdown file committed next to the service it describes. Fill every row: a report with “TBD” in the owner column is a finding that will age out unpatched.
1. Finding Summary
| Field | Description | Example |
|---|---|---|
| Report ID | Unique identifier | VULN-2026-0042 |
| Date discovered | When the finding was identified | 2026-06-27 |
| Reporter | Person or tool that found it | Snyk bot / Security team |
| Project | Repository or service affected | payment-service |
| Environment | Where the dependency runs | Production, CI, Dev |
2. Vulnerability Details
| Field | Description | Example |
|---|---|---|
| CVE ID | Common Vulnerabilities and Exposures identifier | CVE-2021-44228 |
| Severity | CVSS base score and vector | Critical (10.0, CVSS:3.1/AV:N/AC:L/…) |
| EPSS score | Exploit prediction probability | 0.97 (97th percentile) |
| In CISA KEV? | Confirmed in-the-wild exploitation | Yes — added 2021-12-10 |
| Affected package | Library and ecosystem | log4j-core (Maven) |
| Installed version | Current version in the project | 2.14.0 |
| Patched version | First fixed version | 2.17.1 |
| Attack vector | How the vulnerability is exploited | RCE via attacker-controlled log messages |
| Exploit availability | Is a public exploit known? | Public PoC within 24h of disclosure |
3. Impact Assessment
| Question | Answer |
|---|---|
| Is the vulnerable function reachable? | Yes, via request logging |
| Is the dependency exposed to the internet? | Yes, edge service |
| Does the dependency process untrusted input? | Yes, user-supplied content |
| Is a compensating control in place? | WAF blocks known exploit patterns |
| Estimated business impact | Payment flow interruption |
4. Remediation Plan
| Step | Owner | Due Date | Status |
|---|---|---|---|
| Upgrade dependency to 2.17.1 | Backend team | 2026-07-01 | Not started |
| Run regression tests in staging | QA team | 2026-07-02 | Not started |
| Deploy to production | DevOps | 2026-07-03 | Not started |
| Verify fix via re-scan | Security team | 2026-07-05 | Not started |
5. Risk Acceptance
| Field | Value |
|---|---|
| Can the risk be accepted? | No |
| Justification | Internet-facing RCE with public exploit |
| Risk owner | Engineering manager |
| Approval date | N/A |
| Review date | N/A |
Filled Example
The tables below show a completed report for a realistic scenario: an SCA scan finds CVE-2021-44228 (Log4Shell) in log4j-core 2.14.0 inside a payment service. Notice how every row answers a question a decision-maker would ask; the report reads as an argument for acting now, not as scanner output.
1. Finding Summary
| Field | Value |
|---|---|
| Report ID | VULN-2026-0042 |
| Date discovered | 2026-06-27 |
| Reporter | Weekly Snyk scan, confirmed by security team |
| Project | payment-service (repo: org/payment-service) |
| Environment | Production (internet-facing), staging |
2. Vulnerability Details
| Field | Value |
|---|---|
| CVE ID | CVE-2021-44228 |
| Severity | Critical — CVSS 10.0 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) |
| EPSS score | ~0.97, top percentile |
| In CISA KEV? | Yes — actively exploited in the wild |
| Affected package | org.apache.logging.log4j:log4j-core (Maven) |
| Installed version | 2.14.0 (direct dependency) |
| Patched version | 2.17.1 |
| Attack vector | JNDI lookup triggered by attacker-controlled strings in log messages — remote code execution without authentication |
| Exploit availability | Public exploit code widely available; mass scanning observed |
3. Impact Assessment
| Question | Answer |
|---|---|
| Is the vulnerable function reachable? | Yes — every incoming request is logged with headers and body fields |
| Is the dependency exposed to the internet? | Yes — payment-service sits behind the public API gateway |
| Does the dependency process untrusted input? | Yes — User-Agent, request IDs, and form fields reach log statements |
| Is a compensating control in place? | Partial — WAF has rules for common ${jndi: patterns, but obfuscation bypasses are documented |
| Estimated business impact | Full remote code execution on the payment path; cardholder data exposure, PCI scope breach |
4. Remediation Plan
| Step | Owner | Due Date | Status |
|---|---|---|---|
Upgrade log4j-core to 2.17.1 in pom.xml | Backend team | 2026-06-28 | In progress |
| Add WAF emergency rule set for JNDI strings | Security team | 2026-06-27 | Done |
| Regression-test logging in staging | QA team | 2026-06-29 | Not started |
| Deploy to production under change freeze exception | DevOps | 2026-06-30 | Not started |
| Re-scan and verify dependency tree | Security team | 2026-07-01 | Not started |
5. Risk Acceptance
| Field | Value |
|---|---|
| Can the risk be accepted? | No |
| Justification | KEV-listed RCE on an internet-facing PCI service; no acceptable interim state |
| Risk owner | VP Engineering |
| Approval date | N/A |
| Review date | N/A — escalate daily until closed |
The filled example makes the implicit explicit: severity alone would flag hundreds of findings, but “KEV-listed + internet-facing + reachable via request logging + no authentication required” narrows the answer to “fix today, under an exception to the change freeze.”
How It Works
The five sections answer five different questions, and each one earns its place.
Finding Summary locates the report: who found it, where, and when. When the same CVE resurfaces in another service six months later, this block is what makes the old report discoverable.
Vulnerability Details is the objective part. Everything here should be verifiable against the scanner output, NVD, or the package repository — severity, EPSS, KEV status, versions, exploit availability. If a field can’t be verified, say so rather than guessing.
Impact Assessment is where the report earns its cost. A CVSS 9.8 in a build tool that never runs outside CI is a different problem from a CVSS 7.5 in an edge service. Reachability, exposure, untrusted input, and compensating controls convert a generic score into your risk.
Remediation Plan assigns work. Every step gets an owner and a date, and the plan ends with a verification step. A report that skips re-scanning is a report that gets closed on hope.
Risk Acceptance captures the decision not to patch, when that’s the call. If the answer is “no,” the justification explains why the risk can’t wait; if “yes,” a named owner and review date keep the acceptance from becoming permanent amnesia.
Prioritizing Findings
CVSS measures how bad a vulnerability is in general; it says nothing about how bad it’s for you. Two extra signals fix most of that gap:
- EPSS (Exploit Prediction Scoring System, maintained by FIRST.org) estimates the probability that a CVE will be exploited in the wild in the next 30 days. A CVSS 9.8 with EPSS 0.01 is a different conversation from a CVSS 7.5 with EPSS 0.95.
- CISA KEV (Known Exploited Vulnerabilities catalog) lists CVEs with confirmed real-world exploitation. KEV-listed findings on reachable code skip the queue entirely.
- Reachability analysis in tools like Snyk or Endor Labs checks whether your code actually calls the vulnerable function. Unreachable findings can often be documented and scheduled rather than rushed.
Combining them produces a simple, defensible priority scheme. The SLAs below are common starting points. Adjust to your own vulnerability management policy:
| Priority | Signals | Example SLA |
|---|---|---|
| P0 — Emergency | KEV-listed, or public exploit + internet-facing + reachable | Fix within 72 hours |
| P1 — Urgent | High EPSS (>0.5) or reachable exploit path | Fix within 7 days |
| P2 — Standard | High/critical CVSS, no known exploit | Fix within 30 days |
| P3 — Scheduled | Medium severity, reachable | Fix within 90 days |
| P4 — Backlog | Unreachable, dev-only, or strong compensating controls | Next maintenance cycle |
Record the priority and its justification in the report. Six months later, “why was this CVE fixed in 48 hours while that one waited a quarter?” should be answerable from the document alone.
Variants
- Executive summary report: one page with counts by severity, trend over time, and the top open risks — for leadership that needs posture, not CVE lists.
- CI/CD failure report: captures which finding blocked the pipeline, why the gate tripped, and what unblocks it. Lives next to the failed build.
- Open-source maintainer report: reformatted for upstream disclosure (reproducible trigger, affected versions, suggested fix) following coordinated-disclosure norms.
- Historical trend report: aggregates findings per quarter to show whether posture is improving and where the same classes of bugs keep reappearing.
What Works
- Keep one report per finding (or per CVE/project pair): merged reports lose their owner, deadline, and audit trail.
- Always document exploitability in your context, not just the raw CVSS score.
- Assign an owner and a due date before the report leaves triage; an unowned finding is an unfixed finding.
- Link the report to the scan output, the SBOM entry, and the remediation pull request for full traceability in both directions.
- Re-scan after remediation and record the verification step; close on evidence, not on the merge.
- Review accepted risks on a fixed cadence (quarterly works); conditions change, and so should the decision.
- Use reachability analysis to cut false positives, and keep the justification when you deprioritize a finding. Auditors accept “unreachable, documented”; they don’t accept “ignored.”
- Comment on PRs that introduce vulnerable dependencies: catching a bad dep at review time is cheaper than any report:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
allow:
- dependency-type: "all"
labels:
- "security"
- "dependencies"
open-pull-requests-limit: 10
Common Mistakes
- Remediating by CVSS alone. Score is one input; reachability, EPSS, KEV, and exposure usually matter more.
- Forgetting to regression-test the upgraded dependency under real load; version bumps break behavior, not just APIs.
- Closing the report before a re-scan confirms the fix actually landed in the built artifact.
- Reporting only direct dependencies. Most SCA findings live in transitive packages; the Dependency Upgrade Runbook covers safe upgrade paths for those.
- Accepting risk without a written justification, owner, and review date. Undocumented acceptance is indistinguishable from neglect.
- Ignoring dev dependencies. They run in CI, with your secrets and signing keys, so scan them too:
# npm audit covers the whole tree, dev dependencies included
npm audit
npm audit --omit=dev # production-only view
snyk test --dev # include dev dependencies in Snyk
- Missing OS-level packages in container images. A dependency scan of
package-lock.jsonsays nothing about OpenSSL in your base image — scan the image separately:
trivy image --severity HIGH,CRITICAL myapp:latest
grype myapp:latest --fail-on high
Automating the Report
Automated SCA scan in GitHub Actions
This workflow scans on every dependency-change PR and on a weekly schedule, then emits a Markdown table straight into a report file. Note the continue-on-error: Snyk exits non-zero when it finds vulnerabilities, and without it the report step would never run.
name: Dependency Security Scan
on:
pull_request:
paths:
- "package.json"
- "package-lock.json"
- "requirements.txt"
- "pom.xml"
schedule:
- cron: "0 6 * * 1" # Weekly Monday scan
jobs:
sca-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Snyk scan
uses: snyk/actions/node@master # pin to a commit SHA in production
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high --json-file-output=snyk-results.json --dev
- name: Run Trivy filesystem scan
run: |
trivy fs --format json --output trivy-results.json .
trivy fs --format table --exit-code 0 --severity HIGH,CRITICAL .
- name: Generate vulnerability report
run: |
node -e "
const fs = require('fs');
const snyk = fs.existsSync('snyk-results.json')
? JSON.parse(fs.readFileSync('snyk-results.json'))
: { vulnerabilities: [] };
const trivy = fs.existsSync('trivy-results.json')
? JSON.parse(fs.readFileSync('trivy-results.json'))
: { Results: [] };
const findings = [
...snyk.vulnerabilities.map(v => ({
source: 'snyk', cve: v.identifiers?.CVE?.[0] || 'N/A',
package: v.name, severity: v.severity,
fixedVersion: v.fixedIn?.[0] || 'N/A'
})),
...(trivy.Results || []).flatMap(r => r.Vulnerabilities || []).map(v => ({
source: 'trivy', cve: v.VulnerabilityID,
package: v.PkgName, severity: v.Severity,
fixedVersion: v.FixedVersion || 'N/A'
}))
];
console.log('| Source | CVE | Package | Severity | Fixed Version |');
console.log('|--------|-----|---------|----------|---------------|');
findings.forEach(f => {
console.log('| ' + f.source + ' | ' + f.cve + ' | ' + f.package + ' | ' + f.severity + ' | ' + f.fixedVersion + ' |');
});
" > vulnerability-report.md
- name: Upload report as artifact
uses: actions/upload-artifact@v4
with:
name: vulnerability-report
path: vulnerability-report.md
Two details worth keeping: --json-file-output writes machine-readable results instead of piping stdout (Snyk prints progress text there), and fixedIn is the field that actually carries the patched version in Snyk JSON; fixInfo doesn’t exist in the output schema.
SBOM generation with CycloneDX
Generate a Software Bill of Materials to track every dependency, including transitive ones:
#!/bin/bash
set -euo pipefail
# SBOM for a Node.js project (npm lockfile required)
npx @cyclonedx/cyclonedx-npm --output-format JSON --output-file sbom.json
# SBOM for a Python project (cyclonedx-bom 4.x)
pip install cyclonedx-bom
cyclonedx-py requirements requirements.txt --of JSON -o sbom-python.json
# Enrich the SBOM with vulnerability data via Trivy
trivy sbom --format json --output sbom-enriched.json sbom.json
# Extract the vulnerable packages from the enriched SBOM
jq '.Results[] | select(.Class=="sbom") | .Vulnerabilities[] | {
cve: .VulnerabilityID,
package: .PkgName,
severity: .Severity,
fixed_version: .FixedVersion
}' sbom-enriched.json
The enriched SBOM answers the question a single-repo scan can’t: which of my services ship this package at all? For a worked scanner example, see Python: pip-audit Vulnerability Scan.
Finding the transitive chain
npm audit tells you which package is vulnerable but not how it got in. npm ls <pkg> prints the dependency path. Run it for every vulnerable package to see which direct dependency drags it in:
#!/bin/bash
set -euo pipefail
npm audit --json > audit-output.json 2>/dev/null || true
echo "=== Vulnerable packages and their dependency paths ==="
jq -r '.vulnerabilities | keys[]' audit-output.json | while read -r pkg; do
echo "── $pkg"
npm ls "$pkg" 2>/dev/null | head -15 || echo " not resolved in tree"
done
When the vulnerable package is transitive and the direct dependency hasn’t released a fix, overrides (npm ≥8.3) or resolutions (Yarn) forces a safe version. Pin the override, document it in the report, and set a reminder to remove it once the upstream package catches up.
See Also
- Downloadable companion files (blank template, filled example, CI workflow, SBOM scripts) in the stack-practices-resources repository.
- National Vulnerability Database (NVD) — CVE details and CVSS vectors.
- FIRST EPSS — exploit prediction scores used in the prioritization table.
- CISA Known Exploited Vulnerabilities catalog — the authoritative “is it being exploited” list.
- OWASP Dependency-Check — open-source SCA scanner.
- CycloneDX specification — the SBOM format used above.
- For the broader program this report feeds into, see the Complete Guide to Supply Chain Security.
Frequently Asked Questions
How do I choose which vulnerabilities to fix first?
Triage on four signals: is the vulnerable function reachable in your code, is there a known exploit (CISA KEV, public PoC, high EPSS), is the affected service internet-facing, and what is the CVSS severity. A medium score in a public service usually beats a critical score in an internal batch job.
What if no patched version exists?
Document a compensating control — a WAF rule, network isolation, input validation — with an owner and a review date. If the residual risk is still unacceptable, replace the dependency or disable the vulnerable feature. Never leave the field blank: "no fix yet" is still a documented decision.
Who should receive this report?
The engineering team that owns the fix, security for oversight and SLA tracking, product owners when remediation displaces roadmap work, and compliance when the report feeds an audit or customer questionnaire.
How do I handle a vulnerable dependency I cannot upgrade?
For a transitive package blocked by an upstream dependency, force a safe version with npm overrides, Yarn resolutions, or pip constraints, and document why. For a direct dependency pinned by an old framework, evaluate a compensating control versus a planned migration, and put both options with dates in the remediation plan.
What is the difference between SCA and SAST?
SCA scans third-party dependencies for known vulnerabilities (CVEs). SAST analyzes your own source code for flaws like injection or unsafe deserialization. They cover different layers — you need both, because SCA won't find the bug you wrote and SAST won't find the CVE you imported.
How does this report relate to the SBOM?
The SBOM is the inventory: every package, every version, every service that ships it. This report is the decision: one finding, its context, its plan. Use the SBOM to find every place a new CVE lands, then open one report per affected project.
Related Resources
Vulnerability Scan Report Template
A template for summarizing vulnerability scan findings, including asset coverage, severity distribution, and remediation tracking.
DocDependency Vulnerability Triage Template
Process for deciding what to do when a scanner flags a CVE in a dependency: intake, exploitability and blast radius assessment, CVSS+EPSS+KEV prioritization, patch testing and deployment.
DocDependency Upgrade Runbook
A five-phase runbook for upgrading project dependencies safely, covering preparation, the upgrade itself, validation, rollback, and post-deploy monitoring.
DocVendor Risk Assessment Template
A template for evaluating third-party vendor security and operational risks.
RecipeScan Python Packages for Known CVEs with pip-audit
How to use pip-audit to scan Python dependencies for known vulnerabilities, configure ignore lists, integrate with CI/CD, and remediate findings.
GuideComplete Guide to Supply Chain Security
Secure your software supply chain end-to-end: SBOMs, dependency scanning, Sigstore signing, SLSA provenance, and CI/CD hardening with runnable examples.