Files
nix-config-ryan4yin/hardening/tests/runtime-validation.md
T
Ryan Yin 87b7a23cf3 docs(nixpak): consolidate runtime validation notes
Merge the three runtime-validation files into one concise record, update the interactive browser and Zoom results, and document the intentional Chromium host-loopback and Brave/Edge no-media exceptions. Tighten the remaining cookie-crypto workaround and the smoke-test prerequisites.

Assisted-by: DeepSeek Harness (deepseek-flash)
2026-11-01 09:24:23 +08:00

4.0 KiB

Runtime validation

Nixpak wrappers are accepted in three layers:

  1. Policy/build: just test evaluates every wrapper and policy.nix rejects broad mounts, system D-Bus, keyring access and device grants outside each app's declared scope.
  2. Disposable smoke test: verify-chromium.py launches a Chromium-family wrapper with a temporary HOME and validates renderer sandboxing, environment clearing, bind scope, profile persistence and loopback policy.
  3. Interactive desktop check: launch the real app, use its normal file/network/device workflows, and confirm the sandbox does not break required integration.

Current status

App Policy/build Runtime
Firefox Pass Interactive pass
Chromium Pass Automated smoke + interactive pass
Chrome Pass Interactive pass
Brave Origin Pass Interactive pass; backup browser without media grants
Edge Pass Interactive pass; backup browser without media grants
Zoom Pass Interactive pass
QQ / Telegram Pass Interactive startup/tray pass
WeChat (native nixpak) Pass Interactive Wayland validation pending

Intentional exceptions:

  • Chromium keeps host loopback and personal files for local development. Firefox has the same exception. Chrome, Brave Origin and Edge use pasta-isolated loopback.
  • Brave Origin and Edge are backup browsers. They intentionally have no PulseAudio, Camera, ScreenCast or MPRIS grants.

Browser smoke test

Build a wrapper from the locked inputs:

APP=chromium nix build --impure --out-link "browser-validation/$APP" --expr '
  let
    flake = builtins.getFlake (toString ./.);
    pkgs = import flake.inputs.nixpkgs {
      system = builtins.currentSystem;
      config.allowUnfree = true;
    };
  in
  pkgs.callPackage (./hardening/nixpaks + "/${builtins.getEnv "APP"}.nix") {
    mkNixPak = flake.inputs.nixpak.lib.nixpak { inherit pkgs; inherit (pkgs) lib; };
  }
'
python3 hardening/tests/verify-chromium.py "browser-validation/$APP/bin/$APP"

The script writes its disposable HOME under /var/tmp; the sandbox's private /tmp would otherwise hide profile binds. Pasta-wrapped browsers also require a usable /dev/net/tun on the host. The script checks live renderer Seccomp=2 and NoNewPrivs=1, absence of --no-sandbox, canary-free environments, effective bwrap arguments, profile persistence and loopback reachability.

Chromium-family browsers use the app-scoped Secret portal for v12 cookie encryption; they do not receive org.freedesktop.secrets. If the portal is unavailable, Chromium can fall back to weaker v10 storage; monitor Local State.os_crypt.portal.prev_init_success. Firefox uses NSS storage instead. Browserpass separately exposes the password store and GPG-agent socket to Firefox/Chromium/Chrome only.

Zoom

Zoom keeps its upstream FHS runtime inside the outer nixpak boundary. The nested sandbox propagates /.flatpak-info, uses pasta-isolated networking, and binds only DISPLAY, XAUTHORITY, /dev/video0, /dev/video1, DRM sysfs and the declared app directories. X11 remains a shared-display trust boundary: ScreenCast portal permission does not prevent capture through the X11 connection. Interactive login, audio, camera, screen sharing and file workflows passed.

Commands

just fmt
just test
git diff --check
ruff format --check hardening/tests/verify-chromium.py
ruff check hardening/tests/verify-chromium.py
prettier --check hardening/tests/runtime-validation.md