- Shell 100%
| lainos-security-status | ||
| LICENSE | ||
| openrc-security-status.sh | ||
| protocol7-core-security-status | ||
| README.md | ||
🔍 Security Verification Suite
LainOS Layer 02 ships with three complementary security verification tools that work together to confirm your system's security posture at every level:
| Tool | Purpose | Impact | When to Run |
|---|---|---|---|
lainos-security-status |
Read-only status dashboard | None — safe anytime | Daily checks, troubleshooting |
openrc-security-status |
Isolation stack verification | None — read-only | After configuration changes |
protocol7-core-security-status |
Adversarial test suite | Brief service interruption | After security changes, before releases |
Together, they move beyond "configuration review" to runtime verification — they tell you what the system is actually doing, not just what it's configured to do.
lainos-security-status
A read-only status dashboard that reports current security state without attempting to break anything. Safe to run any time, including in production, with no side effects beyond reading /proc, dmesg, and calling aa-status/rc-service status.
Covers:
- Installed package versions (
lainos-apparmor,protocol7-core,apparmor) - AppArmor kernel module state, profile counts (enforce/complain/unconfined)
- Which profiles are currently enforced against running processes
- OpenRC service status for core daemons and the DNS/networking stack
- AppArmor denial count since boot (via
dmesg), with the most recent denials summarized - Basic connectivity and NTP sync state
- Per-daemon hardening state for Protocol 7 daemons:
- AppArmor enforcement
- Capability bounding set
- Seccomp filter mode
- Privilege drop (running as
nobody)
- Boot-time enforce symlink presence for each profile
lainos-security-status
📋 Example output (click to expand)
doas (lain@navi) password:
╔══════════════════════════════════════════╗
║ LainOS Layer 02 — Security Status ║
╚══════════════════════════════════════════╝
Host: navi
Kernel: 7.1.4-arch1-1
Uptime: up 10 hours, 54 minutes
--- Layer 02 Packages ---
lainos-apparmor 1.2.1-29
protocol7-core 5.5.3-27
apparmor 4.1.7-1
--- AppArmor Global State ---
✓ AppArmor kernel module active
Total profiles: 24
Enforce mode: 24
Complain mode: 0
Unconfined procs: 31
--- Enforced Profiles ---
/usr/bin/chronyd
/usr/bin/dhcpcd
/usr/bin/dnsmasq
/usr/bin/gpg
/usr/bin/gpg-agent
/usr/bin/keepassxc
/usr/bin/mpv
/usr/bin/nft
/usr/bin/pipewire
/usr/bin/pipewire-pulse
/usr/bin/snowflake-pt-client
/usr/bin/ssh
/usr/bin/stubby
/usr/bin/syslog-ng
/usr/bin/tor
/usr/bin/unbound
/usr/bin/vlc
/usr/bin/wireplumber
/usr/lib/iwd/iwd
/usr/lib/librewolf/librewolf
/usr/libexec/lainos/lainos-dbus-bridge
/usr/libexec/lainos/lainos-init
/usr/libexec/lainos/lainos-notifyd
✓ /usr/bin/chronyd (8405)
✓ /usr/bin/dhcpcd (8237)
✓ /usr/bin/dnsmasq (9945)
✓ /usr/bin/unbound (9908)
✓ /usr/libexec/lainos/lainos-dbus-bridge (16715)
✓ /usr/libexec/lainos/lainos-notifyd (16773)
--- OpenRC Services ---
✓ dhcpcd running
✓ dnsmasq running
✓ unbound running
✓ dnscrypt-proxy running
✓ iwd running
✓ chrony running
✓ syslog-ng running
✓ lainos-dbus-bridge running
✓ lainos-notifyd running
--- AppArmor Denials ---
✗ 0 denials since boot
--- Network & Time ---
✓ Internet connectivity OK
✓ NTP sync: Normal
--- Protocol 7 Daemon Hardening ---
✓ lainos-dbus-bridge (PID 16715) AppArmor enforced
✓ lainos-dbus-bridge capability bounding set empty
✓ lainos-dbus-bridge seccomp filter mode
✓ lainos-dbus-bridge running as nobody
✓ lainos-notifyd (PID 16773) AppArmor enforced
✓ lainos-notifyd capability bounding set empty
✓ lainos-notifyd seccomp filter mode
✓ lainos-notifyd running as nobody
--- Boot Security Chain ---
✓ lainos-dbus-bridge enforce symlink present
✓ lainos-notifyd enforce symlink present
========================================
openrc-security-status
A per-service containment layer verifier that checks every OpenRC service's isolation stack at runtime. Unlike configuration review, this reads the actual kernel state for each running service to confirm:
- Mount namespace isolation (own mount namespace vs. host)
- Network namespace isolation (with
rc_network_accessverification) - PID namespace isolation (with
rc_unshare_pidverification) - Cgroup limits enforcement (memory.max, pids.max, PID confirmed in cgroup.procs)
- Seccomp-bpf active status (filter mode, filter count, no_new_privs set)
- Capability narrowing (CapEff and CapBnd read from
/proc/[pid]/status) - AppArmor confinement (via aa-status PID mapping)
Verified services: dnsmasq, unbound, dnscrypt-proxy, tor, dhcpcd, iwd, sdwdate, chrony, acpid, syslog-ng
doas openrc-security-status
📋 Example output (click to expand)
=== lainOS OpenRC Isolation Stack — Containment Layer Verification ===
=== dnsmasq ===
process running as PID 4191 (comm=dnsmasq)
namespace: mount ISOLATED (own mount ns)
namespace: network shared with host (rc_network_access=YES, correct)
namespace: pid ISOLATED (own PID namespace)
cgroup limits ENFORCED (memory.max=67108864 pids.max=20, PID confirmed member)
seccomp-bpf ACTIVE (filter mode, 1 filter(s) loaded, no_new_privs set)
Landlock cannot verify directly (no /proc/status field exists for it);
inferred from successful sandboxed operation below
capabilities NARROWED (CapEff=0x0000000000002400 CapBnd=0x00000000000024c3)
AppArmor CONFINED (/usr/bin/dnsmasq)
=== unbound ===
process running as PID 4124 (comm=unbound)
namespace: mount ISOLATED (own mount ns)
namespace: network shared with host (rc_network_access=YES, correct)
namespace: pid ISOLATED (own PID namespace)
cgroup limits ENFORCED (memory.max=268435456 pids.max=20, PID confirmed member)
seccomp-bpf ACTIVE (filter mode, 1 filter(s) loaded, no_new_privs set)
capabilities NARROWED (CapEff=0x0000000001000402 CapBnd=0x0000000001000402)
AppArmor CONFINED (/usr/bin/unbound)
=== iwd ===
process running as PID 2636 (comm=iwd)
namespace: mount NOT ISOLATED (expected — iwd bypasses bwrap entirely)
namespace: network NOT ISOLATED (expected — iwd bypasses bwrap entirely)
namespace: pid NOT ISOLATED (expected — iwd bypasses bwrap entirely)
note: rfkill's write-mode device access is incompatible with any
bwrap-created mount namespace on this kernel, confirmed via
raw bwrap with zero other restrictions. Landlock, seccomp,
capabilities, cgroups, and AppArmor remain fully enforced
without it — see project notes.
cgroup limits ENFORCED (memory.max=max pids.max=max, PID confirmed member)
seccomp-bpf ACTIVE (filter mode, 1 filter(s) loaded, no_new_privs set)
capabilities NARROWED (CapEff=0x00000000000030c6 CapBnd=0x00000000000030c6)
AppArmor CONFINED (/usr/lib/iwd/iwd)
=== syslog-ng ===
process running as PID 1710 (comm=syslog-ng-main)
namespace: mount ISOLATED (own mount ns)
namespace: network ISOLATED (rc_network_access!=YES, correct)
namespace: pid ISOLATED (own PID namespace)
cgroup limits ENFORCED (memory.max=134217728 pids.max=10, PID confirmed member)
seccomp-bpf ACTIVE (filter mode, 1 filter(s) loaded, no_new_privs set)
capabilities NARROWED (CapEff=0x0000000000000000 CapBnd=0x0000000401203ddf)
AppArmor CONFINED (/usr/bin/syslog-ng)
=== Functional proof ===
chrony real NTP sync confirmed
unbound real DNS resolution confirmed
syslog-ng logger round-trip confirmed
=== Summary ===
All containment layers confirmed active on all services.
Why iwd Is Not Namespace-Isolated
iwd requires write access to /dev/rfkill to control WiFi radios. A bwrap-created mount namespace breaks this access on the current kernel. Instead of breaking WiFi functionality, iwd runs without namespace isolation while retaining all other containment layers:
- ✅ Seccomp filtering
- ✅ Capability dropping
- ✅ Cgroup limits
- ✅ AppArmor confinement
The script explicitly documents this exception so you know it's intentional, not a security gap.
protocol7-core-security-status
An adversarial test suite that actively attempts to break containment — exactly what a real attacker would try. Unlike the status scripts above, this one:
- Attempts to escape the mount namespace
- Tries reading blocked paths
- Attempts cross-process ptracing
- Tries writing to
/proc/sys - Verifies each attempt is actually denied
⚠️ Note: This script restarts both Protocol 7 daemons at the end of every run (one test sends SIGTERM to lainos-notifyd as part of testing shutdown behavior, and a gdb ptrace attempt can leave lainos-dbus-bridge in a traced/stopped state). Expect a brief interruption to Protocol 7 services while it runs.
36 Tests Across 12 Categories
| # | Category | What It Actually Proves |
|---|---|---|
| 1 | AppArmor enforcement | Both daemons are confined, not just loaded |
| 2 | Privilege drop | Both daemons run as nobody, not root |
| 3 | Capability drop | CapBnd reads zero on both — no regainable capabilities |
| 4 | Seccomp | Filter mode active (not just loaded, actually filtering) |
| 5 | Mount namespace | /tmp, /home, /root are tmpfs overlays, not the real filesystem |
| 5b | Filesystem isolation effectiveness | /home//root are genuinely empty inside the namespace even when the host has real data; /tmp is bidirectionally isolated |
| 6 | Path blocking (DAC baseline) | /etc/shadow, /home, /root are inaccessible — not AppArmor-specific |
| 6b | AppArmor negative control | /var/tmp write is denied even though it's world-writable (1777) — this is the actual proof of AppArmor enforcement |
| 6c | ptrace denial | Neither daemon can PTRACE_ATTACH the other |
| 7 | Network | No listening sockets; profile text contains no network inet/inet6 grant |
| 8 | Capability abuse | /proc/sys write blocked |
| 9 | Denial log | Zero new denials during this run |
| 10 | D-Bus | The bridge actually responds to a real D-Bus introspection call |
| 11 | lainos-init whitelist |
Unsafe relative and nonexistent-absolute P7_CMD values are rejected |
| 12 | notifyd regressions |
Survives embedded-newline payload; exits promptly on SIGTERM |
Why Tests 6 and 6b Are Both Present: Test 6 alone would keep passing even if the AppArmor profile were unloaded entirely, since plain filesystem permissions already block nobody from those specific paths. Test 6b uses a path that is world-writable at the permission layer — if it's still blocked, something beyond DAC has to be doing the blocking. That's the actual proof of enforcement.
doas protocol7-core-security-status
📋 Example output (click to expand)
==========================================
Protocol 7 AppArmor Adversarial Test Suite
==========================================
--- Layer 02 Packages ---
lainos-apparmor 1.2.1-29
protocol7-core 5.5.3-27
apparmor 4.1.7-1
Target PIDs:
lainos-dbus-bridge: 2639
lainos-notifyd: 1124
--- Test 1: AppArmor Enforcement ---
[PASS] lainos-dbus-bridge in enforce mode
[PASS] lainos-notifyd in enforce mode
--- Test 2: Privilege Drop ---
[PASS] dbus-bridge as nobody
[PASS] notifyd as nobody
--- Test 3: Capability Drop ---
[PASS] lainos-dbus-bri CapBnd zero
[PASS] lainos-notifyd CapBnd zero
--- Test 4: Seccomp ---
[PASS] lainos-dbus-bri seccomp active
[PASS] lainos-notifyd seccomp active
--- Test 5: Mount Namespace ---
[PASS] lainos-dbus-bri /tmp tmpfs
[PASS] lainos-dbus-bri /home tmpfs
[PASS] lainos-dbus-bri /root tmpfs
[PASS] lainos-notifyd /tmp tmpfs
[PASS] lainos-notifyd /home tmpfs
[PASS] lainos-notifyd /root tmpfs
--- Test 5b: Filesystem Isolation Effectiveness ---
[PASS] /home is empty in daemon namespace (host has 1 entries)
[PASS] /root is empty in daemon namespace
[PASS] Daemon /tmp isolated from host
[PASS] Host /tmp isolated from daemon
--- Test 6: Blocked Path Access (DAC baseline — not AppArmor-specific) ---
[PASS] /etc/shadow blocked
[PASS] /home write blocked
[PASS] /root write blocked
--- Test 6b: AppArmor Negative Control ---
[PASS] lainos-dbus-bri: /var/tmp write blocked by AppArmor (DAC alone would have allowed this)
[PASS] lainos-notifyd: /var/tmp write blocked by AppArmor (DAC alone would have allowed this)
--- Test 6c: ptrace Denial ---
[PASS] dbus-bridge cannot ptrace notifyd
--- Test 7: Network ---
[PASS] dbus-bridge no network sockets
[PASS] notifyd no network sockets
[PASS] lainos-dbus-bridge profile text contains no inet/inet6 network rule
[PASS] lainos-notifyd profile text contains no inet/inet6 network rule
--- Test 8: Capability Abuse ---
[PASS] /proc/sys write blocked
--- Test 9: Denial Log ---
[PASS] No current denials
--- Test 10: D-Bus ---
[PASS] D-Bus responding
--- Test 11: lainos-init P7_CMD Whitelist ---
[PASS] unsafe relative P7_CMD rejected
[PASS] nonexistent absolute P7_CMD rejected
[PASS] root session refused without P7_ALLOW_ROOT
--- Test 12: notifyd Behavioral Regressions ---
[PASS] notifyd survived newline payload, socket functional
[PASS] notifyd exited promptly on SIGTERM
==========================================
Test Summary: 36 passed, 0 failed
==========================================
Restarting Protocol 7 daemons...
* Stopping lainos-dbus-bridge ... [ ok ]
* Starting lainos-dbus-bridge ... [ ok ]
* WARNING: you are stopping a sysinit service
* Stopping lainos-notifyd ...
* start-stop-daemon: no matching processes found [ ok ]
* Starting lainos-notifyd ... [ ok ]
All tests PASSED
Exit code: 0 if all tests pass, 1 if any fail.
Requires: gdb (for the ptrace test in 6c; skipped gracefully with a warning if not installed), nsenter, socat, dbus-send, pgrep. Both Protocol 7 daemons (lainos-dbus-bridge, lainos-notifyd) must be running before the suite starts, or it exits immediately with an error.
When to Use Which Tool
| Scenario | Tool | Why |
|---|---|---|
| Routine check — you want to see if anything looks off | lainos-security-status |
Fast, read-only, safe anywhere |
| You changed a service's isolation config | openrc-security-status |
Verifies all containment layers are actually active |
| You updated an AppArmor profile | protocol7-core-security-status |
Tries to break out — confirms the profile actually restricts |
| You changed Protocol 7 daemon code | protocol7-core-security-status |
Tests the daemons' privilege model and confinement |
| Before a release | protocol7-core-security-status |
Full adversarial test suite proves security holds |
| You want actual proof, not configuration review | protocol7-core-security-status |
Tests real enforcement rather than just reading state |
Verification Matrix
| Service | Mount NS | Network NS | PID NS | Cgroups | Seccomp | Capabilities | AppArmor |
|---|---|---|---|---|---|---|---|
dnsmasq |
✅ | ✅ (host) | ✅ | ✅ | ✅ | ✅ | ✅ |
unbound |
✅ | ✅ (host) | ✅ | ✅ | ✅ | ✅ | ✅ |
dnscrypt-proxy |
✅ | ✅ (host) | ✅ | ✅ | ✅ | ✅ | ✅ |
tor |
✅ | ✅ (host) | ✅ | ✅ | ✅ | ✅ | ✅ |
dhcpcd |
✅ | ✅ (host) | ❌ (host, intentional) | ✅ | ✅ | ✅ | ✅ |
iwd |
❌ (intentional) | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
chrony |
✅ | ✅ (host) | ❌ (host, intentional) | ✅ | ✅ | ✅ | ✅ |
acpid |
✅ | ✅ (host) | ✅ | ✅ | ✅ | ✅ (zeroed) | ✅ |
syslog-ng |
✅ | ✅ (isolated) | ✅ | ✅ | ✅ | ✅ | ✅ |
Note:
iwdbypasses bwrap entirely due to rfkill device access requirements. All other containment layers (seccomp, capabilities, cgroups, AppArmor) remain fully enforced. See theiwdexception note above.
Why Runtime Verification Matters
Most security tools check configuration files — they tell you what the system should be doing. These scripts tell you what it is doing:
| Configuration Review | Runtime Verification |
|---|---|
| "The config says seccomp is enabled" | "Seccomp ACTIVE (filter mode, 1 filter loaded, no_new_privs set)" |
| "The service should be in a mount namespace" | "namespace: mount ISOLATED (own mount ns)" |
| "AppArmor profile is loaded" | "AppArmor CONFINED (/usr/bin/dnsmasq)" |
| "The service should have dropped capabilities" | "CapEff=0x0000000000000000 CapBnd=0x0000000000000000" |
| "Cgroup limits are configured" | "ENFORCED (memory.max=67108864 pids.max=20, PID confirmed member)" |
A misconfigured mount namespace won't pass the inode comparison test. A service running outside its cgroup won't pass the cgroup.procs check. A seccomp filter that failed to load won't show as active in /proc/[pid]/status.
If the system is secure, the tests pass. If they fail, you know exactly which layer is broken.
Requirements
- LainOS Layer 02 (or any OpenRC-based system running
protocol7-coreandlainos-apparmor) doasconfigured for the invoking user- AppArmor active in the kernel (
/sys/kernel/security/apparmorpresent) protocol7-core-security-statusadditionally requires:gdb,nsenter,socat,dbus-send,pgrep
License
GPLv3, matching the rest of the lainOS project.