Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 musl tarballs for x86_64 and aarch64, which run on any Linux distribution;
  • gnu tarballs for x86_64 and aarch64, linked against glibc 2.34 or newer (Debian 12, Ubuntu 22.04, Fedora 35, RHEL 9 and later);
  • .deb, .rpm, .apk and Arch (.pkg.tar.zst) packages that install the binary, the man pages and the shell completions;
  • a CycloneDX SBOM, SHA256SUMS and 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@version or pkg: URLs) and an optional distro.
  • check_cve: id such as CVE-2024-6387; scans every installed package.
  • pending_updates, needs_restart and list_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 for amivuln 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] | validate
  • amivuln cache info | clear
  • amivuln completions bash|zsh|fish|elvish|powershell
  • amivuln 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, csv or prometheus
  • [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, critical or kev
  • --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_COLOR is honored
  • --distro fedora:NN|arch: treat the host as this distribution
  • --baseline FILE: a previous -f json report; 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: an OpenVEX document; a not_affected statement overrides the verdict and shows the author and justification
  • --packages-from FILE: check the packages listed in FILE (one name@version per line) instead of the installed ones
  • ignore_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_version is bumped on every incompatible change; adding optional fields does not bump it.
  • findings (affected) and unverified are separate arrays.
  • A finding id is a CVE id, or a distribution update id when the update names no CVE.
  • complete: false means a required source failed. Treat it as unknown, not as clean.
  • The host object only ever holds os_id, os_version and arch.

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

  1. Target. What the user asked about: a package name, a file path, a .service unit, name@version or a purl (amivuln-resolve).
  2. Identity. The installed package, read from the package manager (rpm, pacman) through an allowlisted, bounded subprocess runner.
  3. Distro data. The distribution’s own advisory data, fetched through a cache-aware HTTP client (amivuln-sources, amivuln-cache).
  4. Matching. The distribution status decides, never an upstream version range. Fixed(v) with an installed version at or above v is patched; an older one is affected and names the version that fixes it (amivuln-core).
  5. Enrichment. CISA KEV and FIRST EPSS.
  6. Policy. --fail-on, --fail-on-epss, --strict, and the exit code precedence 70, 64, 2, 3, 1, 0.
  7. Report. summary, detail and 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 against rpm and vercmp on thousands of generated pairs.
  • amivuln-sources: parsers for the Arch Security Tracker, Fedora updateinfo, 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_age is used and reported as stale.
  • 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:

  1. not-affected from the distribution or an OpenVEX statement (--vex), with the justification carried through to every output format.
  2. patched, when the installed version is at or above the published fix.
  3. affected, otherwise; the finding names the version that fixes it.
  4. 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. updateinfo publishes 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.json keeps 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 and crates.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.

#CriterionWhere the evidence isInto a badge justification
AC-01Access control (GitHub enforced)Org 2FA + branch ruleset main“GitHub enforced branch ruleset on the default branch; SSO 2FA at the org is on.”
AC-02Repository access controlGitHub 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-03Releases are immutablerelease 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-01Build script existsjustfile, cargo xtask“cargo + just build (cargo build --release --locked; just).”
B-02Build from a source published releasedist release pack + cargo publish gated by crate“Release artifact built from checked-out source in CI (release.yml).”
B-03Documented build instructionsREADME quickstart, docs/book/src/installation.md“README and install guide include cargo install and signature verification.”
DC-01Documents what it doesREADME overview + docs book
DC-02README names basic commandsamivuln <pkg>; JSON output; exit codes“README quickstart.”
DC-03Project websiteamibuln.dev (pending; domain decision is maintainer-owned)
DC-04Contribution instructionsCONTRIBUTING.md“CONTRIBUTING.md covers setup, just targets, and conventional commit sign-off.”
DP-01Source and binary distributions covereddocs/security.md + release guide“SECURITY.md holds the reporting channel, SLAs and verification steps.”
O-01Maintainers documentedMAINTAINERS.md (pending as file)
O-02LicensingREUSE.toml + SPDX headers + reuse lint gate“REUSE enforces SPDX headers on every file and disallows worsening licenses.”
Q-01CI performs style checks and clippyclippy -D warnings blocking on lint“CI blocks the release patch on clippy -D warnings.”
Q-02CI blocks code compileEmpty required checks on build-a/build-b, test“CI gates develop branches on nextest runs (build+test jobs).”
Q-03Test framework definedcargo test and nextest declared“Tests run through cargo nextest with recorded fixtures.”
Q-04CI blocks on test changeThe build gate denies merge when tests fail“The ruleset enforces the checks on every PR.”
Q-05Security-relevant tests existtests/ground-truth.rs recorded HTTP fixtures“Ground truth captures real advisory HTTP payloads as fixtures.”
Q-06Docs and compatjsonschema steps in CI“rustdoc -D warnings plus the release JSON-schema gate block regressions.”
SA-01Static exchange analysiscargo astray: audit, deny, vet“cargo audit, deny and vet run per PR (advisory DB, license + sources, provenance audits).”
SA-02Dependabot / Dependabot security alertsenabled at the repo“Dependabot security updates are on; Dependabot PRs are auto-tracked.”
S-02Tag provenancecosign keyless + SLSA attestation at release“Releases are cosigned keyless and carry SLSA attestations (release.yml’s actions/attest step).”
S-03Release attestation verifiedgh attestation verify in README and amivuln-action“The composite action refuses unattested binaries before execution.”
D-01System invokes defensessources block in all output, complete flag and exit 2“Every run states exactly what answered, its age, and whether the result was complete.”
T-01Unit tests on package versionsproptest 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 new schema_version. The golden documents under tests/golden pin 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/.