scripts/demo-airgap.sh
#!/usr/bin/env bash
# demo-airgap.sh — Holger 1.0: a secure artifact gateway for air-gapped and
# high-assurance environments, end to end.
#
# Two Holger servers and a directory that stands in for the USB stick:
#
# ONLINE (this host, has the internet) USB (a directory) SECURE (a podman pod, --network none)
# import → hash → inspect → malware/CVE/ signed bundle → verified import → own malware
# license → SBOM → provenance → policy → + trust policy rescan → AVAILABLE → pip install
# quarantine → approval (two people) → seal + intel bundle (unmodified pip)
# → signed airgap export + YARA rules + ClamAV DB
#
# Every step prints its result. The first failure stops the script loudly.
# A step whose lane has not landed prints "NOT YET: <lane>" and NEVER passes.
# The run writes a receipt (receipt.json, one check per step) and a plain-text
# log (demo.log) into DEMO_OUT.
#
# Needs: podman, openssl, curl, sha256sum, python3, and the binaries:
# HOLGER_BIN_DIR a directory with holger-server (built with --features
# secure_update and sign: the intake chain, the DSSE evidence
# signer, and the `export`, `bundle`, `intel` and `intake`
# verbs) and holger-cli (its `bundle approve|finalize` verbs).
# `cargo xtask demo airgap` builds them and runs this script.
# By hand, in the toolchain container:
# cargo build --release -p holger -p holger-cli \
# --features holger/secure_update,holger/sign,holger/holger-server-alias --bins
# The SLSA provenance signer, `sign_slsa` (cargo build --release
# -p holger-provenance --example sign_slsa), in HOLGER_BIN_DIR or
# HOLGER_BIN_DIR/examples, or named by HOLGER_SIGN_SLSA.
# PYTHON_IMAGE default docker.io/library/python:3.13-slim-trixie (pulled once)
# CLAMAV_IMAGE the ClamAV image, pinned by digest. Its signature database is
# the one baked into that image (freshclam never runs), and the
# same database files ride the USB stick to the secure side.
# DEMO_PACKAGE default idna==3.20 (License-Expression: BSD-3-Clause, no dependencies) (downloaded on the online side with pip)
# DEMO_WHEEL a local wheel file to use instead of downloading
# DEMO_INTEL_LIVE set to 1 to fetch the live OSV (PyPI), NVD and KEV feeds for
# the intel bundle; default: the fixture feeds in intel/tests/fixtures
# DEMO_OUT where receipt.json and demo.log go; default target/demo-airgap/<UTC time>
# DEMO_KEEP keep the work directory (default: removed on success)
set -Eeuo pipefail
BIN="${HOLGER_BIN_DIR:?set HOLGER_BIN_DIR to a directory with holger-server and holger-cli}"
BIN="$(cd "$BIN" && pwd)"
REPO="$(cd "$(dirname "$0")/.." && pwd)"
PY_IMAGE="${PYTHON_IMAGE:-docker.io/library/python:3.13-slim-trixie}"
CLAMAV_IMAGE="${CLAMAV_IMAGE:-docker.io/clamav/clamav:1.5.4@sha256:e19130cdebaeacc71fa46eab3ed6f69941f2f22aa5e08d55e9197f8edf43ce5e}"
PKG="${DEMO_PACKAGE:-idna==3.20}"
W="$(mktemp -d "${TMPDIR:-/tmp}/holger-airgap-demo.XXXXXX")"
OUT="${DEMO_OUT:-$REPO/target/demo-airgap/$(date -u +%Y%m%dT%H%M%SZ)}"
mkdir -p "$OUT"
OUT="$(cd "$OUT" && pwd)"
: >"$OUT/demo.log"
: >"$OUT/checks.tsv"
: >"$OUT/facts.tsv"
STARTED="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
ONLINE_PID=""
# The names of the containers and the pod THIS run starts; cleanup removes only these.
TAG="holger-demo-$$-$(od -An -N3 -tx1 /dev/urandom | tr -d ' \n')"
CLAMD_ONLINE=""
# SANDBOX-SCAN: every scan runs in the scan worker's container on both sides. The
# image is minted (`cargo xtask scan-worker image`) and rides the USB stick; both
# Holgers drive rootless podman over an API socket of the demo's own.
SCAN_WORKER_IMAGE="${HOLGER_SCAN_WORKER_IMAGE:-localhost/holger-scan-worker:$(sed -n 's/^version = "\(.*\)"/\1/p' "$REPO/server/cli/Cargo.toml" | head -1)}"
SBX_PID=""
SECURE_POD=""
N=0
CUR=setup
RESULTS=()
red() { printf '\033[1;31m%s\033[0m' "$*"; }
grn() { printf '\033[1;32m%s\033[0m' "$*"; }
yel() { printf '\033[1;33m%s\033[0m' "$*"; }
# The plain-text log a person reads, beside the coloured terminal.
plain() { printf '%s\n' "$*" >>"$OUT/demo.log"; }
# One check per step in the receipt: number, result, step, detail.
check() { printf '%s\t%s\t%s\t%s\n' "$N" "$1" "$CUR" "$(printf '%s' "$2" | tr '\t\n' ' ')" >>"$OUT/checks.tsv"; }
# A fact for the receipt (the package, the hashes, the engines and their databases).
fact() { printf '%s\t%s\n' "$1" "$(printf '%s' "$2" | tr '\t\n' ' ')" >>"$OUT/facts.tsv"; }
step() { N=$((N + 1)); CUR="$*"; printf '\n[%2d] %s\n' "$N" "$*"; plain ""; plain "[$N] $*"; }
ok() { printf ' %s %s\n' "$(grn PASS)" "$*"; plain " PASS $*"; RESULTS+=("PASS | $CUR"); check PASS "$*"; }
notyet() { printf ' %s %s\n' "$(yel "NOT YET: $1")" "${*:2}"; plain " NOT YET: $1: ${*:2}"; RESULTS+=("NOT YET | $CUR ($1)"); check "NOT YET" "$1: ${*:2}"; }
info() { printf ' %s\n' "$*"; plain " $*"; }
# receipt.json from the checks and the facts. Its result: pass (every check
# PASS), incomplete (no failure, a NOT YET left), or fail.
receipt() {
python3 -I -c '
import json, sys, datetime
out, started, ending = sys.argv[1], sys.argv[2], sys.argv[3]
checks = []
for line in open(out + "/checks.tsv", encoding="utf-8"):
n, result, step, detail = (line.rstrip("\n").split("\t") + ["", "", "", ""])[:4]
checks.append({"n": int(n), "step": step, "result": result, "detail": detail})
facts = {}
for line in open(out + "/facts.tsv", encoding="utf-8"):
k, _, v = line.rstrip("\n").partition("\t")
facts.setdefault(k, v)
results = {c["result"] for c in checks}
if ending == "fail" or "FAIL" in results:
result = "fail"
elif "NOT YET" in results:
result = "incomplete"
else:
result = "pass"
doc = {
"schema": "holger.demo-airgap-receipt/v1",
"started": started,
"finished": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"result": result,
"passed": sum(1 for c in checks if c["result"] == "PASS"),
"not_yet": sum(1 for c in checks if c["result"] == "NOT YET"),
"facts": facts,
"checks": checks,
}
with open(out + "/receipt.json", "w", encoding="utf-8") as f:
json.dump(doc, f, indent=2)
f.write("\n")
print(result)
' "$OUT" "$STARTED" "$1"
}
die() {
printf '\n%s [%s] %s\n' "$(red 'FAILED')" "$CUR" "$*" >&2
plain " FAIL $*"
check FAIL "$*"
if [ -f "$W/last.log" ]; then
sed 's/^/ | /' "$W/last.log" >&2
sed 's/^/ | /' "$W/last.log" >>"$OUT/demo.log"
fi
printf ' work directory kept: %s\n' "$W" >&2
receipt fail >/dev/null 2>&1 || true
printf ' receipt: %s/receipt.json\n log: %s/demo.log\n' "$OUT" "$OUT" >&2
exit 1
}
trap 'die "line $LINENO: $BASH_COMMAND"' ERR
cleanup() {
if [ -n "$SBX_PID" ]; then kill "$SBX_PID" 2>/dev/null && wait "$SBX_PID" 2>/dev/null || true; fi
if [ -n "$ONLINE_PID" ]; then kill "$ONLINE_PID" 2>/dev/null && wait "$ONLINE_PID" 2>/dev/null || true; fi
if [ -n "$CLAMD_ONLINE" ]; then podman rm -f "$CLAMD_ONLINE" >/dev/null 2>&1 || true; fi
if [ -n "$SECURE_POD" ]; then podman pod rm -f "$SECURE_POD" >/dev/null 2>&1 || true; fi
}
trap cleanup EXIT
# Run a command; on failure show its output and stop.
run() { "$@" >"$W/last.log" 2>&1 || die "command failed: $*"; }
# Run a command that MUST be refused.
refused() { if "$@" >"$W/last.log" 2>&1; then die "expected a refusal, and this succeeded: $*"; fi; }
for tool in podman openssl curl sha256sum python3; do
command -v "$tool" >/dev/null || die "$tool is required"
done
for b in holger-server holger-cli; do
[ -x "$BIN/$b" ] || die "$BIN/$b is missing"
done
SIGN="${HOLGER_SIGN_SLSA:-}"
for c in "$BIN/sign_slsa" "$BIN/examples/sign_slsa"; do
if [ -z "$SIGN" ] && [ -x "$c" ]; then SIGN="$c"; fi
done
[ -n "$SIGN" ] && [ -x "$SIGN" ] || die "the SLSA provenance signer sign_slsa is missing: cargo build --release -p holger-provenance --example sign_slsa"
for verb in export bundle intel intake; do
"$BIN/holger-server" "$verb" --help >/dev/null 2>&1 \
|| die "$BIN/holger-server has no '$verb' verb: build it with --features secure_update,sign"
done
free_port() { python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",0));print(s.getsockname()[1])'; }
OG=$(free_port); OH=$(free_port)
mkdir -p "$W/online" "$W/pki" "$W/usb" "$W/secure" "$W/dl"
echo "Holger airgap demo — work directory $W"
echo "receipt and log: $OUT"
plain "Holger airgap demo, started $STARTED (work directory $W)"
fact package "$PKG"
fact python_image "$PY_IMAGE"
fact clamav_image "$CLAMAV_IMAGE"
podman image exists "$SCAN_WORKER_IMAGE" || die "the scan worker image $SCAN_WORKER_IMAGE is not minted: cargo xtask scan-worker image"
fact scan_worker_image "$SCAN_WORKER_IMAGE"
# The podman API socket both Holgers start their scan containers through, and the
# job directory both they and the engine see at the same path.
mkdir -p "$W/sbx/jobs"
podman system service --time=0 "unix://$W/sbx/podman.sock" >/dev/null 2>&1 & SBX_PID=$!
for _ in $(seq 1 100); do [ -S "$W/sbx/podman.sock" ] && break; sleep 0.1; done
[ -S "$W/sbx/podman.sock" ] || die "the podman API service did not start"
SANDBOX_RON="rules: (sandbox: required), sandbox: Some((url: Some(\"unix://$W/sbx/podman.sock\"), work_dir: Some(\"$W/sbx/jobs\"), image: \"$SCAN_WORKER_IMAGE\")),"
fact holger_version "$("$BIN/holger-server" --version 2>/dev/null | head -1)"
fact holger_commit "$(git -C "$REPO" rev-parse --short=12 HEAD 2>/dev/null || echo unknown)"
# ─────────────────────────── the keys (never committed) ───────────────────────────
CUR="keys"
# The online Holger's evidence signer: a P-256 root and a leaf it issued.
openssl ecparam -name prime256v1 -genkey -noout -out "$W/pki/scan-root.key" 2>/dev/null
openssl req -x509 -new -key "$W/pki/scan-root.key" -subj "/CN=online holger scan root" -days 3 \
-addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,digitalSignature" \
-outform der -out "$W/pki/scan-root.der" 2>/dev/null
openssl x509 -inform der -in "$W/pki/scan-root.der" -out "$W/pki/scan-root.pem"
openssl ecparam -name prime256v1 -genkey -noout -out "$W/pki/scan.key" 2>/dev/null
openssl pkcs8 -topk8 -nocrypt -in "$W/pki/scan.key" -outform der -out "$W/pki/scan.pk8"
openssl req -new -key "$W/pki/scan.key" -subj "/CN=online holger scanners" -out "$W/pki/scan.csr" 2>/dev/null
printf 'basicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature\n' > "$W/pki/leaf.ext"
openssl x509 -req -in "$W/pki/scan.csr" -CA "$W/pki/scan-root.pem" -CAkey "$W/pki/scan-root.key" \
-CAcreateserial -days 3 -extfile "$W/pki/leaf.ext" -outform der -out "$W/pki/scan.der" 2>/dev/null
# The bundle's people: two approvers and a distinct release signer (ed25519).
for who in security-reviewer system-owner release-signer; do
openssl genpkey -algorithm ed25519 -out "$W/pki/$who.key" 2>/dev/null
openssl req -x509 -key "$W/pki/$who.key" -subj "/CN=$who" -days 3 -out "$W/pki/$who.crt" 2>/dev/null
done
for who in security-reviewer system-owner release-signer; do
openssl x509 -in "$W/pki/$who.crt" -outform der -out "$W/pki/$who.der"
openssl pkcs8 -topk8 -nocrypt -in "$W/pki/$who.key" -outform der -out "$W/pki/$who.pk8"
done
# The build system's provenance key (Ed25519): it signs the wheel's SLSA provenance.
# Both sides trust its public half, by key id, and the one builder id.
KEY_ENTRY=$("$SIGN" --new-key "$W/pki/demo-release.der" demo-release)
BUILDER="https://holger.rs/demo-builder/v1"
fp() { openssl x509 -in "$1" -outform der 2>/dev/null | sha256sum | cut -d' ' -f1; }
TI=$(openssl rand -hex 16); TA=$(openssl rand -hex 16); TB=$(openssl rand -hex 16); TU=$(openssl rand -hex 16)
printf '%s' "$TI" > "$W/pki/importer.token"; printf '%s' "$TA" > "$W/pki/sec-anna.token"; printf '%s' "$TB" > "$W/pki/sec-bert.token"
printf '%s' "$TU" > "$W/pki/auditor.token"
# The trust policies: the export bundle's, and the signed intel bundle's (S7).
policy_ron() { cat <<EOF
(schema_version:"holger.bundle-trust-policy/v1",trust_mode:Some(PinnedSigners),trusted_roots:[],
approved_signers:[(fingerprint:"$(fp "$W/pki/security-reviewer.crt")",roles:["security-reviewer"]),
(fingerprint:"$(fp "$W/pki/system-owner.crt")",roles:["system-owner"])],
required_approval_roles:["security-reviewer","system-owner"],minimum_distinct_approvers:2,
trusted_release_signers:["$(fp "$W/pki/release-signer.crt")"],release_signer_must_be_distinct:true,
require_expiry:true,on_expired_metadata:Some(ReadOnly)$1)
EOF
}
policy_ron ',require_sbom:true,require_scan:true' > "$W/usb/trust-policy.ron"
policy_ron ',allowed_products:["holger.intel/v1"]' > "$W/usb/intel-trust.ron"
# The malware engines' inputs ride the USB stick: the YARA rules as files, the
# ClamAV signature database as the CVD files of the pinned image.
cp -r "$REPO/scripts/demo/yara" "$W/usb/yara"
repo() { printf '(ron_name: "%s", ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "door")), ron_data_dir: Some("%s/%s")),\n' "$1" "$2" "$1"; }
# The malware engines one side runs: its ClamAV (the sandboxed clamd's unix socket: a real
# build talks to no other clamd, holger-sandbox-15), its YARA rules.
inspect_ron() { printf 'inspect: (clamav: Some((socket: Some("%s"), required: true)), yara: Some((rules: Some("%s"), required: true))),' "$1" "$2"; }
# The provenance policy both sides run (lane G3a): SLSA provenance signed by the
# demo-release key, from the one builder, at SLSA build level 2 or more, REQUIRED.
prov_ron() { printf 'provenance: Some((rules: (keys: [%s], builders: ["%s"]), default: require(keys: ["demo-release"], slsa_min: 2)%s)),' "$KEY_ENTRY" "$BUILDER" "$1"; }
ladder() { cat <<EOF
promotion: Some((
ladder_name: "intake",
stages: [
(name: "incoming", repo: "incoming"),
(name: "quarantine", repo: "quarantine"),
(name: "approved", repo: "approved"),
(name: "production", repo: "production"),
],
$1
intake: Some((
incoming: "incoming", quarantine: "quarantine",
approved: "approved", production: "production",
$2
)),
)),
EOF
}
D="$W/online"
cat > "$W/online.ron" <<EOF
(
repositories: [ $(repo incoming "$D") $(repo quarantine "$D") $(repo approved "$D") $(repo production "$D") ],
exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:$OG", ron_http_url: Some("127.0.0.1:$OH")) ],
storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "$D") ],
audit: (dir: "$D/audit", enabled: true),
auth: Some((
methods: [
StaticToken(subject: "importer", token: Some("$TI")),
StaticToken(subject: "sec-anna", token: Some("$TA")),
StaticToken(subject: "sec-bert", token: Some("$TB")),
StaticToken(subject: "auditor", token: Some("$TU")),
],
roles: { "importer": Writer },
duties: { "importer": [Importer], "sec-anna": [SecurityApprover], "sec-bert": [SecurityApprover], "auditor": [Auditor] },
)),
attestation: Some((
trust_roots: ["$W/pki/scan-root.der"],
signer_key: Some("$W/pki/scan.pk8"),
signer_cert: Some("$W/pki/scan.der"),
signer_alg: Some("ecdsa-p256"),
)),
intel: Some((db_dir: "$D/intel-db", offline: true, trust_policy: Some("$W/usb/intel-trust.ron"))),
$(ladder "deny_licenses: [\"GPL-3.0-only\", \"AGPL-3.0-only\"]," \
"$SANDBOX_RON
$(inspect_ron "$CLAMD_SOCK" "$W/usb/yara")
scan: Some((required: [inspection, malware, vulnerability, license, sbom, provenance],
$(prov_ron ", inbox: Some(\"$D/holger-provenance\")"))), approvals: 2,
transitions: [
(to: SCANNED, require_evidence: [scan]),
(to: POLICY_CHECKED, gates: [\"osv\"]),
(to: APPROVED, require_evidence: [approval]),
],")
)
EOF
# clamd answers PONG to PING over its unix socket once its database is loaded.
clamd_cmd() { python3 -I -c '
import socket, sys
s = socket.socket(socket.AF_UNIX); s.settimeout(2); s.connect(sys.argv[1])
s.sendall(b"z" + sys.argv[2].encode() + b"\0")
print(s.recv(4096).decode().strip("\0\n"))
' "$1" "$2"; }
# ─────────────────────────────── the online side ───────────────────────────────
step "Malware engines: ClamAV (pinned image and signature database) and the demo's YARA rules"
run podman pull -q "$CLAMAV_IMAGE"
CLAMD_ONLINE="$TAG-clamd-online"
# freshclam never runs: the signature database is exactly the pinned image's. clamd runs in
# its sandbox (no network, read-only root, no capabilities); its socket is the only way in.
CLAMD_SOCK="$W/clamd-online/clamd.sock"
run env CLAMAV_IMAGE="$CLAMAV_IMAGE" "$REPO/scripts/clamd-sandbox.sh" start "$CLAMD_ONLINE" "$W/clamd-online"
[ "$(clamd_cmd "$CLAMD_SOCK" PING 2>/dev/null)" = PONG ] || { podman logs "$CLAMD_ONLINE" >"$W/last.log" 2>&1 || true; die "clamd did not load its database"; }
CLAM_VERSION=$(clamd_cmd "$CLAMD_SOCK" VERSION)
info "clamd: $CLAM_VERSION"
fact clamav_version "$CLAM_VERSION"
# The same database files go onto the USB stick, with their hashes.
cid=$(podman create "$CLAMAV_IMAGE")
podman cp "$cid:/var/lib/clamav" "$W/usb/clamav-db" >"$W/last.log" 2>&1 || { podman rm "$cid" >/dev/null 2>&1; die "copying the ClamAV database out of the image"; }
podman rm "$cid" >/dev/null 2>&1 || true
(cd "$W/usb/clamav-db" && sha256sum -- *.cvd > ../clamav-db.sha256)
fact clamav_db "$(awk '{printf "%s %s; ", $2, $1}' "$W/usb/clamav-db.sha256")"
info "signature database: $(awk '{printf "%s ", $2}' "$W/usb/clamav-db.sha256")(sha256 in clamav-db.sha256, it rides the USB stick)"
info "YARA rules: $(cd "$W/usb/yara" && ls -- *.yar | tr '\n' ' ')(the built-in matcher; no yara binary)"
fact yara_rules "$(cd "$W/usb/yara" && sha256sum -- *.yar | awk '{printf "%s %s; ", $2, $1}')"
ok "clamd up with the pinned database; YARA rules loaded; both REQUIRED on both sides"
step "Offline vulnerability intelligence: OSV/NVD/KEV → signed intel bundle → verified import"
if [ -n "${DEMO_INTEL_LIVE:-}" ]; then
FEEDS=(--ecosystem PyPI)
info "fetching the live OSV PyPI, NVD and KEV feeds"
else
mkdir -p "$W/feeds"
cp "$REPO/intel/tests/fixtures/PYSEC-2018-28.json" "$W/feeds/osv-PyPI.json"
cp "$REPO/intel/tests/fixtures/nvd-recent.json" "$W/feeds/nvd-recent.json"
cp "$REPO/intel/tests/fixtures/kev.json" "$W/feeds/kev.json"
FEEDS=(--offline --feeds-dir "$W/feeds")
info "feeds: the fixture OSV/NVD/KEV files in intel/tests/fixtures (DEMO_INTEL_LIVE=1 fetches the live ones)"
fi
run "$BIN/holger-server" intel build "${FEEDS[@]}" --policy "$W/usb/intel-trust.ron" \
--release-cert "$W/pki/release-signer.der" --release-key "$W/pki/release-signer.pk8" \
--approve "security-reviewer=$W/pki/security-reviewer.der,$W/pki/security-reviewer.pk8" \
--approve "system-owner=$W/pki/system-owner.der,$W/pki/system-owner.pk8" --out "$W/usb/intel.holger.znippy"
grep '^BUILT' "$W/last.log" | while read -r l; do info "$l"; done
# The first import is an admin's authorization on this side (lane HOL-CDS, NCSC gap 2).
run "$BIN/holger-server" bundle allow-first-import --config "$W/online.ron" --from-bundle "$W/usb/intel.holger.znippy"
run env -u ESTATE_CLOCK "$BIN/holger-server" intel import "$W/usb/intel.holger.znippy" --config "$W/online.ron"
grep '^IMPORTED' "$W/last.log" | while read -r l; do info "$l"; done
ok "a signed, versioned intel bundle verified into the local advisory database (it rides the USB stick too)"
step "Start the ONLINE Holger (intake chain, two-person approval, signed evidence)"
# The server library's info lines: its boot says where the scans run.
RUST_LOG="${RUST_LOG:-warn,holger_server_lib=info}" "$BIN/holger-server" start --config "$W/online.ron" >"$W/online.log" 2>&1 &
ONLINE_PID=$!
for _ in $(seq 1 200); do curl -fsS "http://127.0.0.1:$OH/healthz" >/dev/null 2>&1 && break; sleep 0.05; done
curl -fsS "http://127.0.0.1:$OH/healthz" >/dev/null 2>&1 || { cp "$W/online.log" "$W/last.log"; die "the online Holger did not come up"; }
ok "listening on 127.0.0.1:$OH (HTTP) and :$OG (gRPC), pid $ONLINE_PID"
grep -q 'intake scans run in the scan sandbox' "$W/online.log" || { cp "$W/online.log" "$W/last.log"; die "the online Holger does not scan in the sandbox"; }
info "every scan runs in the scan sandbox: $SCAN_WORKER_IMAGE under rootless podman, --network none"
intake() { local who=$1; shift; "$BIN/holger-server" intake "$@" --server "127.0.0.1:$OG" --token-file "$W/pki/$who.token" --name "$WHEEL"; }
step "Import a Python package ($PKG) into the incoming repository"
if [ -n "${DEMO_WHEEL:-}" ]; then
cp "$DEMO_WHEEL" "$W/dl/"
else
run podman run --rm -v "$W/dl:/dl:Z" "$PY_IMAGE" pip download --quiet --no-deps --only-binary=:all: -d /dl "$PKG"
fi
WHEEL_PATH=$(ls "$W"/dl/*.whl | head -1)
WHEEL=$(basename "$WHEEL_PATH")
fact wheel "$WHEEL"
# The build's SLSA provenance comes with the wheel (DSSE + in-toto Statement v1),
# into the online Holger's provenance inbox under the wheel's sha256.
WSHA=$(sha256sum "$WHEEL_PATH" | cut -d' ' -f1)
mkdir -p "$D/holger-provenance/$WSHA"
"$SIGN" "$W/pki/demo-release.der" demo-release "$BUILDER" "$WHEEL_PATH" 2>/dev/null >"$D/holger-provenance/$WSHA/$WHEEL.intoto.jsonl"
info "SLSA provenance signed by demo-release for builder $BUILDER, beside the wheel"
code=$(curl -sS -o "$W/last.log" -w '%{http_code}' -X PUT -H "Authorization: Bearer $TI" \
--data-binary @"$WHEEL_PATH" "http://127.0.0.1:$OH/incoming/$WHEEL")
[ "$code" = 201 ] || die "PUT /incoming/$WHEEL answered $code"
intake importer state >"$W/last.log" 2>&1 || die "intake state"
grep -q '^INGESTED' "$W/last.log" || die "the wheel is not INGESTED"
ING=$(grep 'UNTRUSTED → INGESTED' "$W/last.log" || true)
ok "$WHEEL uploaded by 'importer': INGESTED"
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$OH/incoming/$WHEEL")
[ "$code" = 404 ] || die "an INGESTED wheel was downloadable ($code)"
info "not downloadable while INGESTED (GET /incoming → 404)"
step "Hash it"
LOCAL_SHA=$(sha256sum "$WHEEL_PATH" | cut -d' ' -f1)
REC_SHA=$(awk 'NR==1{print $3}' "$W/last.log")
[ "$LOCAL_SHA" = "$REC_SHA" ] || die "recorded sha256 $REC_SHA, file sha256 $LOCAL_SHA"
fact wheel_sha256 "$LOCAL_SHA"
ok "sha256 $REC_SHA recorded at INGESTED and re-checked at every later state"
step "A planted EICAR test file is refused by name (ClamAV and YARA, at the door)"
EICAR_WHEEL="eicar_demo-1.0-py3-none-any.whl"
python3 -I -c '
import sys, zipfile
# The EICAR test string, assembled from two halves so no file in the repository is it.
eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$" + "EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
with zipfile.ZipFile(sys.argv[1], "w", zipfile.ZIP_DEFLATED) as z:
z.writestr("eicar_demo/__init__.py", "V = 1\n")
z.writestr("eicar_demo/eicar.com", eicar)
z.writestr("eicar_demo-1.0.dist-info/METADATA", "Metadata-Version: 2.4\nName: eicar-demo\nVersion: 1.0\nLicense-Expression: MIT\n\n")
z.writestr("eicar_demo-1.0.dist-info/WHEEL", "Wheel-Version: 1.0\nGenerator: holger-demo\nRoot-Is-Purelib: true\nTag: py3-none-any\n")
z.writestr("eicar_demo-1.0.dist-info/RECORD", "")
' "$W/dl-eicar.whl"
code=$(curl -sS -o "$W/last.log" -w '%{http_code}' -X PUT -H "Authorization: Bearer $TI" \
--data-binary @"$W/dl-eicar.whl" "http://127.0.0.1:$OH/incoming/$EICAR_WHEEL")
case "$code" in 2??) die "the EICAR wheel was accepted ($code)";; esac
grep -q 'yara: EICAR_Test_File' "$W/last.log" || die "the refusal does not name the YARA rule EICAR_Test_File"
grep -qiE 'clamav: [^,;]*eicar' "$W/last.log" || die "the refusal does not name a ClamAV EICAR signature"
info "PUT /incoming/$EICAR_WHEEL → $code"
YARA_HIT=$(grep -o 'yara: EICAR_Test_File' "$W/last.log" | head -1)
CLAM_HIT=$(grep -oiE 'clamav: [^,;]*eicar[^,; ]*' "$W/last.log" | head -1)
info "refused by $CLAM_HIT and $YARA_HIT"
fact eicar_refusal "$code: $CLAM_HIT; $YARA_HIT"
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$OH/incoming/$EICAR_WHEEL")
[ "$code" = 404 ] || die "the refused wheel is stored ($code)"
ok "refused before a byte was stored (GET → 404), by name: $CLAM_HIT, $YARA_HIT"
step "Scan: run every scanner over the bytes (INGESTED → SCANNED, into quarantine)"
run intake importer advance --reason "scan on arrival"
intake importer state >"$W/state.log" 2>&1 || { cp "$W/state.log" "$W/last.log"; die "intake state"; }
grep -q '^SCANNED' "$W/state.log" || { cp "$W/state.log" "$W/last.log"; die "not SCANNED"; }
SLOTS=$(grep 'INGESTED → SCANNED' "$W/state.log" || true)
info "evidence: $(sed 's/.* at [0-9]* //' <<<"$SLOTS")"
ok "SCANNED; signed scan evidence stored by sha256"
# One evidence slot `kind:slot:source=verdict`, by its prefix.
slot() { tr ' ' '\n' <<<"$SLOTS" | grep -E "^$1" | head -1 || true; }
failing() { [[ "${1,,}" != *"=pass" ]]; }
# The slot into $S, or stop: it must be there, and it must pass.
need() {
S=$(slot "$1")
[ -n "$S" ] || die "no '$1' evidence: $SLOTS"
if failing "$S"; then die "$2 did not pass: $S"; fi
}
step "Inspect the archive (file inspection: zip bombs, traversal, MIME, executables)"
need 'malware:inspection:holger-inspect=' "the file inspection"
ok "$S (at INGESTED too: $(tr ' ' '\n' <<<"$ING" | grep -E '^malware:holger-inspect=' | head -1 || true))"
step "Malware scan (ClamAV and YARA)"
need 'malware:malware:clamav=' "ClamAV"; c=$S
need 'malware:malware:yara=' "YARA"; y=$S
ok "$c, $y"
step "CVE scan (OSV/NVD/KEV, against the verified local intel database)"
need 'scan:vulnerability:' "the CVE scan"
ok "$S (against the verified local intel database)"
step "License"
need 'license:license:' "the license scan"
ok "$S; the license POLICY (deny GPL-3.0/AGPL-3.0, unknown = hold) runs again at POLICY_CHECKED"
step "Secrets (private keys, cloud credentials, tokens, .env, kubeconfigs; in the scan sandbox)"
need 'other:secrets:holger-secrets=' "the secrets scan"
ok "$S; a finding names the rule and the path, never the secret"
step "SBOM (CycloneDX, bound to the wheel's sha256)"
need 'other:sbom:' "the SBOM"
ok "$S; the SBOM is signed as an attestation beside the scan evidence"
step "Provenance verified (SLSA provenance, DSSE + in-toto Statement v1, a require policy)"
need 'provenance:provenance:holger-provenance=' "the provenance check"
case "$S" in *"provenance:holger-provenance=pass"*) ;; *) die "the provenance slot is $S";; esac
ok "$S: signed by demo-release, builder $BUILDER, SLSA build level 2, under require"
step "Apply policy (SCANNED → POLICY_CHECKED: OSV + license policy gate)"
run intake importer advance --reason "policy"
intake importer state >"$W/last.log" 2>&1
grep -q '^POLICY_CHECKED' "$W/last.log" || die "not POLICY_CHECKED"
ok "POLICY_CHECKED"
step "Quarantine: a hold, released by a security approver (never by the importer)"
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$OH/production/$WHEEL")
[ "$code" = 404 ] || die "production served an unapproved wheel ($code)"
run intake sec-anna quarantine --reason "hold for review"
refused intake importer release --reason "the importer tries to release"
grep -q 'holger-duty' "$W/last.log" || die "the importer's release was not refused by a duty"
run intake sec-bert release --reason "reviewed"
intake importer state >"$W/last.log" 2>&1
grep -q '^POLICY_CHECKED' "$W/last.log" || die "the release did not return it to POLICY_CHECKED"
ok "QUARANTINED by sec-anna, the importer's release refused, released by sec-bert; nothing served meanwhile"
step "Security approval (two distinct approvers; the importer may not approve)"
refused intake importer advance --reason "approve my own import"
grep -q 'holger-duty' "$W/last.log" || die "the importer's approval was not refused by a duty"
info "importer refused: $(grep -o '\[holger-duty-[0-9]*\]' "$W/last.log" | head -1)"
run intake sec-anna advance --reason "approved: scan clean, license allowed"
grep -q '1 of 2' "$W/last.log" || die "the first approval did not wait for the second"
info "sec-anna: 1 of 2"
run intake sec-bert advance --reason "second approval"
intake importer state >"$W/last.log" 2>&1
grep -q '^APPROVED' "$W/last.log" || die "not APPROVED after two approvals"
ok "APPROVED by sec-anna and sec-bert (Importer ≠ Approver)"
step "Seal (APPROVED → SEALED → PROMOTED → AVAILABLE)"
for _ in SEALED PROMOTED AVAILABLE; do run intake sec-anna advance --reason "release"; done
intake importer state >"$W/last.log" 2>&1
grep -q '^AVAILABLE' "$W/last.log" || die "not AVAILABLE"
curl -fsS -o "$W/served.whl" "http://127.0.0.1:$OH/production/$WHEEL" || die "production does not serve the AVAILABLE wheel"
[ "$(sha256sum "$W/served.whl" | cut -d' ' -f1)" = "$LOCAL_SHA" ] || die "production served other bytes"
ok "AVAILABLE, served from the sealed content-addressed store with the ingested sha256"
step "Signed airgap export (the verdict is computed from the stored evidence)"
run "$BIN/holger-server" export --config "$W/online.ron" --candidate "$W/candidate" \
--bundle-id "pypi-$(date +%Y%m%d)-1" --sequence 1 --supplier online-holger --product pypi --release-version 1
grep -q 'verdict: pass (computed' "$W/last.log" || die "the export did not compute a pass verdict"
sed -n '2,3p' "$W/last.log" | while read -r l; do info "$l"; done
run "$BIN/holger-cli" bundle approve --candidate "$W/candidate" --role security-reviewer --cert "$W/pki/security-reviewer.crt" --key "$W/pki/security-reviewer.key"
run "$BIN/holger-cli" bundle approve --candidate "$W/candidate" --role system-owner --cert "$W/pki/system-owner.crt" --key "$W/pki/system-owner.key"
run "$BIN/holger-cli" bundle finalize --candidate "$W/candidate" --policy "$W/usb/trust-policy.ron" \
--release-cert "$W/pki/release-signer.crt" --release-key "$W/pki/release-signer.key" --output "$W/usb/pypi.holger.znippy"
# What the secure side trusts for evidence: the online scan root, and the one enrolled signer.
cp "$W/pki/scan-root.der" "$W/usb/scan-root.der"; cp "$W/pki/scan.der" "$W/usb/scan-signer.der"
fact bundle_sha256 "$(sha256sum "$W/usb/pypi.holger.znippy" | cut -d' ' -f1)"
ok "bundle on the USB stick: two bundle approvals, a distinct release signer ($(du -h "$W/usb/pypi.holger.znippy" | cut -f1))"
step "A second bundle whose attestation was tampered with after the scan (the secure side must catch it)"
# An attacker on the online side rewrites the stored attestation after the scan:
# the builder id in the signed statement changes, and the signature stays.
python3 -I -c '
import base64, json, sys
p = sys.argv[1]
env = json.loads(open(p).read())
st = json.loads(base64.b64decode(env["payload"]))
st["predicate"]["runDetails"]["builder"]["id"] = "https://evil.example/builder"
env["payload"] = base64.b64encode(json.dumps(st).encode()).decode()
open(p, "w").write(json.dumps(env) + "\n")
' "$D/holger-provenance/$WSHA/$WHEEL.intoto.jsonl"
run "$BIN/holger-server" export --config "$W/online.ron" --candidate "$W/candidate2" \
--bundle-id "pypi-$(date +%Y%m%d)-2" --sequence 2 --supplier online-holger --product pypi --release-version 2
run "$BIN/holger-cli" bundle approve --candidate "$W/candidate2" --role security-reviewer --cert "$W/pki/security-reviewer.crt" --key "$W/pki/security-reviewer.key"
run "$BIN/holger-cli" bundle approve --candidate "$W/candidate2" --role system-owner --cert "$W/pki/system-owner.crt" --key "$W/pki/system-owner.key"
run "$BIN/holger-cli" bundle finalize --candidate "$W/candidate2" --policy "$W/usb/trust-policy.ron" \
--release-cert "$W/pki/release-signer.crt" --release-key "$W/pki/release-signer.key" --output "$W/usb/pypi-tampered.holger.znippy"
ok "a properly signed bundle (seq 2) carries the rewritten attestation to the USB stick"
step "Audit trail on the online side (one tamper-evident trail per artifact), and audit verify"
run "$BIN/holger-server" audit trail --server "127.0.0.1:$OG" --token-file "$W/pki/auditor.token" --sha256 "$LOCAL_SHA"
for k in imported hash_verified scanned policy_passed approved sealed exported; do
grep -qE "^ #[0-9]+ +$k " "$W/last.log" || die "the online trail has no '$k' step"
done
info "$(head -1 "$W/last.log")"
info "steps: $(grep -E '^ #' "$W/last.log" | awk '{print $2}' | tr '\n' ' ')"
run "$BIN/holger-server" audit verify --config "$W/online.ron"
head -3 "$W/last.log" | while read -r l; do info "$l"; done
ok "the trail runs imported → hash_verified → scanned → policy_passed → approved → sealed → exported, and the chain verifies"
kill "$ONLINE_PID" 2>/dev/null; wait "$ONLINE_PID" 2>/dev/null || true; ONLINE_PID=""
podman rm -f "$CLAMD_ONLINE" >/dev/null 2>&1 || true; CLAMD_ONLINE=""
info "the online Holger and its clamd are stopped"
# The scan worker image rides the USB stick with its sha256; the secure side never pulls.
run podman save --format oci-archive -o "$W/usb/holger-scan-worker.oci.tar" "$SCAN_WORKER_IMAGE"
(cd "$W/usb" && sha256sum holger-scan-worker.oci.tar > holger-scan-worker.oci.tar.sha256)
fact scan_worker_archive "$(cut -d' ' -f1 "$W/usb/holger-scan-worker.oci.tar.sha256")"
info "the USB stick holds: $(cd "$W/usb" && ls | tr '\n' ' ')"
# ─────────────────────────────── the secure side ───────────────────────────────
SD=/secure
SH=18480; SG=18481
{
printf '(\n network: Offline,\n repositories: [ '
printf '(ron_name: "bundles", ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: None, ron_data_dir: Some("%s/bundles")), ' "$SD"
printf '%s %s %s %s ],\n' "$(repo incoming $SD)" "$(repo quarantine $SD)" "$(repo approved $SD)" "$(repo production $SD)"
printf ' exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:%s", ron_http_url: Some("127.0.0.1:%s")) ],\n' "$SG" "$SH"
printf ' storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "%s") ],\n' "$SD"
printf ' audit: (dir: "%s/audit", enabled: true),\n' "$SD"
printf ' attestation: Some((trust_roots: ["/usb/scan-root.der"], enrolled_signers: ["/usb/scan-signer.der"])),\n'
printf ' intel: Some((db_dir: "%s/intel-db", offline: true, trust_policy: Some("/usb/intel-trust.ron"))),\n' "$SD"
# The secure side's OWN engines: the clamd beside it in the pod (the database
# from the USB stick) and the YARA rules from the USB stick.
# Its scans run in the scan sandbox too; the YARA rules are named by the path the
# engine sees (the USB stick is mounted at the same path inside the pod).
ladder "" "$SANDBOX_RON $(inspect_ron "/clamd/clamd.sock" "$W/usb/yara") scan: Some((required: [provenance], $(prov_ron ""))),"
printf ')\n'
} > "$W/secure/secure.ron"
cat > "$W/secure/secure-side.sh" <<'EOS'
#!/usr/bin/env bash
# Runs INSIDE the secure pod: --network none, loopback only.
set -Eeuo pipefail
H=/holger/holger-server
say() { printf '%s\n' "$*"; }
fail() { say "SECURE-FAIL $*"; exit 1; }
clamd() { python3 -I -c '
import socket, sys
s = socket.socket(socket.AF_UNIX); s.settimeout(2); s.connect("/clamd/clamd.sock")
s.sendall(b"z" + sys.argv[1].encode() + b"\0")
print(s.recv(4096).decode().strip("\0\n"))
' "$1"; }
say "STEP no-internet"
if python3 -c 'import socket; socket.create_connection(("pypi.org", 443), 3)' 2>/dev/null; then fail "the secure side reached pypi.org"; fi
if python3 -c 'import socket; socket.create_connection(("1.1.1.1", 443), 3)' 2>/dev/null; then fail "the secure side reached 1.1.1.1"; fi
say "OK the secure side has no route out (pypi.org and 1.1.1.1 unreachable); interfaces: $(ls /sys/class/net | tr '\n' ' ')"
say "STEP clamd"
(cd /usb/clamav-db && sha256sum -c --quiet ../clamav-db.sha256) || fail "the ClamAV database on the USB stick does not match its hashes"
for _ in $(seq 1 240); do [ "$(clamd PING 2>/dev/null)" = PONG ] && break; sleep 0.5; done
[ "$(clamd PING 2>/dev/null)" = PONG ] || fail "the secure clamd did not load the carried database"
say "OK the secure side's clamd: $(clamd VERSION) (database from the USB stick, hashes match)"
[ "$(clamd VERSION)" = "CLAM_VERSION" ] || fail "the secure clamd runs another database than the online one: $(clamd VERSION)"
say "OK the same signature database as the online side"
(cd /usb && sha256sum -c --quiet holger-scan-worker.oci.tar.sha256) || fail "the scan worker image on the USB stick does not match its sha256"
say "OK the scan worker image on the USB stick matches its sha256 (the secure side never pulls an image)"
say "STEP tampered"
cp /usb/pypi.holger.znippy /tmp/tampered.holger.znippy
python3 - <<'PY'
p = "/tmp/tampered.holger.znippy"
b = bytearray(open(p, "rb").read()); b[len(b) // 2] ^= 1; open(p, "wb").write(bytes(b))
PY
if $H bundle import --config /secure/secure.ron --bundle /tmp/tampered.holger.znippy \
--policy /usb/trust-policy.ron >/tmp/tampered.log 2>&1; then
cat /tmp/tampered.log; fail "a tampered bundle imported"
fi
say "OK refused: $(grep -v '^⚠' /tmp/tampered.log | tail -1 | cut -c1-160)"
say "STEP import"
# The admin authorizes the first import of exactly this bundle (lane HOL-CDS, NCSC gap 2).
$H bundle allow-first-import --config /secure/secure.ron --from-bundle /usb/pypi.holger.znippy \
--policy /usb/trust-policy.ron >/tmp/grant.log 2>&1 || { cat /tmp/grant.log; fail "the first-import authorization failed"; }
$H bundle import --config /secure/secure.ron --bundle /usb/pypi.holger.znippy \
--policy /usb/trust-policy.ron >/tmp/import.log 2>&1 || { cat /tmp/import.log; fail "the verified import failed"; }
grep -E 'VERIFIED|LANDED|IMPORTED' /tmp/import.log | sed 's/^ */OK /'
grep -q 'LANDED .* AVAILABLE' /tmp/import.log || { cat /tmp/import.log; fail "nothing landed AVAILABLE"; }
grep -q 'airgap:' /secure/audit/* || fail "the landing left no audit record"
say "OK audited: $(cat /secure/audit/* | grep -c 'airgap:') airgap audit records on the secure side"
grep -h 'provenance verified again on the airgap side' /secure/audit/* | grep -q 'WHEEL_SHA' || fail "the secure side's own provenance check left no audit record"
say "OK the secure side verified the carried provenance itself, before landing, audited"
say "STEP provenance-tampered"
if $H bundle import --config /secure/secure.ron --bundle /usb/pypi-tampered.holger.znippy \
--policy /usb/trust-policy.ron >/tmp/ptamper.log 2>&1; then
cat /tmp/ptamper.log; fail "a bundle with a tampered attestation imported"
fi
grep -q 'holger-provenance-3' /tmp/ptamper.log || { cat /tmp/ptamper.log; fail "the tampered attestation was not refused as holger-provenance-3"; }
say "OK refused: $(grep -o '\[holger-provenance-3\].*' /tmp/ptamper.log | head -1 | cut -c1-200)"
say "STEP intel"
$H bundle allow-first-import --config /secure/secure.ron --from-bundle /usb/intel.holger.znippy >/tmp/igrant.log 2>&1 \
|| { cat /tmp/igrant.log; fail "the intel first-import authorization failed"; }
env -u ESTATE_CLOCK $H intel import /usb/intel.holger.znippy --config /secure/secure.ron >/tmp/intel.log 2>&1 \
|| { cat /tmp/intel.log; fail "the intel bundle did not import"; }
grep '^IMPORTED' /tmp/intel.log | sed 's/^/OK /'
$H intel status --config /secure/secure.ron 2>&1 | grep '^INTEL' | sed 's/^/OK /'
say "STEP serve"
$H start --config /secure/secure.ron >/tmp/secure.log 2>&1 &
SP=$!
for _ in $(seq 1 200); do python3 -c "import urllib.request;urllib.request.urlopen('http://127.0.0.1:SH_PORT/healthz',timeout=1)" 2>/dev/null && break; sleep 0.05; done
grep -q 'network: Offline' /tmp/secure.log || { cat /tmp/secure.log; kill $SP; fail "the server did not start offline"; }
say "OK secure Holger up, offline: $(grep -o 'network: Offline.*' /tmp/secure.log | head -1)"
say "STEP rescan"
$H intake state --server 127.0.0.1:SG_PORT --name WHEEL_NAME >/tmp/state.log 2>&1 || { cat /tmp/state.log; kill $SP; fail "intake state on the secure side"; }
grep -q '^AVAILABLE' /tmp/state.log || { cat /tmp/state.log; kill $SP; fail "not AVAILABLE on the secure side"; }
grep -q 'provenance:provenance:holger-provenance=pass' /tmp/state.log || { cat /tmp/state.log; kill $SP; fail "no provenance evidence on the secure record"; }
say "OK the carried SLSA provenance verified again on this side (provenance:holger-provenance=pass)"
grep -q 'malware:secure-side-rescan=pass' /tmp/state.log || { cat /tmp/state.log; kill $SP; fail "the secure side did not re-scan the import with its own engines"; }
say "OK AVAILABLE; INGESTED carries malware:secure-side-rescan=pass (this side's clamd and YARA, before anything was written)"
say "STEP trail"
$H audit trail --server 127.0.0.1:SG_PORT --sha256 WHEEL_SHA >/tmp/trail.log 2>&1 || { cat /tmp/trail.log; kill $SP; fail "audit trail failed"; }
for k in hash_verified airgap_imported; do grep -qE "^ #[0-9]+ +$k " /tmp/trail.log || { cat /tmp/trail.log; kill $SP; fail "the secure trail has no '$k' step"; }; done
say "OK $(head -1 /tmp/trail.log)"
say "OK steps: $(grep -E '^ #' /tmp/trail.log | awk '{print $2}' | tr '\n' ' ')"
$H audit verify --config /secure/secure.ron >/tmp/verify.log 2>&1 || { cat /tmp/verify.log; kill $SP; fail "audit verify failed"; }
say "OK $(head -1 /tmp/verify.log)"
say "STEP pip"
INDEX=http://127.0.0.1:SH_PORT/production/simple/
python3 - "$INDEX" <<'PY' || { kill $SP; fail "the simple index does not list the wheel with its sha256"; }
import sys, urllib.request
idx = sys.argv[1]
root = urllib.request.urlopen(idx).read().decode()
proj = root.split('href="')[1].split('/"')[0]
page = urllib.request.urlopen(idx + proj + "/").read().decode()
link = page.split('href="')[1].split('"')[0]
print("OK PEP 503 index lists", proj, "->", link.split("/")[-1].replace("#", " #"))
PY
pip install --no-cache-dir --disable-pip-version-check --index-url "$INDEX" PKG_SPEC >/tmp/pip.log 2>&1 \
|| { cat /tmp/pip.log; kill $SP; fail "pip install failed"; }
grep -E '^Successfully installed' /tmp/pip.log | sed 's/^/OK /'
python3 -c "import importlib.metadata as m; print('OK import works:', 'PKG_NAME', m.version('PKG_NAME'))"
if pip install --no-cache-dir --disable-pip-version-check --index-url "$INDEX" requests >/tmp/pip2.log 2>&1; then
kill $SP; fail "pip installed a package that was never imported"
fi
say "OK a package that was never approved and exported is not installable (requests: not found)"
kill $SP; wait $SP 2>/dev/null || true
say "SECURE-DONE"
EOS
PKG_NAME="${PKG%%[=<>]*}"
sed -i "s/SH_PORT/$SH/g; s/SG_PORT/$SG/g; s/WHEEL_SHA/$LOCAL_SHA/g; s/WHEEL_NAME/$WHEEL/g; s/PKG_SPEC/$PKG/g; s/PKG_NAME/$PKG_NAME/g; s|CLAM_VERSION|$CLAM_VERSION|g" "$W/secure/secure-side.sh"
step "Carry the USB stick to the secure side (a podman pod with --network none: no internet)"
SECURE_POD="$TAG-secure"
run podman pod create --name "$SECURE_POD" --network none
# The secure side's clamd, beside the secure Holger in the pod, on the database the USB stick
# carried, in its sandbox as on the online side; its unix socket is shared with the Holger only.
mkdir -p "$W/secure-clamd" && chmod 0777 "$W/secure-clamd"
run podman run -d --pod "$SECURE_POD" --name "$SECURE_POD-clamd" --read-only --cap-drop all \
--security-opt no-new-privileges --user 100:101 --tmpfs /tmp:rw,size=1g,mode=1777 \
-v "$W/usb/clamav-db:/var/lib/clamav:ro,Z" -v "$W/secure-clamd:/run/clamd:rw,z" \
-v "$REPO/server/lib/src/sandbox/clamd.conf:/etc/clamav/clamd-sandbox.conf:ro,z" \
--entrypoint clamd "$CLAMAV_IMAGE" --config-file=/etc/clamav/clamd-sandbox.conf
podman run --rm --pod "$SECURE_POD" \
-v "$W/usb:/usb:ro,Z" -v "$W/usb:$W/usb:ro,Z" -v "$W/sbx:$W/sbx:Z" -v "$W/secure:/secure:Z" -v "$BIN:/holger:ro,Z" -v "$W/secure-clamd:/clamd:z" \
"$PY_IMAGE" bash /secure/secure-side.sh >"$W/secure.out" 2>&1 || true
cp "$W/secure.out" "$OUT/secure-side.out"
section() { awk -v s="STEP $1" '$0==s{on=1;next} /^STEP /{on=0} on' "$W/secure.out"; }
check_section() {
local name=$1 what=$2
if grep -q '^SECURE-FAIL' <(section "$name"); then
cp "$W/secure.out" "$W/last.log"; die "$what: $(grep '^SECURE-FAIL' "$W/secure.out" | head -1)"
fi
section "$name" | grep -q '^OK' || { cp "$W/secure.out" "$W/last.log"; die "$what did not run"; }
section "$name" | grep '^OK' | sed 's/^OK //' | while read -r l; do info "$l"; done
}
check_section no-internet "the no-internet check"
ok "the secure side cannot reach the internet"
step "The secure side's own ClamAV, on the signature database the USB stick carried"
check_section clamd "the secure clamd"
ok "clamd up with no network, on the same pinned database"
step "A tampered bundle is refused at import"
check_section tampered "the tampered-bundle check"
ok "one flipped byte: refused before anything is written"
step "Verified import (bundle signature, per-artifact hashes, evidence attestations)"
check_section import "the verified import"
ok "landed AVAILABLE in the secure production repository, provenance and audit kept"
step "The tampered attestation is refused on the secure side (its own provenance check, under require)"
check_section provenance-tampered "the tampered-provenance check"
ok "the second bundle's signatures are all valid, and the secure side still refuses it by holger-provenance-3; nothing landed"
step "The secure side's own advisory database, from the same signed intel bundle"
check_section intel "the secure intel import"
ok "verified offline into the secure side's local advisory database"
step "The secure Holger serves offline"
check_section serve "the secure server"
ok "network: Offline; every outbound dial is refused by the server"
step "The secure side re-checked the import before it landed: provenance and its own malware engines"
check_section rescan "the secure-side rescan"
ok "the carried provenance verified under this side's require policy; ClamAV and YARA on this side passed the bytes"
step "Audit trail on the secure side (hash_verified → airgap_imported), and audit verify"
check_section trail "the secure audit trail"
ok "the secure trail carries the import, keyed by the same sha256, and the chain verifies"
step "pip install from the secure Holger (unmodified pip, PEP 503 index with hashes)"
check_section pip "pip install"
grep -q '^SECURE-DONE' "$W/secure.out" || { cp "$W/secure.out" "$W/last.log"; die "the secure side did not finish"; }
ok "pip installed $PKG from the imported, verified wheel"
printf '\n══════════════════════ summary ══════════════════════\n'
plain ""
plain "summary"
for r in "${RESULTS[@]}"; do printf ' %s\n' "$r"; plain " $r"; done
pass=$(printf '%s\n' "${RESULTS[@]}" | grep -c '^PASS' || true)
nyet=$(printf '%s\n' "${RESULTS[@]}" | grep -c '^NOT YET' || true)
result=$(receipt done)
printf '\n%s steps passed, %s NOT YET (lanes still running). No step was faked. Result: %s\n' "$pass" "$nyet" "$result"
plain "$pass steps passed, $nyet NOT YET. No step was faked. Result: $result"
printf 'receipt: %s/receipt.json\nlog: %s/demo.log\n' "$OUT" "$OUT"
rm -f "$OUT/checks.tsv" "$OUT/facts.tsv"
if [ -z "${DEMO_KEEP:-}" ]; then rm -rf "$W"; else echo "work directory kept: $W"; fi