Air-gapped install¶
High-assurance environments — classified networks, CMMC/FedRAMP enclaves
handling CUI, HIPAA networks, isolated OT/SCADA, some financial back offices —
cannot reach PyPI or send compliance data to third-party SaaS. Evidentia is
built for this: gap arithmetic runs on-device, the only optional outbound calls
are LLM API requests, and a first-class --offline flag fails closed on any
non-local network call. This guide covers the offline install (wheelhouse
pattern), offline catalog handling, and the GPG-only signing fallback for when
Sigstore's Fulcio/Rekor are unreachable.
Step 1 — Build a wheelhouse on a connected host¶
On an internet-connected machine running the same OS/arch and Python 3.12 as your target, download Evidentia and all its dependencies as wheels into a local directory:
If you need extras offline, download them too (the extra's transitive deps come along):
Build the wheelhouse on a host that matches the target's platform. Wheels can be platform-specific; downloading on macOS for a Linux enclave can pull the wrong binaries. When in doubt, use the matching container image instead (Step 1b).
Step 1b — (Alternative) transfer the container image¶
Pull the cosign-signed image on the connected host, save it to a tarball, and carry the tarball across the air gap:
docker pull ghcr.io/polycentric-labs/evidentia:v0.10.13
docker save ghcr.io/polycentric-labs/evidentia:v0.10.13 -o evidentia.tar
On the air-gapped host: docker load -i evidentia.tar.
Step 2 — Transfer and install offline¶
Move the evidentia-wheelhouse/ directory across the air gap (removable media,
data diode, approved transfer process). On the target host, install with the
index disabled so pip never reaches for the network:
Bash / Linux / macOS
python -m venv .venv && source .venv/bin/activate
pip install --no-index --find-links ./evidentia-wheelhouse evidentia
evidentia version
# → Evidentia v0.10.13 / Python 3.12.x
PowerShell (Windows)
python -m venv .venv ; .\.venv\Scripts\Activate.ps1
pip install --no-index --find-links ./evidentia-wheelhouse evidentia
evidentia version
# → Evidentia v0.10.13 / Python 3.12.x
Step 3 — Validate the air-gap posture¶
Before running anything that matters, confirm every subsystem reports an air-gap-ready posture:
evidentia doctor --check-air-gap
# LLM client AIR-GAP READY model=ollama/llama3.1:8b (local prefix)
# Catalog loader AIR-GAP READY loads only from bundled + user-dir catalogs
# ...
Any red entry is a configuration mistake to fix before you start. Then run every
command with the --offline global flag, which refuses any outbound call to a
non-loopback / non-RFC-1918 host before the network IO is issued:
Bash / Linux / macOS
evidentia --offline gap analyze \
--inventory my-controls.yaml \
--frameworks nist-800-53-rev5-moderate \
--output gap-report.json
PowerShell (Windows)
evidentia --offline gap analyze `
--inventory my-controls.yaml `
--frameworks nist-800-53-rev5-moderate `
--output gap-report.json
If a call would leak, Evidentia raises a structured OfflineViolationError
(naming the subsystem, target, and remediation) rather than a mystery timeout.
Fail closed, not open.
LLM features offline¶
evidentia risk generate and other LLM-backed features need a local
inference endpoint in offline mode. Point Evidentia at Ollama or a self-hosted
OpenAI-compatible server on a loopback/RFC-1918 address:
Bash / Linux / macOS
# Ollama on the same host
export EVIDENTIA_LLM_MODEL=ollama/llama3.1:8b
# or a self-hosted vLLM/LocalAI endpoint on a private IP
export EVIDENTIA_LLM_MODEL=gpt-4o-compatible
export EVIDENTIA_LLM_API_BASE=http://10.50.1.20:8000/v1
evidentia --offline risk generate --gap-id GAP-0001 --context system-context.yaml
PowerShell (Windows)
# Ollama on the same host
$env:EVIDENTIA_LLM_MODEL = "ollama/llama3.1:8b"
# or a self-hosted vLLM/LocalAI endpoint on a private IP
$env:EVIDENTIA_LLM_MODEL = "gpt-4o-compatible"
$env:EVIDENTIA_LLM_API_BASE = "http://10.50.1.20:8000/v1"
evidentia --offline risk generate --gap-id GAP-0001 --context system-context.yaml
The guard checks the api_base host, not the model string — any model name
paired with an RFC-1918 api_base is permitted in offline mode.
Offline catalogs¶
Evidentia loads framework catalogs from two places, both local: the catalogs
bundled inside the wheel and a user catalog directory. No catalog fetch
touches the network, so evidentia catalog list / show / crosswalk work
unchanged offline. To add a framework in an air-gapped environment, import the
catalog file from local disk:
(evidentia catalog import reads a local file path; it does not fetch from a
URL. Any future URL-based import is already routed through the offline guard.)
Signing offline — GPG only¶
Sigstore keyless signing (--sign-with-sigstore) needs network access to Fulcio
(the certificate authority) and Rekor (the transparency log), so it is
refused in --offline mode. Air-gapped chains of custody use GPG-detached
signatures instead, which need no network:
Bash / Linux / macOS
evidentia --offline gap analyze \
--inventory my-controls.yaml \
--frameworks nist-800-53-rev5-moderate \
--format oscal-ar \
--output assessment-results.json \
--sign-with-gpg YOUR_KEY_ID
# Verify (also offline)
evidentia --offline oscal verify assessment-results.json --require-signature
PowerShell (Windows)
evidentia --offline gap analyze `
--inventory my-controls.yaml `
--frameworks nist-800-53-rev5-moderate `
--format oscal-ar `
--output assessment-results.json `
--sign-with-gpg YOUR_KEY_ID
# Verify (also offline)
evidentia --offline oscal verify assessment-results.json --require-signature
The same fallback applies to MCP tool-output signing: wire a GPG-based signer
factory (not the Sigstore one) when EVIDENTIA_MCP_SIGN_OUTPUTS is enabled in an
air-gapped deployment. See Sign and verify evidence
for the full signing workflow.
Auditor proof-of-offline checklist¶
- Environment — disable outbound network on the host (e.g. on Linux,
iptables -A OUTPUT -j DROPexcept loopback). - Preflight —
evidentia --offline doctor --check-air-gap; every subsystem reportsAIR-GAP READY. - Analysis —
evidentia --offline gap analyze ...completes with no network errors. - Risk generation — with a local LLM configured,
evidentia --offline risk generate ...produces output (a misconfiguration yields a clearOfflineViolationError, not a hang). - Signing —
--sign-with-gpgsucceeds offline;--sign-with-sigstoreis correctly refused.
What's next¶
- Installation — the standard (connected) install paths + the extras matrix.
- Sign and verify evidence — the GPG/Sigstore signing workflow in depth.
- The in-repo
docs/air-gapped.mdhas the full guard design, the allowed-host ranges, and a reverse-proxy pattern for multi-user network stations.
Got stuck?¶
OfflineViolationError— a subsystem tried to reach a non-local host. The payload names the subsystem + target + remediation; fix the configuration (usually an LLM endpoint that is not on loopback/RFC-1918).pipstill reaches the network — you omitted--no-index. With--no-index --find-links ./evidentia-wheelhouse, pip installs only from the wheelhouse.- Wrong-platform wheels — rebuild the wheelhouse on a host matching the target OS/arch, or use the container image (Step 1b).