A dependency CVE exposure scanner that gives you a single risk score — not just a list of CVEs.
Most security tools tell you which vulnerabilities exist. gr4pe tells you how exposed you actually are, weighted by real-world exploit probability and active exploitation status. It also shows you the full dependency path — direct and transitive — so you know exactly where each vulnerability comes from.
🔍 gr4pe v0.1.0 — dependency exposure scanner
scanning: ./my-app
📄 parsing package.json
🔗 found 42 transitive dependencies
📦 Found 6 direct + 42 transitive dependencies across 1 ecosystem(s)
🌐 querying OSV.dev for 48 packages...
🔎 enriching with EPSS + CISA KEV...
enriched CVSS for 5 vulnerabilities
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RISK SCORE: 100 / 100 [CRITICAL]
[████████████████████████████████████████] 100%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Dependencies scanned: 48
Ecosystems: npm
Vulnerabilities found: 5
Severity breakdown:
critical ▪ (1)
high ▪▪▪ (3)
moderate ▪ (1)
PACKAGE SEVERITY CVSS VULN ID EPSS KEV FIX AVAILABLE
────────────────────────────────────────────────────────────────────────────────────────────────────
jquery critical 9.8 CVE-2020-11023 58.2% KEV → 3.5.0
minimist high 7.5 CVE-2021-44906 0.9% → 1.2.6
node-fetch high 8.8 CVE-2022-0235 0.3% → 3.1.1
lodash high 7.2 CVE-2021-23337 3.3% → 4.17.21
axios moderate 6.5 CVE-2023-45857 0.2% → 1.6.0
🌳 Dependency exposure paths
● direct (3 vulnerable)
├── jquery critical CVSS 9.8 CVE-2020-11023 58.2% KEV
├── minimist high CVSS 7.5 CVE-2021-44906 0.9%
└── node-fetch high CVSS 8.8 CVE-2022-0235 0.3%
● transitive (2 group(s))
├── express (via)
│ └── lodash high CVSS 7.2 CVE-2021-23337 3.3%
└── webpack (via)
└── axios moderate CVSS 6.5 CVE-2023-45857 0.2%
From source (requires Go 1.22+):
go install github.com/theocampos/gr4pe@latestBinary releases (Linux, macOS, Windows): Download from GitHub Releases.
In CI (curl):
curl -sSL https://github.com/theocampos/gr4pe/releases/latest/download/gr4pe-v0.1.0-linux-amd64.tar.gz \
| tar xz && chmod +x gr4pe# Scan current directory
gr4pe
# Scan a specific path
gr4pe --path ./my-app
# Fail CI if risk score exceeds 50 (stricter)
gr4pe --threshold 50
# Ignore a specific CVE (known false positive)
gr4pe --ignore CVE-2023-1234
# Output JSON (for piping into other tools)
gr4pe --json | jq .riskScore
# Write a SARIF report (uploads to GitHub Security tab)
gr4pe --sarif results.sarif
# Preview the PR comment locally (no token needed)
gr4pe --dry-run
# Post results as a GitHub PR comment
gr4pe --comment
# Print version
gr4pe --version| Flag | Default | Description |
|---|---|---|
--path, -p |
. |
Directory to scan |
--threshold, -t |
70 |
Risk score that fails CI |
--fail-on, -f |
high |
Minimum severity to include |
--ignore, -i |
— | CVE ID to suppress (repeatable) |
--json, -j |
— | Output raw JSON |
--sarif, -s |
— | Write SARIF report (default path: results.sarif) |
--comment, -c |
— | Post results as a GitHub PR comment |
--dry-run |
— | Print the PR comment to stdout instead of posting |
--github-token |
$GITHUB_TOKEN |
GitHub token for PR comments |
--github-repo |
$GITHUB_REPOSITORY |
Repository in owner/repo format |
--pr |
$GITHUB_REF |
PR number (auto-detected in GitHub Actions) |
--previous-score |
— | Base branch score for diff display in PR comment |
--version, -v |
— | Print version and exit |
Drop a .gr4pe.yml at the root of your repo to avoid repeating flags in every CI command:
# .gr4pe.yml
threshold: 70
fail-on: high
sarif: results.sarif
ignore:
- CVE-2023-1234 # known false positive
- CVE-2023-5678 # accepted riskCLI flags always override config file values. See examples/gr4pe.yml for a fully annotated template.
- name: Run gr4pe
run: |
go install github.com/theocampos/gr4pe@latest
gr4pe --threshold 70 --sarif results.sarif
- name: Upload to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarifSee examples/gr4pe-ci.yml for the full workflow.
gr4pe can post a markdown comment on every pull request showing the risk score, severity breakdown, vuln table, and dependency tree — with a diff arrow (↑/↓) compared to the base branch.
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Score base branch
id: base
run: |
git checkout ${{ github.base_ref }}
go install github.com/theocampos/gr4pe@latest
echo "score=$(gr4pe --json | jq -r '.riskScore // 0')" >> $GITHUB_OUTPUT
git checkout ${{ github.head_ref }}
- name: Scan and comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gr4pe --threshold 70 --sarif results.sarif \
--comment --previous-score ${{ steps.base.outputs.score }}GITHUB_TOKEN, GITHUB_REPOSITORY, and the PR number are all read automatically from the GitHub Actions environment — no extra configuration needed.
To preview the comment locally without a token:
gr4pe --dry-runSee examples/gr4pe-pr-comment.yml for the full workflow.
score = clamp(Σ (cvss_weight × exploit_factor × direct_factor), 0, 100)
| Factor | Value | Description |
|---|---|---|
cvss_weight |
(CVSS / 10) × 25 |
Maps CVSS 0–10 → 0–25 pts per vuln |
exploit_factor |
3.0× if in CISA KEV |
Actively exploited in the wild |
1.5 + (EPSS × 2.0)× if CVE with EPSS |
Weighted by real exploit probability | |
1.5× if CVE without EPSS |
Confirmed exploit path | |
1.0× otherwise |
Advisory only (GHSA, no CVE) | |
direct_factor |
1.2× direct / 1.0× transitive |
Direct deps are higher risk |
Score bands:
- 🟢 0–30 — low exposure
- 🟡 31–60 — moderate
- 🟠 61–80 — high
- 🔴 81–100 — critical
gr4pe automatically detects transitive dependencies when a lockfile is present alongside the manifest. The exposure tree section of the report groups vulnerabilities by whether they come from a direct or transitive dependency, and traces each transitive vuln back to the direct dep that pulls it in.
| Ecosystem | Manifest | Lockfile for transitives |
|---|---|---|
| npm | package.json |
package-lock.json (v1, v2, v3) |
| Go | go.mod |
built-in (// indirect marker) |
| Ruby | Gemfile.lock |
built-in (DEPENDENCIES section) |
| Rust | Cargo.toml |
Cargo.lock |
| Python | requirements.txt |
— (all treated as direct) |
When a lockfile is present, transitive deps are scanned too and their vulnerabilities are weighted at 1.0× (vs 1.2× for direct) in the risk score.
| Source | What it provides | Auth required |
|---|---|---|
| OSV.dev | Vulnerability data + CVSS scores for all ecosystems | No |
| EPSS / FIRST.org | Exploit probability score (0–100%) | No |
| CISA KEV | Confirmed actively exploited CVEs | No |
| File | Ecosystem |
|---|---|
package.json |
npm (Node.js) |
requirements.txt |
PyPI (Python) |
go.mod |
Go modules |
Cargo.toml |
crates.io (Rust) |
Gemfile.lock |
RubyGems (Ruby) |
gr4pe/
├── main.go # entry point
├── cmd/ # CLI — flags, config loading, orchestration
├── internal/
│ ├── models/ # shared types: Package, Vulnerability
│ ├── config/ # .gr4pe.yml parser
│ ├── version/ # version string (set via ldflags at build time)
│ ├── scanner/ # manifest discovery + OSV.dev querying
│ │ └── parsers/ # one file per ecosystem
│ │ ├── npm.go # package.json + package-lock.json
│ │ ├── pip.go
│ │ ├── gomod.go # detects // indirect
│ │ ├── cargo.go # Cargo.toml + Cargo.lock
│ │ └── gem.go # Gemfile.lock (DEPENDENCIES = direct)
│ ├── enricher/ # EPSS + CISA KEV enrichment
│ │ ├── aliases.go # fetches full OSV records → CVSS + CVE aliases
│ │ ├── epss.go # FIRST.org EPSS API
│ │ └── kev.go # CISA KEV catalog
│ ├── scorer/ # risk score formula
│ ├── sarif/ # SARIF 2.1.0 report writer
│ ├── github/ # PR comment formatter + GitHub API client
│ └── report/ # terminal rendering
│ ├── banner.go
│ ├── table.go
│ ├── tree.go # 🌳 dependency exposure paths
│ ├── summary.go
│ ├── recommendations.go
│ └── colors.go
├── tests/ # unit + integration tests
├── testdata/ # fixture manifests per ecosystem
│ ├── package_json/ # npm fixtures (clean, low, moderate, high, normal)
│ ├── pip/ # PyPI fixtures
│ ├── gomod/ # Go fixtures
│ ├── cargo/ # Rust fixtures
│ └── ruby/ # Ruby fixtures
└── examples/ # CI workflow templates + config example
├── gr4pe-ci.yml # basic scan + SARIF upload
├── gr4pe-pr-comment.yml # PR comment with score diff
└── gr4pe.yml # annotated config file template
Data flow:
Scan(path) → map[ecosystem][]Package (direct + transitive)
QueryOSV(deps) → []Vulnerability
filterIgnored() → []Vulnerability
EnrichOSVDetails() → []Vulnerability (CVSS + GHSA → CVE resolution)
EnrichEPSS() → []Vulnerability (exploit probability)
EnrichKEV() → []Vulnerability (active exploitation flag)
Score(vulns, deps) → Result{RiskScore, RiskLabel, Scorecard, …}
Render(result) → terminal output (table + 🌳 tree)
sarif.Write(result) → results.sarif
github.PostComment() → GitHub PR comment (markdown)
Key design decisions:
- Zero external dependencies — single binary, easy deployment
internal/modelsas the single source of truth for shared types (no circular imports)internal/scanner/parsersis its own package — each ecosystem is isolated in one file- Lockfile parsing is opportunistic — if no lockfile exists, only direct deps are scanned
- Enrichment failures (EPSS, KEV) are soft warnings — offline CI still works
- OSV.dev errors are hard failures — no silent fallbacks, scan accuracy is non-negotiable
go test ./tests/...
# Verbose
go test -v ./tests/...
# With coverage
go test -cover ./tests/...Integration tests make real API calls to OSV.dev, EPSS, and CISA KEV. Run them with the integration build tag:
go test -tags integration -timeout 120s -v ./tests/...
# Only integration tests
go test -tags integration -timeout 120s -run TestIntegration ./tests/...| File | What's covered |
|---|---|
tests/scorer_test.go |
All 4 score bands, clamping, direct vs transitive factor, GHSA vs CVE exploit factor |
tests/parsers_test.go |
All 5 manifest formats + lockfile parsing; direct/transitive detection; parent tracking; deep transitive chains |
tests/scanner_test.go |
Directory walking, empty dir, all testdata scenarios |
tests/comment_test.go |
PR comment formatter: score header, emoji bands, diff arrows, zero-vuln case, vuln table, severity breakdown, direct/transitive tree |
tests/integration_test.go |
Full pipeline per ecosystem: scan → OSV → enrich → score, KEV hit assertion |
| Directory | Ecosystem | Notable packages |
|---|---|---|
testdata/package_json/clean/ |
npm | No known vulns |
testdata/package_json/low/ |
npm | axios@0.21.1 (CVE-2023-45857) |
testdata/package_json/moderate/ |
npm | lodash@4.17.4 (CVE-2021-23337) |
testdata/package_json/high/ |
npm | minimist@1.2.5 + lodash@4.17.4 |
testdata/package_json/normal/ |
npm | jquery@3.4.1 (CVE-2020-11023, KEV, EPSS 58%) |
testdata/pip/vuln/ |
PyPI | pyyaml@5.3.1 (CVE-2020-14343, EPSS 14%) |
testdata/gomod/vuln/ |
Go | gorilla/websocket@v1.4.0 (CVE-2020-27813) + indirect dep |
testdata/cargo/vuln/ |
Rust | regex@1.3.0 (CVE-2022-24713, EPSS 10%) |
testdata/ruby/vuln/ |
Ruby | nokogiri@1.11.0 + rexml@3.2.4 |
PRs welcome! This project is intentionally kept simple — no framework dependencies, stdlib only.