Freshness is a correctness problem
A vulnerability scanner’s most dangerous failure is not an error. It is a scan that succeeds, exits zero, and reports a clean image — because the advisory data behind it predates the vulnerabilities it was supposed to find. Nothing in the result, the timestamp or the status distinguishes that from a correct clean scan. Every other failure in TRUSTIVAN announces itself. A scan that cannot reach a registry fails. A worker that dies has its lease reaped. A stale database announces nothing, so it has to be made to.Two thresholds: degrade, then fail closed
The honest answer to “is this data too old” is not binary. A database two days
old is missing a handful of advisories and is still enormously more useful than
no scan. A database three months old will confidently report a clean image with
dozens of known criticals.
48 hours is chosen because a deployment with working egress is never near it —
the upstream database rebuilds several times a day — so reaching it means
something is wrong: blocked egress, a stale mirror, or a cache directory that is
not actually persisted. Exactly the conditions that otherwise go unnoticed for
months.
An operator who sets the hard limit below the soft one has made a mistake that
would refuse every stale database instantly. The policy widens it rather than
rejecting it at startup: refusing to boot over an ordering slip in two optional
settings is worse than applying the stricter of the two sensibly.
Why the hard limit fails the scan rather than flagging it
Because reconciliation resolves findings a scan did not observe. A scan against a three-month-old database does not merely under-report. It reads its own silence as evidence that everything is fixed, and closes the tenant’s existing backlog — the accepted risks, the triage decisions, the regression history.A stale database is not a reporting problem. It is a data-destruction problem.This is why freshness is assessed before anything is persisted. The check runs against the result’s own metadata and, if the data is unusable, the scan returns an error and writes nothing. A test establishes a real critical, runs a scan against 120-day-old data that finds nothing, and asserts the critical is still open. Freshness is assessed after the scan rather than before it, for a related reason: the engine refreshes its database as part of scanning, so a pre-scan check would refuse the very run that would have fixed the staleness, and would be asserting something about a database other than the one actually used.
Where staleness surfaces
database_state is serialised while the engine identity and database version
are not. Those name internal machinery and are stripped at the API boundary;
database_state names nothing internal and answers a question the user
genuinely has — was this scan looking at current data? — which a green result
cannot otherwise be challenged on.
unusable never appears on a succeeded scan, because such a scan never
succeeds.
Providers
upstream — the default, and its honest limits
upstream pulls the database from the scanning engine’s own public
infrastructure, published as OCI artifacts on ghcr.io and mirror.gcr.io.
Integrity comes from OCI content digests, verified by the registry client during
the pull.
It is convenient, and the configuration comments in this repository say plainly
that it is not an acceptable production configuration. Two reasons, both
operational:
- It makes TRUSTIVAN’s core data path depend on free public infrastructure whose own documentation warns it “could be rate limited”.
- That infrastructure is operated by a company selling a competing product, and a commercial product cannot depend on a competitor’s goodwill for its core data path.
ENV=development). In any deployed
environment there is no default: the backend refuses to start until
VULN_DB_PROVIDER is set, and choosing upstream there also requires
VULN_DB_ACKNOWLEDGE_UPSTREAM=true. That refuses inaction, not upstream: a
deployment can still choose it, deliberately.
mirror — the intended answer, with an unresolved question
A mirror provider exists in internal/vulndb and works: it pulls from an OCI
registry you operate.
mirror with no repositories is a startup error, not a silent
fallback. Falling back quietly would mean an operator who configured a mirror
for a perimeter deployment discovers the mistake as a scan reaching for the
public internet — which either fails confusingly or, worse, succeeds and defeats
the isolation they were configuring.
A mirror must be populated by copying artifacts by digest, never by
rebuilding them, so the digest a customer verifies is the digest the upstream
publisher produced.
Two things must be said about this rather than left implied:
- Configuring a mirror is a technical capability, not a licence. Whether TRUSTIVAN may mirror this data for commercial redistribution is an unresolved legal question. A customer mirroring for their own internal use is a materially different position from TRUSTIVAN mirroring for redistribution, and the code distinguishes neither and grants neither. The open questions are logged as warnings at startup, so an operator running a commercial deployment has seen them at least once.
- Air-gapped operation is a design goal, not a shipping feature. The provider interface exists so that a source inside a perimeter is a configuration change rather than a code change, and the 14-day hard limit was chosen partly to leave a site refreshing monthly room to notice before an auditor does. But the default provider still reaches the public internet, and the question of how an isolated site lawfully obtains the data is open.
Caching
Set it to a persistent path —
/cache, backed by a volume, in the shipped
compose file. Without persistence every worker restart re-downloads roughly
50 MB, which is the difference between a scan taking seconds and taking
minutes.
A cache directory that is silently not persisted is one of the conditions the
48-hour soft threshold exists to catch: nothing else about the deployment looks
wrong, and the age creeps up.
The cache is safely disposable. Discarding the volume costs one slow scan, not
any data. See Upgrades.
Next
- Health and metrics — where freshness is visible to an operator.
- Scanning — the settings around this one.

