A Go-based dependency vulnerability scanner that goes beyond a flat list of CVEs — it traces how a vulnerable package entered your build, scores risk based on exposure depth and fix availability, checks whether your code actually imports the vulnerable package, tracks scan history over time, and generates a plain-English risk brief using an LLM.
Why this exists
Most dependency scanners show you a wall of CVE IDs and CVSS scores with no context on whether they actually matter for your project. This tool answers three questions existing tools often leave to you to figure out manually:
- How did this vulnerable package get into my build? — Is it a direct dependency, or did it arrive transitively through three layers of other packages I never chose?
- Do I even use it? — A vulnerable package sitting unused in your dependency tree is a very different risk than one your code actively imports.
- What changed since my last scan? — Is this a new finding, or something that's been sitting there for weeks?
Architecture
cmd/scanner/ entrypoint — CLI wiring
internal/
manifest/ parses go.mod/go.sum, resolves the full module graph
via `go list -m all` and `go mod graph`
vulndb/ OSV API client — batch querying + per-vuln detail fetch,
alias deduplication across source databases
graph/ dependency graph traversal — finds all paths from your
root module to a vulnerable transitive dependency
scoring/ CVSS v3.1 base score computation from vector strings,
weighted by hop depth, fix availability, and import reachability
reachability/ parses your actual .go source files to check whether a
vulnerable package is directly imported anywhere
store/ Postgres persistence — scan history, findings, and
diffing against the previous scan
report/ LLM-generated plain-English risk brief (Groq / Llama 3.3 70B)
Pipeline
go.mod / go.sum
│
▼
manifest.ListAllModules() ──▶ resolved module list (direct + indirect, deduplicated)
│
▼
vulndb.QueryBatch() ──▶ OSV API batch lookup + per-ID detail fetch + alias dedup
│
▼
manifest.GetDependencyGraph() ──▶ `go mod graph` edges
│
▼
graph.FindPathsToModule() ──▶ root → ... → vulnerable package (all paths, any hop depth)
│
▼
reachability.ImportedPackages() ──▶ is this package actually imported in source?
│
▼
scoring.ComputeFinding() ──▶ CVSS base score × depth weight × fix weight × import weight
│
├──▶ store.SaveScan() ──▶ Postgres — persists findings, diffs against last scan
│
└──▶ report.GenerateBrief() ──▶ LLM-written summary (optional, --brief)
Example: a real multi-hop finding
Running this against a project using spf13/viper surfaced a transitive vulnerability three layers deep — a case a direct-dependency-only scanner would completely miss:
[6.2] GO-2026-5970 — Infinite loop on invalid input in golang.org/x/text
path: Krane → github.com/spf13/viper@v1.21.0 → github.com/sagikazarmark/locafero@v0.11.0 → github.com/spf13/afero@v1.15.0 → golang.org/x/text@v0.28.0
not directly imported (dependency-of-a-dependency only)
viper was added intentionally; golang.org/x/text was never chosen directly — it arrived as a dependency of a dependency of a dependency. The tool surfaces this chain explicitly rather than just listing the vulnerable package in isolation.
Scoring model
FinalScore = CVSSBaseScore × depthWeight × fixWeight × importWeight
| Factor |
Values |
Rationale |
| CVSS base score |
0–10, computed from the OSV-provided CVSS v3.1 vector string |
Standard severity baseline |
| Depth weight |
1.0 (direct) → 0.55 (4+ hops) |
A direct dependency is more immediately your responsibility than something four layers removed |
| Fix weight |
1.15× if no fix version exists yet |
Findings you can't yet resolve are ranked slightly above equally-severe-but-fixable ones |
| Import weight |
1.0 if directly imported in source, 0.4 if not |
A package present in the build graph but never actually called is real but lower-priority risk |
CVSS v3.1 base score is computed directly from the vector string (attack vector, complexity, privileges required, user interaction, scope, and the confidentiality/integrity/availability impact triad) per the official FIRST.org specification, not just passed through from a pre-computed field.
Known limitations
- Import-level, not function-level, reachability. The reachability check confirms whether your source files import the vulnerable package at all — it does not (yet) confirm whether the specific vulnerable function is actually called. True call-graph reachability (via
golang.org/x/tools/go/callgraph) is a documented next step, not yet implemented.
- Go modules only. The manifest parser is built around
go.mod/go.sum and the go toolchain; other ecosystems aren't supported.
- OSV as the sole vulnerability source. NVD and GitHub Advisories are consulted only insofar as OSV aggregates them; no direct NVD/GHSA API integration.
Usage
# Basic scan
go run . <path-to-project-dir>
# With Postgres persistence (tracks scan history, flags new findings)
go run . --db "postgres://user:pass@localhost:5432/krane?sslmode=disable" <path-to-project-dir>
# With an LLM-generated plain-English risk brief
go run . --brief <path-to-project-dir>
# Both together
go run . --db "postgres://user:pass@localhost:5432/krane?sslmode=disable" --brief <path-to-project-dir>
Environment variables
| Variable |
Purpose |
GROQ_API_KEY |
Required for --brief — Groq's free tier, OpenAI-compatible endpoint, llama-3.3-70b-versatile |
A .env file in the project root is loaded automatically if present (via godotenv).
Local Postgres setup
sudo apt-get update && sudo apt-get install -y postgresql postgresql-contrib
sudo systemctl start postgresql
sudo -u postgres psql -c "CREATE USER krane WITH PASSWORD 'krane_dev_password';"
sudo -u postgres psql -c "CREATE DATABASE krane OWNER krane;"
psql -h localhost -U krane -d krane -f migrations/0001_init.sql
Roadmap
- Manifest parsing (direct + resolved module graph)
- OSV batch vulnerability matching with alias deduplication
- Transitive dependency graph traversal
- CVSS v3.1 scoring engine
- Import-level reachability weighting
- Postgres persistence with scan-to-scan diffing
- LLM-generated risk brief
- Function-level (call-graph) reachability analysis
- Webhook-triggered scanning on push (GitHub webhook, reusing Krane's ingestion pattern)
- Redis caching layer for OSV responses (once running as a persistent service)
- Test coverage for pure functions (dedup, scoring, graph traversal, reachability)
Most commercial Software Composition Analysis tools (Snyk, Endor Labs, Socket, Black Duck, Sonatype, JFrog Xray) show raw CVE data or, at the high end, full call-graph reachability. This project sits deliberately in between: real transitive-path tracing and import-level reachability, with an LLM layer that turns the structured findings into a readable brief — a synthesis step most existing tools skip in favor of raw data dumps.