amivuln
amivuln answers one question: which CVEs apply to this package, file or version on this host, taking the patches my distribution already shipped into account?
Generic scanners compare upstream version ranges and report CVEs that your distribution fixed long ago through backports. amivuln asks the distribution’s own security data first, so a package your distro has fixed is not reported as vulnerable, and a security update you have not installed yet is.
$ amivuln pcre2 --format detail
pcre2 10.47-1.fc44.1 (Fedora 44)
CRITICAL 0 HIGH 8 MEDIUM 0 LOW 0 KEV 0 EPSS max 0.00
Fixed in: 10.49-1.fc44 (dnf upgrade pcre2)
Affected:
CVE-2026-103111 HIGH fix: 10.49-1.fc44 EPSS 0.00
...
source fedora-updateinfo ok age 0s required
source kev ok age 0s
source epss ok age 0s
result: complete
Principles
- Precision before coverage. A false positive from a backport is a bug.
- Never degrade silently. Every result lists the status of each source. A required source that failed makes the result incomplete (exit code 2), so an outage can never look like a clean scan.
- Nothing identifying leaves the host. Only the ecosystem, package name and version are sent. No telemetry.
- The output is a contract. JSON with a versioned schema.
- Verifiable releases. Checksums, GitHub build attestations, a cosign signature and an SBOM accompany every release.
Status
Pre-alpha. Fedora and Arch Linux are supported, as are Rocky Linux and
AlmaLinux 8 and 9 through OSV (--distro rocky:9, --distro alma:9). Alpine
and Red Hat Enterprise Linux come later (OSV has no RHEL data). See Data sources for exactly what each
distribution report covers and where the blind spots are.
Start with Installation and the Quick start.
Installation
From a release
Every release at github.com/nubian-ibex/amivuln/releases contains:
- static
musltarballs forx86_64andaarch64, which run on any Linux distribution; gnutarballs forx86_64andaarch64, linked against glibc 2.34 or newer (Debian 12, Ubuntu 22.04, Fedora 35, RHEL 9 and later);.deb,.rpm,.apkand Arch (.pkg.tar.zst) packages that install the binary, the man pages and the shell completions;- a CycloneDX SBOM,
SHA256SUMSand its cosign signature.
The tarballs also contain man/ and completions/.
From crates.io
cargo install amivuln --locked
This builds from source and needs a C compiler (SQLite is compiled in).
cargo binstall amivuln skips the build and downloads the static musl
release archive instead.
Verify what you downloaded
gh attestation verify amivuln-x86_64-unknown-linux-musl.tar.zst --repo nubian-ibex/amivuln
cosign verify-blob SHA256SUMS --bundle SHA256SUMS.sigstore.json \
--certificate-identity-regexp '^https://github.com/nubian-ibex/amivuln/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum -c SHA256SUMS --ignore-missing
The .rpm and .deb packages are not signed with a repository key yet. Check
them with the commands above before installing, and use
rpm -i --nosignature for the .rpm.
Shell completions and man pages
The packages install them. From a tarball, copy completions/amivuln.bash,
completions/_amivuln or completions/amivuln.fish to your shell’s completion
directory and man/*.1 to a directory on MANPATH. amivuln completions bash|zsh|fish also prints them.
Quick start
amivuln nginx # an installed package
amivuln /usr/sbin/nginx # the package that owns a file
amivuln nginx.service # the package behind a systemd unit
amivuln openssl@3.5.1-1 --distro arch # a version, without reading the host
amivuln --all # every installed package
Questions about the whole host
amivuln cve CVE-2024-6387 # is any installed package affected?
amivuln pending # which updates close the most risk, riskiest first
amivuln services # check the package behind every running service
amivuln restart # which processes still run files an update replaced
amivuln --packages-from list.txt # check a package list exported from another host
cve exits 1 when a package is affected. When the distribution data has no
record of the CVE it says so explicitly: that is not a statement that the host
is safe. pending groups subpackages built from one source into one update and
ranks by exploited (KEV) findings, severity and EPSS. restart reads /proc (run it as root to see every process; the output says how many it could not inspect) and exits 1 when something needs a restart. services lists running systemd services that are not packaged without failing the run. A whole-system scan also
prints a host score from A to F (see ADR-0011 in the repository).
Read the result
For every package you get the number of affected findings by severity, how many are known to be exploited (CISA KEV), the highest EPSS score, and the version that fixes them with the command you would run. amivuln never runs that command.
Findings the distribution cannot confirm are listed apart as unverified and are not counted with the affected ones.
Fail a build
amivuln --all --fail-on high # exit 1 on HIGH or worse
amivuln --all --fail-on kev # exit 1 only on exploited CVEs
amivuln --all -f sarif > amivuln.sarif # SARIF 2.1.0 for code scanning
amivuln --all -f csv > amivuln.csv # spreadsheet-safe (formula injection guarded)
Exit codes: 0 clean, 1 findings at or above --fail-on, 2 incomplete,
3 target not resolved, 64 usage error, 70 internal error. Exit code 2
keeps an outage of the advisory data from passing as a clean scan.
A GitHub Actions step
The amivuln-action downloads a release, verifies its attestation and checksum, then runs it. Pin it to a full commit SHA:
permissions:
contents: read
attestations: read
steps:
- uses: nubian-ibex/amivuln-action@5c61781a5497fbcb34401a5b2d2eee54a59e9ebc # v0.1.1
with:
args: --all --fail-on high
The same steps by hand:
- name: Download amivuln
run: |
gh release download --repo nubian-ibex/amivuln --pattern 'amivuln-x86_64-unknown-linux-musl.tar.zst'
gh attestation verify amivuln-x86_64-unknown-linux-musl.tar.zst --repo nubian-ibex/amivuln
tar --zstd -xf amivuln-x86_64-unknown-linux-musl.tar.zst --strip-components=1
- name: Check packages
run: ./amivuln --all --fail-on high
Next: the User guide and the Reference.
User guide
Checking packages
amivuln nginx # installed package by name
amivuln /usr/sbin/nginx # the package that owns this file
amivuln openssl@3.5.1-1 # a specific version, without reading the host
amivuln pkg:rpm/fedora/nginx@1.26.2-1.fc44?arch=x86_64 # a package URL
amivuln --all # every installed package
--file PATH, --package NAME[@VERSION] and --purl PURL are explicit forms
of the same targets and can be repeated. When a name is ambiguous (for
example several kernel versions installed) amivuln fails with exit code 3 and
tells you which selector to use. It never guesses.
Reading the result
The summary prints, per package, the number of affected findings by severity, how many are in the CISA KEV catalog, the highest EPSS score, and the fixed version with the command you would run to apply it. amivuln never runs that command.
Findings the distribution could not confirm are listed apart as unverified
and are not counted with the affected ones. --strict makes them count for
--fail-on.
The last lines list every source that was consulted, its status
(ok, stale, failed, skipped), its age, and whether the result is
complete. A required source that failed makes the result incomplete: the
output says so loudly and the exit code is 2, so an outage can never look like
a clean scan.
Use in CI
amivuln --all --fail-on high # fail the job on HIGH or worse
amivuln --all --fail-on kev # fail only on actively exploited CVEs
amivuln --all --fail-on-epss 0.5 # fail on high exploitation probability
amivuln --all -f json > amivuln.json # keep a machine-readable record
To check a version for another distribution, for example a Fedora package in a
Debian-based runner, pass --distro fedora:44 or --distro arch together
with name@version. Only the ecosystem, package name and version are sent over
the network.
Offline and caching
Responses are cached in $XDG_CACHE_HOME/amivuln/cache.sqlite (default
~/.cache/amivuln). A response younger than cache_ttl (6 h) is used without
asking the network; when the network fails, one up to stale_max_age (72 h)
old is used and reported as stale. --offline never touches the network and
fails closed (exit 2) when the cache has nothing usable. amivuln cache info
and amivuln cache clear manage the cache; --no-cache skips the disk cache.
Configuration
amivuln config init writes a commented default file; amivuln config show
prints the effective configuration and amivuln config validate checks it.
Unknown keys are rejected so typos do not pass silently.
Prometheus and the node_exporter textfile collector
amivuln --all -f prometheus writes text metrics: amivuln_findings{severity,kev},
amivuln_unverified_findings, amivuln_source_up{source},
amivuln_source_age_seconds{source}, amivuln_run_complete and
amivuln_last_run_timestamp_seconds. Labels come from fixed vocabularies, so
no package name, hostname or path reaches the metrics store. Write the output
to a .prom file in the textfile directory through a temporary file and a
rename so the collector never reads a partial file.
Running on a timer
contrib/systemd/amivuln.service and amivuln.timer scan the host every six
hours and write amivuln.prom for the textfile collector, through --output
so the file is replaced atomically. The service runs as a dynamic user with no
capabilities, a read-only file system and a system call filter;
systemd-analyze security amivuln.service reports an exposure of 1.2.
sudo install -m 0644 contrib/systemd/amivuln.{service,timer} /etc/systemd/system/
sudo systemctl enable --now amivuln.timer
Adjust ExecStart and ReadWritePaths when the textfile directory differs.
Exit codes 1 (policy), 2 (incomplete) and 3 (unresolved) still write a valid
file, so the unit only fails on a usage or internal error.
Behind a proxy or a corporate CA
Set the [network] table in the configuration file:
[network]
proxy = "http://proxy.corp.example:3128" # default: HTTPS_PROXY and NO_PROXY
ca_file = "/etc/pki/ca-trust/source/anchors/corp.pem"
user_agent_suffix = "team-sre"
ca_file adds trusted roots to the built-in ones (PEM, at most 1 MiB).
user_agent_suffix is appended to the User-Agent and is the only
host-specific text that can leave the machine, only when you set it. A proxy
URL may carry credentials; amivuln never prints it. To use an internal mirror
instead, set sources.<name>.url.
For AI agents: the MCP server
amivuln mcp speaks the Model Context Protocol over stdio (newline-delimited
JSON-RPC 2.0, protocol revisions 2025-06-18, 2025-03-26 and 2024-11-05). Register
it in a client such as:
{ "mcpServers": { "amivuln": { "command": "amivuln", "args": ["mcp"] } } }
The tools are read-only and each one runs a fixed command line with -f json;
the result is the same versioned report the command line prints.
check_target:targets(1 to 20 package names,name@versionorpkg:URLs) and an optionaldistro.check_cve:idsuch asCVE-2024-6387; scans every installed package.pending_updates,needs_restartandlist_services: no arguments.
There is no shell, no file path input and no way to pass options: a target that
starts with - or holds control characters is refused. The server updates the
local cache like any other run; pass --offline or --no-cache to
amivuln mcp to restrict that. Always read complete and sources in a
result before treating an empty list as clean.
Checking an SBOM
amivuln --sbom bom.cdx.json --distro fedora:44 --fail-on high reads a
CycloneDX JSON document (any 1.x), checks every component whose purl is an
rpm, deb or alpm package and leaves the host alone. A purl whose
namespace disagrees with --distro is an error. Components that are not OS
packages (npm, PyPI, Maven and so on) are not dropped silently: they appear as
unresolved entries, one per purl type with a count, and the run exits with 3.
To gate only on OS packages, filter the SBOM first, for example with
jq '.components |= map(select(.purl // "" | startswith("pkg:rpm/")))'.
Resilient networks
Sources can have mirrors. A source that fails on its primary endpoint is asked
again on each mirror in fallback, in the order written, before it counts as
failed. Every failing call is retried with exponential backoff and jitter
(retries, default 2, at most 10); a Retry-After the server sends is honored
inside the retry budget. An endpoint that fails repeatedly trips a per-endpoint
circuit breaker and is skipped for the remainder of a run (cooldown, 30 s by
default), so a dead mirror does not stall the scan. A slow host can be slowed
down deliberately with min_interval, which spaces requests to the same host
(for example "100ms").
The fallback order is: primary endpoint, then mirrors, then the cache, then
the installed offline database (see the offline database section), then failure. sources in
every output says which of them answered, their age, and whether the result is
complete.
Webhook notifications
A [notify] section sends a compact summary after a check run: host facts,
completeness, score, totals, finding ids. It is opt-in and carries nothing
beyond what the JSON report already states.
[notify]
webhook = "https://ntfy.sh/example-topic" # any endpoint that accepts a POST
bearer_file = "/etc/amivuln/notify-token" # optional; sent as Authorization: Bearer
on = "violation" # violation (default) | incomplete | always
A failed delivery prints a warning and never changes the exit code: the policy decision was made from the scan, not from the notification.
The offline database
amivuln db moves advisory data between hosts that may reach the network and
hosts that may not. The archive is a tar.zst holding the SQLite cache and a
manifest with a SHA-256 of the database; --key and --sig add a minisign
signature over the manifest.
amivuln --all -f json # warm the cache (needs the network once)
amivuln db build --archive db.tar.zst
amivuln db verify db.tar.zst --key amivuln.pub --sig db.tar.zst.minisig
amivuln db pull db.tar.zst # verify and install for this user
amivuln db info db.tar.zst
db pull installs under $XDG_DATA_HOME/amivuln/offline and refuses an
archive older than the installed one (anti-rollback); --force overrides.
Once installed, a run with --offline answers from it and reports those
sources as stale, so an air-gapped host produces the same report shapes with
exit codes that still respect --fail-on. amivuln db verify exits 65 when
an archive is tampered; a regressive db pull also exits 65.
Signing is deliberately not built in: generate the project key with the
minisign tool and sign outside amivuln
(minisign -S -s key.sec -m manifest.json -x db.tar.zst.minisig), so the
secret key never runs through this binary.
Files no package owns
A path target that the package manager does not know (a binary copied into a
container, vendored software) is identified by its own systemd
.note.package when it has one. The package, version and packaging system
come from the file, and the report marks the package with the provenance
embedded_note. A note for another packaging system than the host’s (a deb
note on an RPM host) is refused as unresolved instead of guessed, and the file
is never executed. On Debian and Ubuntu hosts, which have no package database
to ask, the note is the only way to identify a file.
Go and Rust programs nothing owns
When a path target is not owned by a package and has no package note, amivuln
reads the Go modules (build information) or Rust crates (cargo-auditable data)
the program declares and asks OSV about each exact version. Findings show the
lowest fixed version above the installed one, and update_hint says the program
has to be rebuilt, because no package manager can update a statically linked
dependency. Files that a package owns are deliberately not expanded this way:
a distribution backports fixes into vendored code without changing versions, so
the module versions of an owned binary would produce false positives.
The OSV endpoint for this pass is [sources.osv] (required by default: a
failed pass leaves those modules unjudged, which must not look clean).
Textual severity comes from the advisory when it states one (GitHub advisories
do; RUSTSEC entries do not and report unknown).
The project minisign key
contrib/offline-db/amivuln.pub is the project minisign public key. It is
also what the nightly workflow uses to verify the database before it is
published on the release. The project’s private key lives only in the
repository secrets (MINISIGN_SECRET_KEY plus MINISIGN_PASSWORD) and never
on a developer machine, so a compromised mirror cannot fake freshness or
replay an old database past the anti-rollback timestamp.
Reference
Commands
amivuln [OPTIONS] TARGET...is a shortcut foramivuln check.amivuln check [TARGET...] [--all]amivuln cve CVE-ID...: is this host affected? Exit 1 when it is.amivuln pending: the updates that close the most risk, ranked.amivuln services: check the package behind every running systemd service.amivuln restart: processes that still run files an update replaced. Exit 1 when a restart is needed.amivuln config show | init [--path FILE] | validateamivuln cache info | clearamivuln completions bash|zsh|fish|elvish|powershellamivuln db build|pull|verify|info: the offline advisory database (see the user guide)amivuln mcp: a read-only Model Context Protocol server on stdio (see the user guide)amivuln version [--json]
Options
-f, --format:summary,detail,json,sarif,markdown,csvorprometheus[network] retries = 2: retry attempts per failing call;cooldown = "30s": breaker pause;min_interval: optional per-host spacing--output FILE: write the result to FILE instead of stdout; the file is replaced atomically through a rename--fail-on:none,low,medium,high,criticalorkev--fail-on-epss SCORE: exit 1 when an affected finding reaches this EPSS score--strict: unverified findings count for--fail-on--offline,--no-cache: network and cache behavior--config FILE: use this configuration file only--color auto|always|never:NO_COLORis honored--distro fedora:NN|arch: treat the host as this distribution--baseline FILE: a previous-f jsonreport; only findings absent from it violate--fail-on--pid PID: check the executable of a running process (repeatable); a process that is gone, unreadable or whose executable was replaced on disk is reported as unresolved--sbom FILE: check the OS packages of a CycloneDX JSON SBOM (rpm, deb and alpm purls); other components are listed as unresolved, one entry per purl type--vex FILE: anOpenVEXdocument; anot_affectedstatement overrides the verdict and shows the author and justification--packages-from FILE: check the packages listed in FILE (onename@versionper line) instead of the installed onesignore_file(configuration): accepted findings; expired entries are errors-q, --quiet: only errors
AMIVULN_FORMAT, AMIVULN_FAIL_ON, AMIVULN_OFFLINE, AMIVULN_CONFIG and
AMIVULN_COLOR set the corresponding options.
Exit codes
- 0: finished, nothing at or above
--fail-on - 1: finished, findings at or above
--fail-on - 2: incomplete: a required source was unavailable
- 3: a target could not be resolved
- 64: usage or configuration error
- 70: internal error
When several apply, the precedence is 70, 64, 2, 3, 1, 0.
JSON output (schema version 1)
{
"schema_version": 1,
"tool": {"name": "amivuln", "version": "0.0.3"},
"generated_at": "2026-11-08T12:00:00Z",
"host": {"os_id": "fedora", "os_version": "44", "arch": "x86_64"},
"complete": true,
"sources": [{"id": "fedora-updateinfo", "status": "ok", "age_s": 0, "required": true}],
"targets": [{
"input": "nginx",
"package": {"name": "nginx", "version": "1:1.26.2-1.fc44", "arch": "x86_64",
"ecosystem": "fedora", "purl": "pkg:rpm/fedora/nginx@1.26.2-1.fc44?arch=x86_64&epoch=1",
"provenance": "package_db"},
"summary": {"critical": 0, "high": 1, "medium": 0, "low": 0, "kev": 0, "epss_max": 0.02, "unverified": 0},
"findings": [{"id": "CVE-2026-0001", "severity": "high",
"distro_status": {"state": "affected", "fixed_in": "1:1.26.3-1.fc44"},
"kev": null, "epss": {"score": 0.02, "percentile": 0.6, "date": "2026-11-07"}}],
"unverified": []
}],
"unresolved": []
}
The JSON Schema is published at schema/v1.json and is checked against the real output in CI. Findings and unverified findings are separate arrays. The host block never
contains a host name, address or user. Adding optional fields does not change
schema_version; removing or retyping one does.
Configuration file
schema_version = 1
[general]
offline = false
cache_ttl = "6h"
stale_max_age = "72h"
timeout = "10s"
[output]
format = "summary"
color = "auto"
[policy]
fail_on = "none"
fail_on_incomplete = true
strict = false
[sources.distro]
enabled = true
required = true
# url = "https://mirror.example/fedora/linux/updates"
[sources.kev]
enabled = true
[sources.epss]
enabled = true
--format sarif writes SARIF 2.1.0 (one result per affected finding, error/warning/note levels); --format csv writes one row per finding with every field quoted and a ' guard before = + - @ so spreadsheets do not execute formulas; --format markdown writes a table per target.
JSON schema
amivuln -f json writes one document per run. The schema is published as
v1.json (also served as
https://nubian-ibex.github.io/amivuln/schema/v1.json) and the project’s tests
validate the real output against it.
The suppressed array (all documents) lists accepted findings. The cve and pending commands write their own documents ("kind": "cve" and "kind": "pending") with the schemas cve-v1.json, pending-v1.json and, for restart, restart-v1.json. The run report also carries a score object.
Rules:
schema_versionis bumped on every incompatible change; adding optional fields does not bump it.findings(affected) andunverifiedare separate arrays.- A finding
idis a CVE id, or a distribution update id when the update names no CVE. complete: falsemeans a required source failed. Treat it as unknown, not as clean.- The
hostobject only ever holdsos_id,os_versionandarch.
Validate a document:
check-jsonschema --schemafile v1.json amivuln.json
Architecture
amivuln is a Cargo workspace. The amivuln binary only orchestrates; the
domain rules perform no I/O.
Pipeline
- Target. What the user asked about: a package name, a file path, a
.serviceunit,name@versionor a purl (amivuln-resolve). - Identity. The installed package, read from the package manager
(
rpm,pacman) through an allowlisted, bounded subprocess runner. - Distro data. The distribution’s own advisory data, fetched through a
cache-aware HTTP client (
amivuln-sources,amivuln-cache). - Matching. The distribution status decides, never an upstream version
range.
Fixed(v)with an installed version at or abovevis patched; an older one is affected and names the version that fixes it (amivuln-core). - Enrichment. CISA KEV and FIRST EPSS.
- Policy.
--fail-on,--fail-on-epss,--strict, and the exit code precedence 70, 64, 2, 3, 1, 0. - Report.
summary,detailand JSON (amivuln-report).
Crates
amivuln: command line, configuration and orchestration.amivuln-core: domain types, matching rules, policy, exit codes and the traits for the outside world.amivuln-resolve: target resolution and the system process runner.amivuln-version: RPM and pacman version comparison, verified againstrpmandvercmpon thousands of generated pairs.amivuln-sources: parsers for the Arch Security Tracker, Fedoraupdateinfo, CISA KEV and FIRST EPSS, and the bounded HTTP client.amivuln-cache: SQLite HTTP cache with ETag revalidation and fresh/stale/expired classification.amivuln-report: output formatters.
All crates are published to crates.io because the binary depends on them. The library crates carry no API stability promise before 1.0.
Invariants
- The distribution status overrides upstream version comparison.
- A finding without a distribution statement is unverified, listed apart, and never counted as affected.
- A failed required source makes the result incomplete (exit code 2). Stale
cached data within
stale_max_ageis used and reported asstale. - Every file read, HTTP response and subprocess output is size-bounded, every subprocess has a timeout and an allowlisted absolute path, and no shell is involved.
- Text from outside the process is sanitised before it reaches a terminal.
- No field that identifies the host exists in the output or the requests.
How matching works
Every verdict answers one question: given the installed version of a package and everything the consulted sources said about it, is the host affected?
The distribution wins
The distribution’s own advisory data decides. Upstream ranges are never used to call a package affected: distributors ship fixes without changing the upstream version, so an upstream range would report false positives on RPM-based and Debian-based systems alike.
Precedence
Per CVE and per package the statements merge, most restrictive first:
- not-affected from the distribution or an OpenVEX statement (
--vex), with the justification carried through to every output format. - patched, when the installed version is at or above the published fix.
- affected, otherwise; the finding names the version that fixes it.
- unverified, when there is a record but no comparable version: the distribution says will not fix or fix deferred (the reporter lists it under its own id), or the fixed version cannot be compared with the installed version (an rpm fix against a deb install, for instance).
Everything a source could not answer for is listed separately as unverified
and never silently counted as affected. Enrichment (KEV, EPSS) never changes a
verdict.
Per source
- Fedora.
updateinfopublishes the version that fixed the CVE. Installed at or above any published fix is patched. A CVE with no published fix is not visible in this data. - Arch. The tracker records a build; the same upstream version is vulnerable, a rebuild of it too, and a newer one unverified.
- Debian, Ubuntu, Rocky,
AlmaLinux, Go modules and Rust crates. OSV serialises advisories per ecosystem; the fixed versions that name the package decide. For language packages OSV answers per exact version, so every advisory returned affects the queried version and the fix reported is the lowest one above it. A language advisory without a CVE alias is reported under its own id (GHSA, GO, RUSTSEC) instead of vanishing. - Baseline.
--baseline prev.jsonkeeps only findings that are new since that report; it never turns an affected call into clean. - Ignore file. An accepted finding is suppressed with its reason, owner and expiry shown in the output; an expired entry is an error.
Determinism
The rules are pure functions in amivuln_core (matching::merge): two runs
on the same data agree, and the ground truth corpus (Fedora and Arch cases
with recorded HTTP fixtures) pins them against recorded data.
Data sources
- Arch Security Tracker: CVE status and fixed version on Arch (
https://security.archlinux.org/issues/all.json) - Fedora
updateinfo: security updates and the CVEs they fix on Fedora (https://dl.fedoraproject.org/pub/fedora/linux/updates/<release>/Everything/<arch>/repodata/) - OSV: Debian, Ubuntu, Rocky Linux,
AlmaLinux, Go modules andcrates.io(https://api.osv.dev/v1/querybatch, one advisory document per id). Advisories that also exist in the distro trackers carry the same meaning; for Go and crates exact versions are what decide. - CISA KEV: actively exploited CVEs (
https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json) - FIRST EPSS: probability of exploitation (
https://api.first.org/data/v1/epss)
The distribution source is required by default. KEV and EPSS enrich the result and are optional: if they fail the output says so, but the result is still complete.
Why not OSV for Fedora and Arch
The OSV ecosystem list (checked 2026-11-07) contains Debian, Ubuntu, Alpine, Red Hat, Rocky, AlmaLinux, SUSE and others, but not Fedora or Arch Linux. For these two, the distribution’s own data is the source of truth.
Fedora specifics
updateinfo states which CVEs an update fixed. amivuln reports a CVE as
affecting an installed package when Fedora has published a fix in a newer
version. Only CVE ids that open a bugzilla bug title are trusted; ids that
appear only in free-text descriptions are ignored, because “regression from
the fix for CVE-X” would otherwise mark CVE-X as fixed. A security update that
names no CVE is reported under its update id. A CVE without a published fix is
not visible in this data.
Where the data lives offline
amivuln db build packs everything consulted into a tar.zst that another
host verifies, amivuln db pull, and queries with --offline (see the guide). VEX
input comes from documents you supply with --vex.
Planned
The Fedora Bodhi API as a cross-check, NVD by CPE as a fallback, and endoflife.date. RHEL itself stays out until its data licensing is settled (ADR-0012).
Arch specifics
The tracker marks a group Vulnerable with no fixed version for the build that was current when the entry was written, and some entries stay that way for years. amivuln reports an installed package as affected only when it has the same upstream version as the recorded build (a rebuild of it is still affected). A newer upstream version is reported as unverified, because the entry cannot say whether it still applies. An install older than the recorded build is outside the entry and is not reported.
OpenSSF security insights form
You can reach the badge form at bestpractices.dev. Each criterion the OSPS baseline asks for, and the evidence this repository already gives, is summarized here. Copy the justification beside each the status on the form and link back to the file or workflow.
| # | Criterion | Where the evidence is | Into a badge justification |
|---|---|---|---|
| AC-01 | Access control (GitHub enforced) | Org 2FA + branch ruleset main | “GitHub enforced branch ruleset on the default branch; SSO 2FA at the org is on.” |
| AC-02 | Repository access control | GitHub roles (read from push), ruleset removes deletion and force-push | “Default-branch history is protected by ruleset; only the maintainer account has write access.” |
| AC-03 | Releases are immutable | release environment with a required reviewer; tag ruleset denies update and deletion on refs/tags/v* | “Release artifacts are pipeline-published from signed tags created through an environment that only the maintainer can approve.” |
| B-01 | Build script exists | justfile, cargo xtask | “cargo + just build (cargo build --release --locked; just).” |
| B-02 | Build from a source published release | dist release pack + cargo publish gated by crate | “Release artifact built from checked-out source in CI (release.yml).” |
| B-03 | Documented build instructions | README quickstart, docs/book/src/installation.md | “README and install guide include cargo install and signature verification.” |
| DC-01 | Documents what it does | README overview + docs book | |
| DC-02 | README names basic commands | amivuln <pkg>; JSON output; exit codes | “README quickstart.” |
| DC-03 | Project website | amibuln.dev (pending; domain decision is maintainer-owned) | |
| DC-04 | Contribution instructions | CONTRIBUTING.md | “CONTRIBUTING.md covers setup, just targets, and conventional commit sign-off.” |
| DP-01 | Source and binary distributions covered | docs/security.md + release guide | “SECURITY.md holds the reporting channel, SLAs and verification steps.” |
| O-01 | Maintainers documented | MAINTAINERS.md (pending as file) | |
| O-02 | Licensing | REUSE.toml + SPDX headers + reuse lint gate | “REUSE enforces SPDX headers on every file and disallows worsening licenses.” |
| Q-01 | CI performs style checks and clippy | clippy -D warnings blocking on lint | “CI blocks the release patch on clippy -D warnings.” |
| Q-02 | CI blocks code compile | Empty required checks on build-a/build-b, test | “CI gates develop branches on nextest runs (build+test jobs).” |
| Q-03 | Test framework defined | cargo test and nextest declared | “Tests run through cargo nextest with recorded fixtures.” |
| Q-04 | CI blocks on test change | The build gate denies merge when tests fail | “The ruleset enforces the checks on every PR.” |
| Q-05 | Security-relevant tests exist | tests/ground-truth.rs recorded HTTP fixtures | “Ground truth captures real advisory HTTP payloads as fixtures.” |
| Q-06 | Docs and compat | jsonschema steps in CI | “rustdoc -D warnings plus the release JSON-schema gate block regressions.” |
| SA-01 | Static exchange analysis | cargo astray: audit, deny, vet | “cargo audit, deny and vet run per PR (advisory DB, license + sources, provenance audits).” |
| SA-02 | Dependabot / Dependabot security alerts | enabled at the repo | “Dependabot security updates are on; Dependabot PRs are auto-tracked.” |
| S-02 | Tag provenance | cosign keyless + SLSA attestation at release | “Releases are cosigned keyless and carry SLSA attestations (release.yml’s actions/attest step).” |
| S-03 | Release attestation verified | gh attestation verify in README and amivuln-action | “The composite action refuses unattested binaries before execution.” |
| D-01 | System invokes defenses | sources block in all output, complete flag and exit 2 | “Every run states exactly what answered, its age, and whether the result was complete.” |
| T-01 | Unit tests on package versions | proptest ground truth and amivuln-version test | “Comparators get property-tested against rpmvercmp, and are cross-checked with the reference tools.” |
Trailing rows follow the badge form; some criteria are never fully automatic
(DC-03 website, O-01 maintainers file) and are as of yet pending. Future
regs are tracked in docs/ROADMAP.md, §8.
Support policy
Versions
Before 1.0 only the latest release receives fixes, security or otherwise. From 1.0 the latest minor release receives security fixes, and a security fix is backported to the previous minor for 90 days after a new minor ships.
Minimum supported Rust version
The MSRV is the rust-version in Cargo.toml (1.97 today) and the msrv CI
job builds with it on every change. It is raised only in a minor release,
never in a patch release, and every raise is named in the changelog.
What is a contract
- JSON output. Documents carry
schema_version. Within a version, fields are never removed, renamed or retyped; a new field or a new enum value is an additive change and ships in a minor release with a note in the changelog and the published schema. Breaking the shape means a newschema_version. The golden documents undertests/goldenpin the exact output, field order included, and must keep validating against the current schema; a change to them is visible in review. - Exit codes. 0, 1, 2, 3, 64, 65 and 70 and their precedence are stable.
- Configuration. Keys are not removed or renamed within a major version; new keys have defaults that keep today’s behavior.
- Command line. Flags are not removed or repurposed within a major
version. Text output (
summary,detail) is for humans and may change.
Platforms
Linux on x86_64 and aarch64 (static musl and glibc builds). macOS and
Windows are not supported.
Security and privacy
What leaves the host
Only the ecosystem, the package name and the version, in requests to the sources listed in Data sources. EPSS requests carry CVE ids. No host name, path, user name or address is ever sent, and a test inspects every request to make sure of it. There is no telemetry and no update check.
How the tool protects itself
- It never runs the target binary. Versions come from the package manager.
- System tools run from fixed absolute paths with a cleared environment,
LC_ALL=C, fixed arguments, a timeout and an output limit. - Advisory data is untrusted: sizes and nesting depth are limited, identifiers that do not look like CVE ids are dropped, and control characters never reach the terminal.
- The Fedora metadata is checked against the SHA-256 in
repomd.xml, so a mirror serving other bytes is rejected. - Network access uses HTTPS only, with rustls, and at most three redirects.
Reporting a vulnerability
Use GitHub private vulnerability reporting:
github.com/nubian-ibex/amivuln/security/advisories/new.
See the repository’s SECURITY.md for response targets.
Release integrity
Releases are built by GitHub Actions from a signed tag and ship build
provenance attestations, a cosign signature over SHA256SUMS and an SBOM.
Release builds are reproducible when built in the same environment; see
docs/reproducible-builds.md in the repository.
False positive FAQ
amivuln reports a CVE my distribution already fixed. Distribution status
overrides version comparison, so this should not happen. Open a
false positive issue with the output of amivuln PACKAGE -f json and the
link to the advisory or changelog that shows the fix.
amivuln reports nothing but I know of a CVE. On Fedora, amivuln only sees fixes that Fedora has published. A CVE with no update yet is not in the data. Open a false negative issue.
The result says INCOMPLETE. A required source was unreachable and there
was no cached copy within stale_max_age. Retry, check the network, or use a
mirror with sources.distro.url. The exit code is 2 on purpose.
Why is a package “unverified”? The distribution has no statement about it.
It is listed apart and does not fail a build unless you pass --strict.
My updates repository is disabled. amivuln looks at Fedora’s published
updates data regardless of your local repository configuration, so it can
report an update that your system cannot see yet.
Contributing
The full guide is CONTRIBUTING.md in the repository. In short:
git clone https://github.com/nubian-ibex/amivuln && cd amivuln
just ci # fmt, clippy, tests, docs, assets, deny, audit, vet, typos, reuse, actionlint, zizmor
- Commits follow Conventional Commits and are signed off (
git commit -s). - Tests never touch the network: recorded fixtures and a mock server.
- Report a wrong result with the false positive or false negative issue
form, including the output of
amivuln PACKAGE -f json. - AI-assisted contributions are welcome; disclose them with an
Assisted-by:trailer. The human author is responsible for the change.
Architecture decisions are recorded as ADRs in docs/adr/.