Skip to content

theocampos/gr4pe

Repository files navigation

gr4pe 🍇

CI Release License

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%

Installation

From source (requires Go 1.22+):

go install github.com/theocampos/gr4pe@latest

Binary 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

Usage

# 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

Flags

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

Config file

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 risk

CLI flags always override config file values. See examples/gr4pe.yml for a fully annotated template.

CI / GitHub Actions

Basic scan + SARIF upload

- 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.sarif

See examples/gr4pe-ci.yml for the full workflow.

PR comment with score diff

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-run

See examples/gr4pe-pr-comment.yml for the full workflow.

How the risk score works

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

Transitive dependency graph

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.

Data sources

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

Supported ecosystems

File Ecosystem
package.json npm (Node.js)
requirements.txt PyPI (Python)
go.mod Go modules
Cargo.toml crates.io (Rust)
Gemfile.lock RubyGems (Ruby)

Architecture

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/models as the single source of truth for shared types (no circular imports)
  • internal/scanner/parsers is 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

Testing

Unit tests

go test ./tests/...

# Verbose
go test -v ./tests/...

# With coverage
go test -cover ./tests/...

Integration 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/...

Test coverage

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

Testdata fixtures

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

Contributing

PRs welcome! This project is intentionally kept simple — no framework dependencies, stdlib only.

About

Dependency CVE exposure scanner that gives you a single risk score — weighted by CVSS, EPSS exploit probability, and CISA KEV. Scans npm, Go, Rust, Python, and Ruby. SARIF output, transitive dep graph, and GitHub PR comments with score diff.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages