Skip to content

Air-gapped security data updates (ClamAV, Grype, EPSS, CISA KEV)

repod needs no internet connection for its core operation — self-hosted, manual upload works fully offline. Internet access is only used by a handful of optional features, the most important of which is keeping the CVE-scanning pipeline current. Four data sources refresh from the internet:

Source What it is Refreshed by Local path (default)
ClamAV signatures Antivirus signature database freshclam /var/lib/clamav
Grype vulnerability DB CVE matching database (grype db update, cron security_sync_daily) grype db update /repos/grype-db
EPSS scores Exploit Prediction Scoring System (FIRST.org) services/cve_enrichment.py /repos/security/epss_cache.json
CISA KEV Known Exploited Vulnerabilities catalog services/cve_enrichment.py /repos/security/kev_cache.json

If your repod instance has no internet access at all, none of these 4 can self-refresh. This guide covers the supported workflow: a relay host (any machine with internet access) fetches fresh data, you transfer it offline (USB, disk, or whatever your security policy allows), and you import it on the air-gapped instance. Same pattern already used for the upstream cache, applied here to the security pipeline instead of package content.


1. Fetch on the relay host

The relay host needs Docker and a checkout of this repository — it does not need to be a full repod install, and it does not need internet access to the air-gapped site, only to the outside world.

git clone <your-repod-repo> && cd repod
bash scripts/security-data-transfer.sh fetch ./security-data-2026-08.tar.gz

This runs freshclam, grype db update, and repod's own services/cve_enrichment.py (EPSS + KEV refresh) each inside a short-lived container of the repod backend image itself — not a hand-rolled reimplementation. This matters: it guarantees the exported files are byte-for-byte what a normally-connected repod instance would have produced (same tool versions, same cache file format), so there's no format drift to worry about on import.

If a backend image has already been built locally (docker compose build backend), the script reuses it. Otherwise it builds one from ./backend — this can take several minutes (Nuitka compilation is part of the normal backend build), which is expected the first time.

A partial failure (e.g. Grype's upstream is briefly unreachable) doesn't abort the whole fetch — each of the 3 steps is independent, and the script warns and continues, packaging whatever succeeded. Check the console output before transferring if you need all 4 sources current.


2. Transfer

Move security-data-2026-08.tar.gz to the air-gapped site via whatever offline medium your security policy allows (USB drive, write-once disk, etc.) — same handoff step as the upstream cache guide.


3. Import on the air-gapped instance

From the repod checkout on the air-gapped host:

bash scripts/security-data-transfer.sh import ./security-data-2026-08.tar.gz

This extracts the bundle directly into the same host directories docker-compose.yaml already mounts into the backend container (./repos/clamav-db, ./repos/grype-db, ./repos/security) — no manual path juggling needed on a standard deployment. Override CLAMAV_HOST_DIR/GRYPE_HOST_DIR/SECURITY_HOST_DIR env vars first if your deployment maps these elsewhere.

ClamAV needs a restart to pick up new signaturesclamd loads them into memory once at container startup:

docker compose restart backend

Grype and EPSS/KEV are re-read from disk on every scan/request — no restart needed for those.


4. Keeping it current

Repeat steps 1–3 periodically (e.g. weekly for ClamAV/Grype, which change often; EPSS/KEV can go longer between refreshes) on the relay, transfer, and import. There's no delta/incremental mode — each run re-fetches the full current state of all 4 sources, which is what freshclam/grype db update already do incrementally against their own upstream anyway; the tarball itself is always a full snapshot, not a diff.


What this does not cover

This guide is specifically about the 4 CVE-pipeline data sources above. For air-gapping the package content itself (APT/RPM/APK repository mirroring), see the upstream cache guide instead — the two are independent and typically used together on a fully air-gapped deployment.