# This is a representative Air-Gap Check transcript for a correctly hardened # station. Every check line below is the shipped script's own output string, # quoted from lib/airgap-check.sh, and the section order is the order the script # asks the questions (and the order security.html lists them). It is a # reconstruction, not a capture from one machine, so no host identifiers appear # in it, and the version in the banner is the 1.0 demo fixture this gallery uses # throughout rather than the number a pre-release box prints today. # # The one part of this file that anyone can reproduce anywhere is the hermetic # self-test at the bottom — that IS a real run, made with --self-test. Air-Gap Check — Bitcoin Witness v1.0 2026-05-27T14:23:07Z ───────────────────────────────────────── 1. Network interfaces: [PASS] Interface eth0: DOWN or not UP [PASS] Interface wlan0: DOWN or not UP 2. Default routes: [PASS] No default routes [PASS] No IPv6 default routes 3. DNS resolvers: [PASS] No nameserver entries in /etc/resolv.conf 4. Listening network sockets: [PASS] No non-loopback listening sockets 5. Wireless interfaces: [..] iw could not enumerate (wireless stack absent — expected on a hardened box); /sys scan + Check 7 are authoritative [..] iwconfig could not enumerate (wireless extensions absent — expected); /sys scan + Check 7 are authoritative [PASS] No wireless interfaces detected 6. rfkill status: [PASS] rfkill: all interfaces soft-blocked (or none present) 7. Wireless kernel modules: [PASS] No wireless kernel modules loaded 8. Surveillance daemons: [PASS] No surveillance daemons running 9. Wireless firmware (Layer 3): [PASS] No wireless firmware present under /lib/firmware (Layer 3) ───────────────────────────────────────── AIR-GAP: CONFIRMED All checks passed. Device appears to be air-gapped. ======================================================================== LAYER REFERENCE — what each layer does (editorial summary; see 01-harden.sh and lib/airgap-check.sh) ======================================================================== The air-gap is enforced at four independent hardware/software layers: Layer 1 — Device-tree overlay (hardware disable) /boot/config.txt entries applied by 01-harden.sh: dtoverlay=disable-wifi dtoverlay=disable-bt Effect: instructs the SoC firmware to not initialize the Wi-Fi and Bluetooth hardware. The chip is present but not powered on. Visible in check 1 (interfaces) and check 5 (wireless sysfs). Layer 2 — Kernel module blacklist (driver disable) /etc/modprobe.d/sentinel-blacklist.conf applied by 01-harden.sh: blacklist brcmfmac blacklist brcmutil blacklist cfg80211 blacklist mac80211 blacklist hci_uart blacklist btbcm blacklist btqca blacklist btintel blacklist bluetooth blacklist rfcomm blacklist bnep blacklist rtl8xxxu blacklist rtl8192ce blacklist rtl8192cu blacklist rtl8192se blacklist rtl8192de blacklist rtl8723ae blacklist rtl8723be blacklist rtl8188ee blacklist rtl8821ae blacklist rtl8192ee blacklist rtl8723de blacklist mt76x0u blacklist mt76x2u blacklist mt7601u blacklist libertas_sdio blacklist libertas blacklist mwifiex blacklist mwifiex_sdio blacklist mwifiex_pcie Effect: even if the hardware is powered, the kernel has no driver loaded to communicate with it. The module names above are for Raspberry Pi 4/5; Pi Zero 2W uses the same brcmfmac driver. Visible in check 7 (lsmod). Layer 3 — Firmware deletion (irreversible without reinstall) 01-harden.sh deletes the wireless firmware directories: /lib/firmware/brcm /lib/firmware/cypress /lib/firmware/rtl_nic /lib/firmware/rtlwifi /lib/firmware/rtw88 /lib/firmware/rtw89 /lib/firmware/mediatek /lib/firmware/intel /lib/firmware/ath10k /lib/firmware/ath11k /lib/firmware/ath9k_htc /lib/firmware/qca and the known loose USB-radio blobs (iwlwifi-*, mt7601u.bin, mt7650*.bin). Effect: even if a module were somehow loaded, the driver would fail to initialize the hardware because the firmware file it needs is absent. This is the hardest layer to accidentally bypass. Verified at runtime by check 9 (wireless firmware read under /lib/firmware): a present wireless-firmware directory is a defense-in-depth gap that WARNs and is escalated to FAIL under --strict. Also confirmed by the install log. Layer 4 — rfkill block + service masking (OS-level disable) 01-harden.sh runs: rfkill block all And masks these systemd units: wpa_supplicant.service bluetooth.service avahi-daemon.service avahi-daemon.socket cups.service cups.socket ssh.service ssh.socket chrony.service systemd-timesyncd.service It also disables the unattended-upgrade and packagekit timers and units. Effect: software kill switch that survives a reboot (rfkill state is persistent on PiOS). Even if layers 1-3 were bypassed, the OS-level block prevents association with any network. Service masking prevents the daemons from starting even if triggered by a dependency. Visible in check 6 (rfkill) and check 8 (pgrep surveillance daemons). Additional runtime assertions (checks 1-4): - Check 1: No non-loopback interface in UP state (ip link show) - Check 2: No default route exists (ip route show default) - Check 3: No non-loopback nameserver in /etc/resolv.conf - Check 4: No non-loopback listening socket (ss -tuln) These runtime checks would catch any unexpected re-enabling of network capability — for example, if a future OS update re-enabled a service that was masked, or if a user accidentally ran `rfkill unblock wifi`. There are three verdicts, not two. A single [FAIL] gives AIR-GAP: VIOLATED and exit code 1. If nothing failed but a check could not be completed — its tool missing or erroring — the verdict is AIR-GAP: NOT CONFIRMED, which says the box may well be air-gapped but this run did not establish it; --strict turns those into failures. Only a clean sweep prints AIR-GAP: CONFIRMED. Check 9, the wireless-firmware read, WARNs rather than fails unless --strict is passed, because firmware without its blacklisted module is inert. Every workflow calls airgap-check as its first action, shows the verdict, and continues only on explicit consent. To run it on your own station: /usr/local/bin/airgap-check or bash /home/sentinel/tools/lib/airgap-check.sh Your output will differ in the details a machine reports — an interface name, a loopback socket a running app happens to hold — and should carry the same nine sections and the same verdict. Source code: /home/sentinel/tools/lib/airgap-check.sh (mode 755, inside a 700 directory) No external dependencies. No network calls. Plain bash. ======================================================================== SELF-TEST — the one part of this file you can reproduce anywhere ======================================================================== The checks above read a running machine, so they need a station. The predicates BEHIND them do not: --self-test drives each one with synthetic input and asserts the verdict, with no network and no hardware. It runs on any Linux box, and on this one it was run to produce exactly the output below. bash /home/sentinel/tools/lib/airgap-check.sh --self-test airgap-check --self-test (hermetic; no network, no hardware) ───────────────────────────────────────── [PASS] self-test: eth0 UP -> violation [PASS] self-test: eth0 DOWN -> ok [PASS] self-test: lo UP -> ok (skipped) [PASS] self-test: default route present -> violation [PASS] self-test: no default route -> ok [PASS] self-test: public DNS -> violation [PASS] self-test: loopback DNS -> ok [PASS] self-test: ::1 DNS -> ok [PASS] self-test: 127.x socket -> loopback [PASS] self-test: wildcard:8333 -> wildcard (violation class) [PASS] self-test: bound non-lo addr -> other (warn/strict-fail) [PASS] self-test: IPv6-any [::]:22 -> wildcard (SA-01) [PASS] self-test: IPv4-any 0.0.0.0:22 -> wildcard (SA-01) [PASS] self-test: IPv6 loopback [::1]:631 -> loopback (SA-01) [PASS] self-test: port-0 all-iface [::]:0 -> loopback (benign) [PASS] self-test: brcmfmac -> wireless module [PASS] self-test: cfg80211 -> wireless module [PASS] self-test: ext4 -> not a wireless module [PASS] self-test: wpa_supplicant -> daemon [PASS] self-test: bluetoothd -> daemon [PASS] self-test: sshd -> not tracked [PASS] self-test: Soft blocked: no -> unblocked [PASS] self-test: Soft blocked: yes -> ok [PASS] self-test: present fw path -> violation [PASS] self-test: absent fw path -> ok [PASS] self-test: WIRELESS_FW_DIRS non-empty ───────────────────────────────────────── AIRGAP-CHECK SELF-TEST: ALL PASSED Twenty-six predicates, including the failure injections — an interface UP, a bound default route, public DNS, a wildcard listener on :22 — so a check that silently stopped detecting would fail here rather than print green.