Cve discovery Tool
  • Common Lisp 98.4%
  • Nix 1.6%
Find a file
nsaspy b932ef338f
Some checks failed
CI / Nix packages (push) Failing after 2s
CI / Common Lisp tests (push) Failing after 33s
CI / merge-gate (push) Failing after 1s
Import software stacks from CycloneDX and SPDX SBOMs (#3)
Add standard SBOM ingestion, PURL/CPE normalization, CPE-aware indexing and matching, immediate alert reconciliation, upgrade migration, documentation, and tests.
2026-09-09 11:51:32 -04:00
.github/workflows Migrate CVE Hunt to Tek9 (#1) 2026-09-08 18:05:25 -04:00
install Migrate CVE Hunt to Tek9 (#1) 2026-09-08 18:05:25 -04:00
src Import software stacks from CycloneDX and SPDX SBOMs (#3) 2026-09-09 11:51:32 -04:00
tests Import software stacks from CycloneDX and SPDX SBOMs (#3) 2026-09-09 11:51:32 -04:00
web Add software-stack CVE filters and alerts (#2) 2026-09-09 11:36:53 -04:00
.envrc sync files 2023-09-05 04:33:54 -04:00
.gitignore Migrate CVE Hunt to Tek9 (#1) 2026-09-08 18:05:25 -04:00
cve-hunt-web.asd Import software stacks from CycloneDX and SPDX SBOMs (#3) 2026-09-09 11:51:32 -04:00
cve-hunt.asd Import software stacks from CycloneDX and SPDX SBOMs (#3) 2026-09-09 11:51:32 -04:00
example-init.lisp Add software-stack CVE filters and alerts (#2) 2026-09-09 11:36:53 -04:00
flake.lock Migrate CVE Hunt to Tek9 (#1) 2026-09-08 18:05:25 -04:00
flake.nix Import software stacks from CycloneDX and SPDX SBOMs (#3) 2026-09-09 11:51:32 -04:00
README.org Import software stacks from CycloneDX and SPDX SBOMs (#3) 2026-09-09 11:51:32 -04:00

CVE Hunt

CVE Hunt

CVE Hunt is a local CVE search, synchronization, and software-stack alert service written in Common Lisp. Version 0.4 uses Tek9 0.2, an embedded LMDB document database.

The source of truth remains the official CVEProject/cvelistV5 repository. CVE Hunt stores complete CVE JSON 5.x records, including CNA and ADP containers, inside Tek9 and maintains durable secondary indexes for:

  • state;
  • year;
  • assigner;
  • update timestamp;
  • CWE;
  • CVSS severity;
  • vendor;
  • product.

Named software stacks, saved filters, alert rules, deduplication state, and the alert inbox are also durable Tek9 documents. Nothing requires a networked database.

Software-stack alerts

Create a stack, add the exact software you operate, save a reusable filter, and join both into an alert rule:

./cve-hunt stack create production
./cve-hunt stack add production --vendor Apache --product Log4j --version 2.14.1
./cve-hunt stack add production --vendor PostgreSQL --product PostgreSQL --version 16.4

./cve-hunt filter create urgent-rce --severity critical --min-score 9.0 --keyword execution
./cve-hunt alert create production-urgent --stack production --filter urgent-rce
./cve-hunt alert events --alert production-urgent --unread
./cve-hunt alert ack EVENT-ID

The alert create command immediately backfills matching CVEs already in Tek9. Every later sync evaluates only new, changed, or deleted CVEs. The durable inbox emits these transitions:

Event Meaning
MATCHED A CVE newly matches the stack and filter.
UPDATED The CVE or the rule evidence materially changed.
RESOLVED A prior match no longer matches or the CVE was deleted.

Repeated evaluation of identical data emits nothing. Events contain the CVE title, severity, numeric scores, CWEs, affected products, references, and the specific stack components that matched. Acknowledgement changes inbox state but does not erase the evidence.

CVE Hunt understands exact versions and the CVE 5.x lessThan, lessThanOrEqual, defaultStatus, and version changes fields. Omitting a component version watches all affected versions for that vendor/product. Use * as the vendor when the CVE producer does not use a stable vendor name.

Import stacks from SBOMs

Version 0.4 can populate or refresh a stack directly from CycloneDX 1.x or SPDX 2.x JSON. Format detection is automatic:

syft /srv/my-application -o cyclonedx-json=production.cdx.json
./cve-hunt stack import production production.cdx.json

./cve-hunt stack import production release.spdx.json --replace

Imports preserve Package URLs and CPE 2.3 identifiers as evidence while normalizing supplier, package name, and version into the fields used by alert matching. Nested CycloneDX components are included. SPDX NOASSERTION values are ignored, and PURL/CPE data fills missing package identity fields.

Imports merge into an existing stack by default. --replace makes the SBOM the entire authoritative inventory. An import immediately re-evaluates every alert using that stack, so added packages can emit MATCHED and removed packages can emit RESOLVED without waiting for the next feed sync.

Stack commands:

Command Effect
stack create NAME Create an empty named stack.
stack add NAME --product P [--vendor V] [--version X] Add one component.
stack remove-component NAME ... Remove an exact component tuple.
stack import NAME SBOM.json Merge a CycloneDX/SPDX inventory.
stack import NAME SBOM.json --replace Replace the stack from an SBOM.
stack list / stack show NAME Inspect stack documents.
stack delete NAME Delete an unreferenced stack.

Filter commands:

Command Effect
filter create NAME [FILTERS] Create or replace a saved filter.
filter run NAME [--limit N] Run it against current CVEs.
filter list / filter show NAME Inspect saved filters.
filter delete NAME Delete an unreferenced filter.

Alert commands:

Command Effect
alert create NAME --stack S [--filter F] Save, enable, and backfill a rule.
alert run NAME Re-evaluate and print only newly generated events.
alert enable NAME / alert disable NAME Control continuous evaluation.
alert events [--alert A] [--cve ID] [--unread] Read the durable inbox.
alert ack EVENT-ID Acknowledge one event.
alert delete NAME Remove a rule while retaining historical events.

Writes are serialized through one actor-style mailbox. Each batch and its index updates commit in one LMDB transaction. Read queries use Tek9 snapshots and do not wait for the writer.

What changed in 0.2

  • Removed CouchDB, its credentials, the login-refresh workaround, Docker, and the destructive Raspberry Pi installer.
  • Loading :cve-hunt no longer starts a daemon or loads a config file as a side effect.
  • Replaced the broken CVE path builder and stale recent_activities.json assumptions.
  • Synchronization now records the exact imported Git commit in Tek9 and imports the CVE files changed between commits. A missing or diverged watermark causes a safe full reconciliation.
  • Initial imports are chunked, restartable, and skip unchanged records.
  • Added indexed query, record deletion, index rebuild, stats, CLI, HTTP API, synchronous hooks, tests, and CI.
  • Updated the feed URL from the old personal fork to the official CVE Project repository.

What changed in 0.3

  • Added version-aware software stacks stored in Tek9.
  • Added named filters with keyword and minimum CVSS score constraints.
  • Added continuous, deduplicated alert rules with durable match state.
  • Added MATCHED, UPDATED, and RESOLVED history plus acknowledgement.
  • Added post-commit alert hooks and stack/filter/alert HTTP read endpoints.
  • Added referential checks so a live rule cannot lose its stack or filter.

What changed in 0.4

  • Added automatic CycloneDX and SPDX JSON inventory import.
  • Added PURL and CPE 2.3 normalization with evidence retention.
  • Added nested component handling, semantic deduplication, merge, and replace modes.
  • Added CPE-only CVE matching for records without usable vendor/product fields.
  • Added immediate alert reconciliation after every SBOM refresh.

Install

Install SBCL, Git, LMDB, and Quicklisp. Clone both projects into Quicklisp local projects:

git clone https://github.com/lost-rob0t/tek9.git ~/quicklisp/local-projects/tek9
git clone https://github.com/lost-rob0t/cve-hunt.git ~/quicklisp/local-projects/cve-hunt

Load or build the command:

(ql:quickload :cve-hunt)
(asdf:make :cve-hunt)

The produced cve-hunt executable uses the same commands documented below.

Quick start

The first sync clones the official feed and imports it into Tek9. The full baseline is large; later syncs only reconcile files changed between Git commits.

./cve-hunt sync
./cve-hunt get CVE-2021-44228
./cve-hunt query --severity critical --cwe CWE-502 --limit 25
./cve-hunt query --keyword deserialization --min-score 8.0
./cve-hunt query --vendor Microsoft --year 2026 --json
./cve-hunt stats

Commands:

Command Effect
sync Fetch the official feed and reconcile changed records.
sync --no-pull Reconcile the current local checkout without network access.
import [PATH] Reconcile every CVE record under a checkout or cves/ tree.
get CVE-ID Emit the complete stored JSON record.
query FILTERS Query Tek9 indexes and combine filters.
stack ACTION Create and maintain software inventories.
filter ACTION Save and run reusable CVE filters.
alert ACTION Manage rules and the durable alert inbox.
stats Show record count, source commit, and Tek9/LMDB statistics.
rebuild-indexes Rebuild all secondary indexes from stored documents.
serve Synchronize forever at the configured interval.

Query supports --state, --year, --assigner, --cwe, --severity, --vendor, --product, --updated-after, --updated-before, --keyword, --min-score, --limit, and --json. A limit of zero means unlimited.

Data locations and configuration

Defaults follow XDG:

Data Default
CVE checkout ~/.local/share/cve-hunt/cvelistV5/
Tek9 database ~/.local/share/cve-hunt/tek9/
Init file ~/.config/cve-hunt/init.lisp

Environment variables:

Variable Meaning
CVE_HUNT_REPOSITORY CVE feed checkout directory.
CVE_HUNT_DATABASE Tek9 database directory.
CVE_HUNT_SOURCE_URL Git clone URL.
CVE_HUNT_SYNC_INTERVAL Service interval in seconds; default 600.
CVE_HUNT_IMPORT_CHUNK_SIZE Import transaction batch size; default 1000.
CVE_HUNT_MAP_SIZE LMDB virtual map ceiling in bytes; default 64 GiB.
CVE_HUNT_DURABILITY full, metadata-lazy, or nosync.
CVE_HUNT_INIT Optional Lisp init file path.

Full durability is the default. The map size is virtual address space, not a preallocated file.

Copy example-init.lisp to the init path when Lisp-level hooks or custom configuration are useful. The file may mutate cve-hunt::*config* and register hooks with add-new-cve-hook, add-updated-cve-hook, or add-deleted-cve-hook. Register alert delivery integrations with add-alert-hook. Hooks run only after a Tek9 transaction commits; one failing hook is warned about without rolling back stored CVE data.

HTTP API

The optional :cve-hunt-web system provides a read-only localhost API:

(ql:quickload :cve-hunt-web)
(cve-hunt.web:start-web :address "127.0.0.1" :port 8080)
Route Purpose
GET /api/v1/health Health and backend identity.
GET /api/v1/cve?id=CVE-2021-44228 Exact record lookup.
GET /api/v1/cves?severity=CRITICAL&limit=25 Indexed query.
GET /api/v1/stacks[?id=NAME] List or fetch software stacks.
GET /api/v1/filters[?id=NAME] List or fetch saved filters.
GET /api/v1/alerts[?id=NAME] List or fetch alert rules.
GET /api/v1/alert-events?unread=true Read alert events.
GET /api/v1/stats Record count and imported source commit.

The server binds to 127.0.0.1 by default. Authentication and TLS belong at a reverse proxy before exposing it beyond the local host.

Library API

(ql:quickload :cve-hunt)

(defparameter *db*
  (cve-hunt:open-cve-database :path #P"/var/lib/cve-hunt/tek9/"))

(cve-hunt:fetch-cve *db* "CVE-2021-44228")
(cve-hunt:query-cves *db* :cwe "CWE-502" :severity "CRITICAL" :limit 10)
(cve-hunt:close-cve-database *db*)

Keep the database open for the life of the process. Route mutations through a cve-writer when multiple threads can write.

Testing

(ql:quickload :cve-hunt/tests)
(asdf:test-system :cve-hunt/tests)

The tests exercise CVE ID/path normalization, JSON 5.2 CNA+ADP extraction, Tek9 persistence and every secondary index, actor error propagation, hooks, Git commit diff parsing, alert lifecycles, CVE version ranges, CycloneDX/SPDX imports, PURL normalization, and CPE-only matching.

Service

install/cve-hunt.service is a hardened systemd unit template. Create the cve-hunt user and /var/lib/cve-hunt yourself, install the compiled executable at /usr/local/bin/cve-hunt, review the unit, and then enable it. The project no longer curls an installer into a root shell or creates a CouchDB container.