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)
This commit is contained in:
Ryan Yin committed 2026-11-01 09:24:23 +08:00
1 parent ccffddc864
commit 87b7a23cf3
8 files changed
+115 -242

No files matched your search

+10 -6
View File
@@ -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
+6 -6
View File
@@ -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
+16 -14
View File
@@ -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
-126
View File
@@ -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: <https://webrtc.github.io/samples/src/content/getusermedia/gum/>
- Click **Start**, accept Chrome and portal prompts, verify local video preview and audio level.
- Screen sharing: <https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/>
- 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.
+81
View File
@@ -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
```
-35
View File
@@ -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`.
+2 -1
View File
@@ -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
-54
View File
@@ -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.