Command Line Interface#
This section describes the various commands available.
All commands below are run from the Vulnerability-Lookup project directory using poetry run.
Core#
Start all the services#
$ poetry run start
Stop all the services#
$ poetry run stop
Start/stop the backend (Valkey and Kvrocks)#
$ poetry run run_backend --start
$ poetry run run_backend --stop
Start/stop all enabled feeders#
$ poetry run feeders_manager --start
$ poetry run feeders_manager --stop
Start only the website#
$ poetry run start_website
Restart only the website#
$ poetry run restart_website
Dump a source in a JSON file#
$ poetry run dump --feed nvd
Bootstrap from Vulnerability-Lookup dumps#
Download the NDJSON files published by a trusted Vulnerability-Lookup instance (for example, the CIRCL dump directory), then import the directory into a fresh instance while Kvrocks is running:
$ poetry run import_dump /path/to/dumps
$ poetry run index_vulnerabilities
The filename (without .ndjson) becomes the source name. Existing
vulnerability documents are never overwritten by default — a skipped document
only gains metadata and source-index membership it does not have yet, so the
command is safe to resume and re-running it never re-triggers notifications.
Pass --replace to replace existing documents. Collaborative SQL exports
(comments, bundles, sightings, and KEV entries) are ignored, documents whose
shape is not recognized are counted and reported without aborting the file,
and a failing file does not block the remaining dumps.
Dumps produced by the current dump command annotate each line with the
physical storage key and index score (vulnerability-lookup:id and
vulnerability-lookup:score), which the importer uses to rebuild storage
faithfully. Older dumps without these annotations still import, with the
identifier derived from the document content and the file’s modification time
used as the index score — freshness-based features (recent listings, since=
queries) will then treat those documents as modified at import time.
After bootstrapping, enable and run the normal feeders to receive regular
updates. Dumps are intended for one-time initial bootstrap, not periodic
synchronization: do not schedule recurring dump downloads and imports (a
daily cron, for instance). Without --replace an import never updates a
document that already exists, so a polled corpus silently goes stale — and
repeatedly fetching full dumps shifts significant bandwidth costs onto the
publisher, who may rate-limit or block clients seen doing it. The feeders and
the API’s since= parameter exist precisely so nobody has to re-download the
whole dataset.
Run an individual feeder manually#
$ poetry run nvd_importer
$ poetry run cvelist_importer
$ poetry run github_importer
See pyproject.toml under [project.scripts] for the full list of available feeder commands.
Update the documentation#
$ cd docs; poetry run make html
The build reads the API reference from docs/_static/files/swagger.json, which is
not written by hand – regenerate it first with
dump_openapi whenever the API changed.
Web service#
This section describes the main commands related to the web service.
All Flask commands are prefixed with poetry run flask --app website.app.
Database#
Init the database:
$ poetry run flask --app website.app db_init
Backup the PostgreSQL database (also runs automatically during poetry run update):
$ poetry run flask --app website.app db_backup
Generate a database models diagram:
$ poetry run flask --app website.app db_diagram
User management#
Create a user:
$ poetry run flask --app website.app create_user --login <login> --email <email> --password <password>
Create an admin:
$ poetry run flask --app website.app create_admin --login <login> --email <email> --password <password>
List all users:
$ poetry run flask --app website.app user_list
Delete a user:
$ poetry run flask --app website.app user_delete --login <login>
Retrieve a user’s API key:
$ poetry run flask --app website.app user_get_api_key --login <login>
Data management#
Update MISP warning lists (the administrator will be prompted to run this during updates):
$ poetry run flask --app website.app update_warninglists
Update the local copy of the GCVE registry (can be scheduled via cron):
$ poetry run flask --app website.app update_gcve_registry
Import OSI-approved licenses:
$ poetry run flask --app website.app import_osi_approved_licenses
Import source code languages:
$ poetry run flask --app website.app import_languages
Clean sightings by source or pattern:
$ poetry run flask --app website.app clean_sightings
Backfill sightings from existing data:
$ poetry run flask --app website.app backfill_sightings
Creates sightings retroactively from all existing comments (seen), bundles (seen,
one per vulnerability), and KEV entries (exploited). Each sighting inherits the
creation_timestamp of its source object. The command is idempotent: running it
multiple times will not create duplicates, as it skips any entry whose source URL
already exists in the database.
Regenerate the OpenAPI specification#
The API reference published in the documentation is a snapshot of the specification,
committed at docs/_static/files/swagger.json. This command rewrites it from the API
object itself, so no instance has to be running and the result cannot be an older
deployment’s:
$ poetry run flask --app website.app dump_openapi
Two things make this preferable to curling /api/swagger.json off a live instance.
The output is stable: flask-restx serialises the methods of an endpoint out of a
set, so two dumps of an unchanged API otherwise differ by well over a thousand lines
of reordering, and the diff of a real change is unreadable. And the instance UUID
that reaches the schema as the documented default of every
vulnerability_lookup_origin field is replaced by a fixed example, so the published
reference does not advertise whichever instance happened to produce it
(--example-origin chooses another).
The specification only covers what the instance exposes: the namespaces behind
user_accounts, cna and cpe_enabled are registered at import time, so an instance
with one of them off has no routes to document for it. The shipped samples alone are
three paths short (/cna_credential/, /cna_credential/{credential_id} and
/cpe_editor/status/{vulnerability_id}). Rather than quietly publishing the smaller
file, the command refuses to write and names the modules to turn on; pass
--allow-partial if you really do want only what this instance exposes.
--check writes nothing and exits non-zero when the committed file is not what the
current code would produce:
$ poetry run flask --app website.app dump_openapi --check
That is the form the OpenAPI workflow runs on every pull request, which is what keeps
the snapshot honest. It needs no storage, cache or database – the specification comes
from the API object, which touches none of them – only the samples with every optional
module enabled, the profile .github/workflows/openapi.yml writes before checking.
Regenerating from a full instance reproduces that byte for byte.
A pre-commit hook regenerates the snapshot for you when you touch
website/web/api/ or bump the version in pyproject.toml
(tools/regenerate_openapi_snapshot.py). It cannot be the gate – it runs on whoever
is committing, with whatever modules they happen to have enabled, and
pre-commit install is a choice each contributor makes – so it is built to step aside
rather than get it wrong: it checks that the instance can produce the complete
reference before doing anything, and skips with a one-line reason when it cannot. What
it does not do is pass quietly when the generator is broken; that fails, with the
error. When it does regenerate, the commit stops so you can stage the result and look
at it.
Note
A relative --output is taken from VULNERABILITYLOOKUP_HOME: importing the
application changes the working directory. The command prints the path it actually
used.
Health and integrity checks#
Report whether the derived state indexes still account for the corpus. Nothing else surfaces a stale index: every read path that trusts one is quietly wrong while the UI, the API and the logs look normal.
$ poetry run flask --app website.app index_health
Count EUVD identifier invariant breaches — records carrying several CVEs or none,
pointer disagreements, and published CVEs with no EUVD — and store the result for the
/metrics endpoint to report. Exits non-zero when an invariant is broken, so it can be
scheduled and left to speak up:
$ poetry run euvd --integrity-scan
It walks the whole identifier space, so run it on a schedule (daily is ample) rather than expecting the metrics endpoint to compute it per scrape. See Metrics.
Prepared statistics figures#
The EUVD statistics surfaces (the public statistics page and the analytics dashboard) read figures that are prepared in advance rather than recomputed per visitor: at ~380k records, counting severities or scanning the known-exploited catalogue on request would not answer in a page load.
Two commands produce them, and both are meant to run on a schedule:
# counts per year and status, top vendors, top assigners, and the CVSS
# distribution -- all derived from the stored records
$ poetry run index_vulnerabilities --source euvd
# average time to exploit -- spans PostgreSQL (KEV listing dates) and Kvrocks
# (publication dates), so it is computed as a job and stored as a snapshot
$ poetry run euvd_seed_counters --stats-scan
--stats-scan refuses to overwrite the stored figure when it finds no exploited
KEV entries at all: an empty catalogue table is indistinguishable from an import
that has not run yet, and the previous snapshot is the better answer.
Whatever the cadence, run the reindex at least once before trusting any counter on a new or restored instance: VL’s derived indexes are rebuilt from the stored records, and a stale index makes a “by status” figure report a fraction of reality while looking entirely normal.
Background services#
Launch the email notification service:
$ poetry run flask --app website.app notify_users
Launch the synchronization service (see Synchronization service for details):
$ poetry run flask --app website.app sync