Skip to main content

vulnctl

Auditable decisions, not scores.

CVSS base scores are a poor way to decide what to fix first: they ignore exploitation likelihood, exploit availability, your asset exposure, and your risk tolerance. Teams end up drowning in "criticals" that will never be exploited while missing medium-severity CVEs under active attack. vulnctl fuses the public intelligence that actually predicts risk — EPSS, CISA KEV, NVD, OSV, GHSA, and public exploit feeds — evaluates each finding against a declarative SSVC decision tree using your organizational context, and emits a ranked set of Track / Track* / Attend / Act verdicts. Every verdict ships with its full decision path: which input, what value, and which source supplied it. The audit trail is the product — you can defend each call to engineering and leadership instead of hand-waving at a number.

What it does

  • Ingest a list of CVE or GHSA IDs, a CycloneDX SBOM (1.4–1.6), or Grype scanner JSON. GHSA identifiers alias-resolve to CVEs via OSV.
  • Enrich each finding from EPSS (exploit probability), CISA KEV (known exploited + ransomware), NVD (CVSS vector, CWE), OSV/GHSA (affected/fixed versions), and exploit presence (Exploit-DB, Metasploit, nuclei).
  • Decide with a bundled CISA-style SSVC deployer tree — or bring your own with --tree.
  • Explain every verdict with the full path that produced it — and one finding in depth with vulnctl explain <ID>: each source's answer and provenance, plus which single input change would flip the decision. Degraded inputs (a source down, or --offline) are visibly flagged, never hidden.
  • Output a rich table, JSON, SARIF 2.1.0 (GitHub code scanning), or a stakeholder Markdown report — and gate CI with --fail-on.

Install

pipx install vulnctl
vulnctl --version

Requires Python 3.11+ on Linux or macOS. No credentials are required; an NVD API key (optional, for higher rate limits) is read from the VULNCTL_NVD_API_KEY environment variable only.

Enable tab completion for commands, flags, and values with vulnctl --install-completion (see docs/cli.md).

Quickstart (60 seconds)

1. See a verdict and the decision path behind it. This runs entirely from bundled snapshots — no network, no API key:

$ vulnctl enrich CVE-2021-44228 --offline --show-path
                                          vulnctl enrichment
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓
┃ CVE            ┃ Decision ┃ CVSS          ┃ EPSS           ┃ KEV            ┃ Exploits             ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│ CVE-2021-44228 │ ACT      │ n/a (offline) │ 1.000 (p100.0) │ yes ransomware │ EDB·3 MSF·5 nuclei·1 │
└────────────────┴──────────┴───────────────┴────────────────┴────────────────┴──────────────────────┘
sources: epss, exploits, ghsa, kev, nvd, osv · cache hits: 0% avg · degraded: ghsa 1 (offline), nvd 1
                              (offline), osv 1 (offline) · offline mode

CVE-2021-44228 → ACT  (tree cisa-deployer-v1)  [degraded: defaults applied]
  1. exploitation = active  [kev]
  2. exposure     = open  [context]
  3. automatable  = yes  [default]
  4. human_impact = high  [context]
1 finding(s) · 1 ACT · 1 KEV-listed

The path is the point. This is ACT because CISA KEV lists it as actively exploited (exploitation = active, from kev), it's treated as internet-exposed (exposure = open, from the default org context), and mission impact is high. automatable fell back to the tree default here because --offline has no CVSS vector to derive it from — so the verdict is flagged degraded. Drop --offline and the live CVSS vector resolves automatable from data (value_source = cvss), clearing the flag.

For the whole evidence file behind one verdict — every source's answer with its provenance, the fixed versions, and each single input change that would land on a different decision — run vulnctl explain CVE-2021-44228.

2. Feed it your context. Exposure, mission impact, and overrides that no intel source can know come from a small context.yaml:

vulnctl enrich CVE-2021-44228 --context examples/context.yaml --show-path

3. Prioritize a whole SBOM or scanner report:

# CycloneDX SBOM: components resolve to CVEs via OSV, then rank
vulnctl enrich --sbom app.cdx.json --context context.yaml

# Grype JSON straight off a scan (‘-’ reads stdin)
grype my-image:latest -o json | vulnctl enrich --grype - --context context.yaml

4. Gate CI on risk, not raw CVSS. Emit SARIF for code scanning, then fail the build only when something crosses your threshold:

vulnctl enrich --sbom app.cdx.json --format sarif > vulnctl.sarif  # always written
vulnctl enrich --sbom app.cdx.json --fail-on act                   # exit 2 blocks the PR

A complete GitHub Actions gate lives in examples/ci/vulnctl-gate.yml.

How it works

Four strictly-ordered layers, data flowing one way — Ingest → Enrich → Decide → Output. Source adapters are isolated and fail open (a source outage degrades one field, never the run); the SSVC engine is a pure, deterministic tree-walker that records every node visit. Verdicts come from a declarative SSVC tree (bundled: cisa-deployer-v1, the CERT/CC deployer model with CISA's Track/Track*/Attend/Act labels). See FRAMEWORK.md for the architecture.

Documentation

Doc What's in it
docs/cli.md CLI reference: every command, argument, and option
docs/output.md Reading the output: what Track/Track*/Attend/Act mean, the decision path, and every enrichment column
docs/context.md Every context.yaml field, its values, and how each maps to an SSVC decision point
docs/trees.md The YAML decision-tree format and how to author/validate a custom tree
docs/schema.md The --format json output schema (machine-readable schema.json)
docs/exit-codes.md Exit codes and --fail-on semantics for CI gating
docs/releasing.md Cut-a-release runbook: build, sign, publish, verify
SPEC.md · FRAMEWORK.md · CLAUDE.md Product spec, architecture, and contributor conventions

Development

Requires uv and Python 3.11+.

uv sync                       # install deps (incl. dev group)
uv run pytest                 # tests
uv run ruff check .           # lint
uv run ruff format --check .  # format check
uv run mypy src/              # type check
uv run vulnctl --help         # smoke-test the CLI
uv run pre-commit install     # wire the git hook

All four checks (pytest, ruff check, ruff format, mypy) must pass before any commit. The SSVC engine holds a 100% branch-coverage gate in CI.

Security posture

vulnctl is a security tool and holds itself to the bar it enforces: GitHub Actions pinned by full commit SHA, least-privilege token scopes, a committed lockfile, no eval and no pickle of untrusted data, and strict Pydantic validation of every byte of external JSON before it becomes a model. Releases are built with an SBOM (Syft), scanned (Grype, and dogfooded through vulnctl itself), and signed with keyless cosign — see docs/releasing.md.

License

Apache-2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vulnctl-0.2.2.tar.gz (691.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vulnctl-0.2.2-py3-none-any.whl (498.4 kB view details)

Uploaded Python 3

File details

Details for the file vulnctl-0.2.2.tar.gz.

File metadata

  • Download URL: vulnctl-0.2.2.tar.gz
  • Upload date:
  • Size: 691.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vulnctl-0.2.2.tar.gz
Algorithm Hash digest
SHA256 3d3ca12d4d7e89e5223ccccd8a6985ed52904d9718ffe428ab15dd906759f804
MD5 08f26451ceabb79b5f87a95c62ceb8a7
BLAKE2b-256 a286933554f22d1d5c9f35415bba492d685724e707fbe38144de7b49fcdc83af

See more details on using hashes here.

Provenance

The following attestation bundles were made for vulnctl-0.2.2.tar.gz:

Publisher: release.yml on NokiGuard/vulnctl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vulnctl-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: vulnctl-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 498.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vulnctl-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b864d71df63d4325cbd05189ba86474d506f82cf0f8d0eb9934f0b954e487d69
MD5 20caab6129660ca6803277b9bd970b2a
BLAKE2b-256 e2d84f647e79caa5e6c57c9e8957bf3f58a3baa0c0a3d1653154cb2b373f7461

See more details on using hashes here.

Provenance

The following attestation bundles were made for vulnctl-0.2.2-py3-none-any.whl:

Publisher: release.yml on NokiGuard/vulnctl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page