From 87b7a23cf3545e791e3f7029099391d06dd7e78b Mon Sep 17 00:00:00 2001 From: Ryan Yin Date: Sun, 1 Nov 2026 09:24:23 +0800 Subject: [PATCH] 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) --- SECURITY.md | 16 ++-- WORKAROUNDS.md | 12 +-- hardening/README.md | 30 +++--- hardening/tests/browser-runtime.md | 126 ------------------------- hardening/tests/runtime-validation.md | 81 ++++++++++++++++ hardening/tests/unified-app-runtime.md | 35 ------- hardening/tests/verify-chromium.py | 3 +- hardening/tests/zoom-runtime.md | 54 ----------- 8 files changed, 115 insertions(+), 242 deletions(-) delete mode 100644 hardening/tests/browser-runtime.md create mode 100644 hardening/tests/runtime-validation.md delete mode 100644 hardening/tests/unified-app-runtime.md delete mode 100644 hardening/tests/zoom-runtime.md diff --git a/SECURITY.md b/SECURITY.md index 19fb4ebc..3f19803c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -326,9 +326,11 @@ isolation. Source review and socket metadata—not reads of private credentials - All nine desktop wrappers use shared nixpak policies: cleared environment, filtered session D-Bus and selected mounts. The [permission inventory](hardening/README.md#app-permissions) lists each app's file, network, credential and media grants. -- Only Firefox/Chromium share host networking. Other apps isolate loopback with pasta but can still - reach the LAN/Internet. Browser media and browserpass are enabled only for - Firefox/Chromium/Chrome; browserpass exposes password-store/GPG data and agent operations. +- Only Firefox/Chromium share host networking. Chromium is an intentional local-development + exception; all other apps isolate loopback with pasta but can still reach the LAN/Internet. + Browser media and browserpass are enabled only for Firefox/Chromium/Chrome. Brave Origin and Edge + are backup browsers with no media grants. browserpass exposes password-store/GPG data and agent + operations. - Nixpak supplements Chromium's renderer sandbox, which remains enabled. Zoom/WeChat retain their FHS runtimes inside the outer boundary; inner mounts cannot recover hidden host resources. - Desktop access has limits: GPU drivers remain exposed, PulseAudio permits recording, read-only @@ -339,9 +341,11 @@ isolation. Source review and socket metadata—not reads of private credentials browser receives broad GNOME Keyring authority. Firefox uses its own NSS storage. - **Rollout note (WA-045):** v12 is verified on a disposable profile, but if the portal is unavailable Chromium may fall back to weaker v10 storage; portal initialization is monitored. -- Runtime evidence is partial: Brave/Edge headless export times out; interactive login, media, IME - and file workflows require acceptance. See the - [validation records](hardening/README.md#validation). +- Policy/build passes for all nine wrappers. Interactive validation passed for Firefox, Chrome, + Chromium, Brave Origin, Edge, QQ, Telegram and Zoom; the Chromium smoke test also validates + renderer sandboxing, environment clearing, bind scope and profile persistence. WeChat's + interactive Wayland check remains pending. See the + [runtime validation](hardening/tests/runtime-validation.md). #### Agent-first diagnostics by host role diff --git a/WORKAROUNDS.md b/WORKAROUNDS.md index 46354240..37533691 100644 --- a/WORKAROUNDS.md +++ b/WORKAROUNDS.md @@ -83,12 +83,12 @@ A known limitation with no fix yet. An unfinished feature or gap. -| ID | What & where | Why | Removal condition | Added | Revisit | Status | -| ------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------ | ---------- | -| WA-013 | NixOS tests — `outputs/README.md` | full-host tests still rebuild custom packages (no private cache) and need the host ssh key to decrypt agenix; the shared-firewall security-exporters VM test now runs in CI (KVM); `outputs/x86_64-linux/nixos-tests/idols-ruby.nix` is exposed as `packages.x86_64-linux.ruby-nixos-tests` but is not in `checks` or CI | Resolve when a private cache exists and tests can run per-service | 2024-03-13 | quarterly | active | -| WA-027 | NFS golden store `no_root_squash` — `hosts/12kingdoms-youko/nfs-golden.nix` | the existing PVC subdirectories are root-owned, so enabling `root_squash` would break running workloads until they are migrated; the CSI node plugin is hostNetwork and sources from the allowlisted node IPs | Drop when the PVC subdirectories are migrated and `root_squash` is enabled | 2026-10-07 | next maintenance window | active | -| WA-044 | transmission AppArmor lock permission — `hosts/12kingdoms-youko/homelab-services/transmission.nix` | nixpkgs' transmission 4.1.4 profile grants `rw` on the daemon's config dir but not `k`, while 4.1.4 takes an fcntl lock on `~/.config/transmission-daemon/lock`; the daemon exits with "Another process is already using". A `local/bin.transmission-daemon` include adds `rwk` for that lock file only | Drop once the nixpkgs profile includes the lock permission | 2026-10-31 | on the next nixpkgs bump | active | -| WA-045 | Chromium cookie crypto — `hardening/nixpaks/modules/chromium-browser.nix` | Chromium-family browsers use the app-scoped Secret portal and write v12 cookies. If the portal is unavailable, Chromium may fall back to weak v10 storage. Brave/Edge headless export also times out. | Drop once the v10 fallback is eliminated and the Brave/Edge headless checks pass; monitor portal initialization and interactive browser checks | 2026-10-31 | on Chromium updates | active gap | +| ID | What & where | Why | Removal condition | Added | Revisit | Status | +| ------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------------ | ---------- | +| WA-013 | NixOS tests — `outputs/README.md` | full-host tests still rebuild custom packages (no private cache) and need the host ssh key to decrypt agenix; the shared-firewall security-exporters VM test now runs in CI (KVM); `outputs/x86_64-linux/nixos-tests/idols-ruby.nix` is exposed as `packages.x86_64-linux.ruby-nixos-tests` but is not in `checks` or CI | Resolve when a private cache exists and tests can run per-service | 2024-03-13 | quarterly | active | +| WA-027 | NFS golden store `no_root_squash` — `hosts/12kingdoms-youko/nfs-golden.nix` | the existing PVC subdirectories are root-owned, so enabling `root_squash` would break running workloads until they are migrated; the CSI node plugin is hostNetwork and sources from the allowlisted node IPs | Drop when the PVC subdirectories are migrated and `root_squash` is enabled | 2026-10-07 | next maintenance window | active | +| WA-044 | transmission AppArmor lock permission — `hosts/12kingdoms-youko/homelab-services/transmission.nix` | nixpkgs' transmission 4.1.4 profile grants `rw` on the daemon's config dir but not `k`, while 4.1.4 takes an fcntl lock on `~/.config/transmission-daemon/lock`; the daemon exits with "Another process is already using". A `local/bin.transmission-daemon` include adds `rwk` for that lock file only | Drop once the nixpkgs profile includes the lock permission | 2026-10-31 | on the next nixpkgs bump | active | +| WA-045 | Chromium cookie crypto — `hardening/nixpaks/modules/chromium-browser.nix` | Chromium-family browsers use the app-scoped Secret portal and write v12 cookies. If the portal is unavailable, Chromium may fall back to weak v10 storage. | Drop once the v10 fallback is eliminated; monitor portal initialization and the `Local State.os_crypt.portal.prev_init_success` flag | 2026-10-31 | on Chromium updates | active gap | ## Related diff --git a/hardening/README.md b/hardening/README.md index 8f17edac..9e9ee2ba 100644 --- a/hardening/README.md +++ b/hardening/README.md @@ -1,6 +1,6 @@ # Linux Hardening -Deployed and interactively validated on `ai` (2026-10-31). Policy and limits: +Deployed and interactively validated on the Linux desktop host (2026-10-31). Policy and limits: [SECURITY.md](../SECURITY.md). ## Shared boundaries @@ -10,7 +10,9 @@ Deployed and interactively validated on `ai` (2026-10-31). Policy and limits: - Read-only `/nix/store`; GPU and display; fonts and themes; LAN and Internet. - Portals for file chooser, URI opening, notifications, input methods and document exports. - No whole host home, system D-Bus, SSH agent, USB/serial, raw camera or raw PipeWire socket. -- Host loopback only for Firefox/Chromium; all other apps use pasta-isolated loopback. +- Host loopback only for Firefox/Chromium; all other apps use pasta-isolated loopback. Chromium is + an intentional development exception. Brave Origin and Edge are backup browsers and receive no + media grants. ## App permissions @@ -50,21 +52,21 @@ All browsers get read-only NSS certificates, printing, native notifications and capture. - PulseAudio grants recording authority. Read-only GPG sockets still permit signing and decryption. - pasta isolates loopback, not LAN access. Chromecast discovery is unverified. -- Brave/Edge renderer and permission checks pass, but headless page export still times out. +- Brave Origin and Edge intentionally have no PulseAudio, Camera, ScreenCast or MPRIS grants. They + are backup browsers, not media browsers. ## Validation -- Passed: `just fmt`, `just test`, `ai-niri` build and `shoukei-niri` eval. -- Deployed on `ai`; interactive startup and tray checks passed for Chrome, Firefox, QQ, Telegram and - Zoom. -- WeChat uses the native deb package from nixpkgs-patched, with nixpak as the only bwrap layer. The - build passes; interactive Wayland validation is pending. -- Chromium/Chrome: renderer sandbox, mounts, rendering and disposable-profile persistence passed. - Chromium reaches host loopback; Chrome cannot. -- Firefox: disposable headless screenshot passed. -- Brave/Edge: renderer and permission checks passed; headless page export timed out. -- Evidence: [browsers](tests/browser-runtime.md), - [Firefox/QQ/Telegram](tests/unified-app-runtime.md), [Zoom](tests/zoom-runtime.md). +- Passed: `just fmt`, `just test`, `ai-niri` build, `shoukei-niri` eval and all wrapper builds. +- Interactive validation passed for Firefox, Chrome, Chromium, Brave Origin, Edge, QQ, Telegram and + Zoom. Chromium's host-loopback/personal-file exception and the Brave/Edge no-media policy are + intentional. +- Chromium smoke test passed: renderer sandbox, environment clearing, bind scope, disposable-profile + persistence and loopback policy. +- WeChat uses the native nixpkgs-patched package with nixpak as the only bwrap layer. Build/policy + pass; interactive Wayland validation is still pending. +- Evidence and repeat commands: [runtime validation](tests/runtime-validation.md), + [policy test](tests/policy.nix). ## Code map diff --git a/hardening/tests/browser-runtime.md b/hardening/tests/browser-runtime.md deleted file mode 100644 index f6f5ea39..00000000 --- a/hardening/tests/browser-runtime.md +++ /dev/null @@ -1,126 +0,0 @@ -# Browser runtime verification - -- Run from the repository root as the Linux desktop user, with user D-Bus; no sudo or activation. -- Tests use disposable profiles only, synthetic environment canaries, and no environment dumps. -- **Keep HOME under `/var/tmp`:** the sandbox's private `/tmp` masks a HOME created there. Rendering - reads a host-prepared HTML file through the profile bind; persistence requires a nonempty - `Local State` file in the disposable host HOME. - -## Reproduce - -Build against the locked flake inputs: - -```bash -mkdir -p browser-validation -for app in chromium google-chrome brave-origin microsoft-edge; do - APP="$app" 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; - }; - } - ' -done -``` - -Run each test separately so a failing browser does not hide the other results: - -```bash -python3 hardening/tests/verify-chromium.py browser-validation/chromium/bin/chromium -python3 hardening/tests/verify-chromium.py browser-validation/google-chrome/bin/google-chrome -python3 hardening/tests/verify-chromium.py browser-validation/brave-origin/bin/brave-origin -python3 hardening/tests/verify-chromium.py browser-validation/microsoft-edge/bin/microsoft-edge -``` - -- Each launch has a 35-second limit; failures exit nonzero. Temporary HOME is cleaned up. -- Intermediate JSON records process checks; final JSON appears only after all checks pass. - -## Current measured results - -Host: `ai`, x86_64 Linux. All four rebuilt after the shared policy/media opt-in changes and tested -with `/var/tmp` HOME, explicit Fontconfig configuration, CJK fonts, and narrow NVIDIA mappings. - -| Browser | Process/security checks | HTML rendering | Profile persistence | Host loopback | Overall | -| ---------------------- | ----------------------- | --------------- | ------------------- | ------------- | -------- | -| Chromium 156.0.8078.73 | Pass | Pass | Pass | Pass: allowed | **Pass** | -| Chrome 156.0.8078.73 | Pass | Pass | Pass | Pass: blocked | **Pass** | -| Brave Origin 1.98.60 | Pass | **35s timeout** | Not reached | Not reached | **Fail** | -| Edge 156.0.4314.42 | Pass | **35s timeout** | Not reached | Not reached | **Fail** | - -Measured evidence: - -- All four build and have live renderers with `Seccomp=2`, `NoNewPrivs=1`; no sandbox-disabling - flags are present. -- Actual bwrap arguments pass user/PID namespace, effective network flag, clear environment, new - session, parent-death, and private `/dev/shm` checks. No broad HOME/`/home`/`/dev`, SSH, - system-bus, raw video, X11, GVfs, or direct PipeWire binds. Brave/Edge also have no browserpass - credential or PulseAudio binds. -- All nine canaries are absent: `NIXPAK_VALIDATION_CANARY`, `SSH_AUTH_SOCK`, `GPG_AGENT_INFO`, and - upper/lowercase `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`. -- Chromium/Chrome render the prepared file without Fontconfig errors and persist `Local State`. Host - HTTP control succeeds; Chromium fetches its marker. Chrome reports `ERR_CONNECTION_REFUSED`, - returns no marker, and sends zero requests to the host listener. -- Brave/Edge stall on `--headless=new --dump-dom` despite `--timeout=10000`. Cause unresolved; - system D-Bus/UPower warnings also occur in passing browsers. No upstream-package control was run, - so this does not establish a wrapper regression. - -## Current permission checks - -- **Firefox/Chromium/Chrome:** evaluated media opt-in enables PulseAudio, camera/screencast portals, - MPRIS, and inhibition. **Brave/Edge:** all disabled; live socket-bind checks pass. -- Chromium/Chrome main processes enable `WaylandWindowDecorations,WebRtcPipeWireCamera`. Brave/Edge - retain Wayland decorations and have **no camera feature**; all flag checks pass. -- Firefox/Chromium share host networking and allow personal files; Chrome/Brave/Edge use pasta - isolation without personal-file access. Firefox parity is evaluated, not runtime-tested here. -- GPU nodes remain available for webpage rendering. Interactive audio/camera/capture, hardware - acceleration, CJK/IME, portals, downloads, and browserpass remain unverified by this headless - test. -- Inventory: [hardening README](../README.md). Security limits: [SECURITY.md](../../SECURITY.md). - -## Cookie crypto - -- Chromium-family wrappers enable `SecretPortalKeyProviderUseForEncryption` and receive only - `org.freedesktop.portal.Secret.RetrieveSecret`; they do **not** get - `--talk=org.freedesktop.secrets`. -- A disposable-profile probe under `/var/tmp` served an HTTP `Set-Cookie`, then inspected the - profile DB: the cookie prefix was `v12` (`763132`) and - `Local State.os_crypt.portal.prev_init_success` was true. -- If the portal is unavailable, Chromium can fall back to v10. That is weak; monitor portal startup - Portal startup is a monitored degradation, not a fail-closed guarantee. -- Firefox uses NSS (`key4.db`/`logins.json`) and `cookies.sqlite`, not Chrome OSCrypt. Set a Firefox - Primary Password if saved-login at-rest protection matters. -- Sources: - [feature defaults](https://github.com/chromium/chromium/blob/156.0.8078.73/chrome/browser/browser_features.cc), - [v12 provider](https://github.com/chromium/chromium/blob/156.0.8078.73/components/os_crypt/async/browser/secret_portal_key_provider.cc), - [prefix decryption](https://github.com/chromium/chromium/blob/156.0.8078.73/components/os_crypt/async/common/encryptor.cc). - -## Check these files - -```bash -ruff format --check hardening/tests/verify-chromium.py -ruff check hardening/tests/verify-chromium.py -prettier --check hardening/tests/browser-runtime.md -git diff --check -``` - -## Manual Chrome media smoke test - -Use the official WebRTC samples. They require no account and do not upload the stream; the preview -stays local. Chrome uses pasta-isolated loopback, so a test server on the host is intentionally not -reachable. - -- Camera + microphone: - - Click **Start**, accept Chrome and portal prompts, verify local video preview and audio level. -- Screen sharing: - - Click **Start**, choose a window/screen, verify preview, then **Stop**. -- Files: use any site's upload control, then download the same file; confirm the file chooser and - Downloads work. diff --git a/hardening/tests/runtime-validation.md b/hardening/tests/runtime-validation.md new file mode 100644 index 00000000..b332fc3c --- /dev/null +++ b/hardening/tests/runtime-validation.md @@ -0,0 +1,81 @@ +# Runtime validation + +Nixpak wrappers are accepted in three layers: + +1. **Policy/build:** `just test` evaluates every wrapper and [`policy.nix`](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`](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: + +```bash +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 + +```bash +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 +``` diff --git a/hardening/tests/unified-app-runtime.md b/hardening/tests/unified-app-runtime.md deleted file mode 100644 index f5ca4072..00000000 --- a/hardening/tests/unified-app-runtime.md +++ /dev/null @@ -1,35 +0,0 @@ -# Unified desktop-policy validation - -Validation on the Linux `ai` host, using the pinned nixpkgs and nixpak inputs. These checks use -disposable profiles and do not read existing browser or chat accounts. - -| Check | Result | Scope | -| --------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Firefox, QQ, Telegram derivations | Pass | All three wrappers evaluated and built after conversion to the shared desktop policy. | -| Browser policy regression checks | Pass | `policy.nix` returned `true`; covers Firefox/Chromium capability and portal-call parity, host loopback, Chrome isolated networking and media grants, browserpass scope, and forbidden service/device access. | -| Firefox headless screenshot | Pass | Exit status 0; `about:blank` produced a PNG with a fresh profile and HOME under `/var/tmp`. | -| QQ startup | Partial | With a disposable HOME and `--ozone-platform=headless`, initialized private QQ data and stayed running for 12 seconds until explicitly terminated. Headless EGL/GPU initialization errors occurred. No account login or media function was tested. | -| Telegram runtime | Not run | Evaluation and build only. | - -The Firefox invocation used the built wrapper with -`--headless --no-remote --profile "$HOME/.mozilla/test" --screenshot "$HOME/Downloads/smoke.png" about:blank`. -The disposable environment retained only the launcher's PATH, USER, locale, Wayland display and -session-bus/runtime-directory variables, with HOME and all XDG storage paths redirected to the -temporary tree. Existing account profiles were not mounted. The temporary tree was removed after the -process exited. - -Use `/var/tmp`, not `/tmp`, for disposable host profiles. Nixpak emits the sandbox's private `/tmp` -mount after filesystem binds; a profile prepared under host `/tmp` is hidden by that mount. An -application that creates a fresh profile inside the resulting tmpfs can appear to work without -testing persistence. - -The common policy replaces the earlier direct camera-device and PipeWire-socket grants with explicit -Camera and ScreenCast portal methods. QQ and Telegram retain their private XDG data and existing -personal-file mappings, and opt into notifications, tray, MPRIS and accessibility. Firefox retains -its `.mozilla` profile and MPRIS artwork mounts and shares browser capability definitions with -Chromium. - -Interactive validation remains necessary for camera capture, microphone input, video playback, -desktop/window sharing, file chooser/save operations, IME, tray, notifications and media controls. -The QQ headless startup is not evidence that those functions work. Browser encrypted-login behavior -is covered in `browser-runtime.md`. diff --git a/hardening/tests/verify-chromium.py b/hardening/tests/verify-chromium.py index 56f3cb88..4bbf8b67 100755 --- a/hardening/tests/verify-chromium.py +++ b/hardening/tests/verify-chromium.py @@ -2,7 +2,8 @@ """Runtime smoke test: verify a built nixpak Chromium without existing profiles. Usage: python3 hardening/tests/verify-chromium.py /nix/store/.../bin/chromium -Requires Linux procfs and an active user D-Bus session. Uses only stdlib. +Requires Linux procfs and an active user D-Bus session. Pasta-wrapped browsers also +need /dev/net/tun. Uses a disposable HOME under /var/tmp and only the standard library. """ import argparse diff --git a/hardening/tests/zoom-runtime.md b/hardening/tests/zoom-runtime.md deleted file mode 100644 index 9a430454..00000000 --- a/hardening/tests/zoom-runtime.md +++ /dev/null @@ -1,54 +0,0 @@ -# Zoom nested runtime verification - -The Zoom 7.2.1.5760 candidate built and opened two `Zoom Workplace` X11 windows on the NVIDIA -desktop. The final tested output was -`/nix/store/yy6sxsgn5rqafvvgs3k53akwqjcfa2r7-nixpak-zoom-7.2.1.5760`. - -The live main process had `NoNewPrivs: 1`, a network namespace different from the host, and -`/.flatpak-info` with `name=us.zoom.Zoom`. Its ancestry included ZoomLauncher and the nested -bubblewrap processes. Its `/dev` contained only standard synthetic devices, `dri`, `nvidia0`, -`nvidiactl`, `nvidia-modeset`, and `nvidia-uvm`; no camera, USB or block devices were exposed. -Zoom's main process reported `Seccomp: 0`; this test does not establish a renderer seccomp -guarantee. - -The upstream FHS runtime remains intact. Its automatic root mounts propagate directories, so the -wrapper explicitly propagates the outer `/.flatpak-info` file for portal identification. Its `/dev` -bind inherits the outer sandbox's filtered devices. The outer network uses pasta isolate mode, -without host loopback sharing. X11 remains a display trust boundary: explicit ScreenCast portal -permission does not prevent access through the X11 connection. - -Initial display testing found that `/tmp` tmpfs shadowed the X11 socket and that NVIDIA OpenGL -needed the four device mappings now in `desktop-base.nix`. Zoom therefore keeps the sandbox's -private root `/tmp` instead of mounting another tmpfs over the socket, and explicitly passes -`DISPLAY` and `XAUTHORITY`. - -## Repeat without touching the normal Zoom profile - -Build the candidate without activating a host, then substitute its output path below. The temporary -home must be outside `/tmp` when testing policies that mount a `/tmp` tmpfs. Keep the current -display and session bus environment. - -```bash -zoom_probe_dir=$(mktemp -d "$HOME/.cache/zoom-nixpak-probe.XXXXXX") -mkdir -p "$zoom_probe_dir"/{.config,.cache,.local/share,.local/state,Documents,Downloads} -HOME="$zoom_probe_dir" \ -XDG_CONFIG_HOME="$zoom_probe_dir/.config" \ -XDG_CACHE_HOME="$zoom_probe_dir/.cache" \ -XDG_DATA_HOME="$zoom_probe_dir/.local/share" \ -XDG_STATE_HOME="$zoom_probe_dir/.local/state" \ -timeout --kill-after=3 30 /nix/store/OUTPUT/bin/zoom -``` - -While it runs, inspect the exact test process through `/proc/PID/root/dev`, -`/proc/PID/root/.flatpak-info`, `/proc/PID/ns/net`, `/proc/PID/status`, and its parent processes. Do -not sign in or join a meeting during this smoke test. The timeout terminates the owned launcher and -its sandbox. Confirm the test windows and processes are gone afterward. - -The disposable run created `zoomus.conf` under the private application config directory. Existing -host `zoomus.conf` and `zoom.conf` are individually mounted when present. Application caches use the -private application cache directory. - -Still pending: login and meeting join, microphone and speaker routing, camera portal behavior on -supported hardware, screencast selection and PipeWire FD handoff, recording/export paths, file -chooser access, URL opening and IME. No successful camera, audio or screencast session is claimed by -this smoke test.