ops-warden/docs/development/safe-unit-tests.md
tegwick b593c87983
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
test: isolate ordinary unit runs from ambient credentials and provider I/O
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 21:30:12 +02:00

4.2 KiB

Safe unit-test entry point

Use make test, or .venv/bin/python -I scripts/run_unit_tests.py. Dependencies must already be installed; this path does not run uv, synchronize or download. UNIT_PYTHON may select another existing interpreter. PHASE_MEMORY_REPO remains an explicit local source option. Pass pytest options with TEST_ARGS, for example make test TEST_ARGS='-q tests/test_safe_unit_runner.py'.

The launcher creates an empty temporary HOME and temporary fixture root and starts an isolated (-I) Python interpreter with a constructed environment. Caller token, proxy, Python, pytest, XDG and Warden configuration variables are never copied or inventoried. Third-party pytest plugin autoload is disabled. Tests can explicitly introduce synthetic variables with monkeypatch, but allowed children always get the original temporary HOME and a fresh environment, never those variables.

Before pytest import/collection, a Python audit hook and subprocess wrapper:

  • deny file opens outside the selected source roots, installed interpreter/library roots and fixture root; writes through open are limited to the fixture root;
  • deny sockets, DNS and process creation except the closed fixture inventory;
  • refuse shell, executable, preexec and inherited-FD overrides outside that inventory;
  • install the same file/socket/process denial before executing every allowed Python child, including BrowserInputs' exact validator code and reverified copied module hashes. Validator children cannot start further processes.

The process inventory in scripts/run_unit_tests.py is intentionally small: BrowserInputs validation, four metadata-checker scripts with fixed argument shapes, the exact original-intent restart probe, the synthetic containment print, and closed guard-regression probes. Metadata checkers receive only fixture output paths. Only the generated-artifact checker child can run the exact read-only Git log/status queries for this repository's catalog, with global/system config and optional locks disabled, hooks disabled and fsmonitor disabled. Those local Git commands are not provider operations.

Two reviewed non-Python cases remain: exact-byte, temporary contract-double scripts with a closed argv list, and five exact fixed printf commands used by existing transport tests. They run Bash without profile/rc startup and without inherited environment; no arbitrary shell text, provider binary, SSH, kubectl or OpenBao command is permitted. Default unit selection excludes four integration-marked SSH-keygen tests. Selecting them explicitly does not grant their native commands.

Only test_desk.py::test_approve_flow_http_dry receives a network lease. It may bind 127.0.0.1:0 and connect to the endpoint actually allocated to that test's socket. DNS is replaced by numeric fixture resolution. Another loopback service (e.g. the real Hub on port 8000), Unix sockets and remote endpoints stay denied. The lease and registered endpoints are cleared after the test.

Coverage tests now use the explicit public declaration projection snapshot in tests/fixtures/workload-coverage, with provenance hashes. They compare the published source snapshot rather than silently consulting the workstation HOME. Run the existing coverage report separately with an explicit estate root when measuring current estate state. Tests of the acknowledged NetKingdom source use --netkingdom-root (default: sibling source checkout), never a HOME symlink. The optional Phase Memory library's one existing public coordination-profile fixture is explicitly readable alongside its source; no runtime memory store is inherited.

This is an accident guard for trusted unit tests, not a security sandbox for hostile Python, native extensions or hostile installed dependencies. It does not revoke already-open descriptors, prevent every filesystem mutation syscall, or provide OS process/network isolation. Fixed Bash/Git fixture processes rely on the reviewed executable and source/argument shapes; Python audit hooks do not run inside those binaries. Do not run untrusted test code under a privileged runtime. Direct pytest bypasses this launcher and is not equivalent validation. No production Warden boundary, grant, catalog, source pin or owner credential changes.