- Common Lisp 98.4%
- Nix 1.6%
Add standard SBOM ingestion, PURL/CPE normalization, CPE-aware indexing and matching, immediate alert reconciliation, upgrade migration, documentation, and tests. |
||
|---|---|---|
| .github/workflows | ||
| install | ||
| src | ||
| tests | ||
| web | ||
| .envrc | ||
| .gitignore | ||
| cve-hunt-web.asd | ||
| cve-hunt.asd | ||
| example-init.lisp | ||
| flake.lock | ||
| flake.nix | ||
| README.org | ||
CVE Hunt
- CVE Hunt
- Software-stack alerts
- What changed in 0.2
- What changed in 0.3
- What changed in 0.4
- Install
- Quick start
- Data locations and configuration
- HTTP API
- Library API
- Testing
- Service
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-huntno longer starts a daemon or loads a config file as a side effect. - Replaced the broken CVE path builder and stale
recent_activities.jsonassumptions. - 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, andRESOLVEDhistory 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.