import configparser import importlib.util import json from pathlib import Path import re import stat import pytest import yaml ROOT = Path(__file__).resolve().parents[1] UPDATER_PATH = ROOT / "deploy" / "fail2ban" / "truf_caddy_admin_denylist.py" SPEC = importlib.util.spec_from_file_location("truf_caddy_admin_denylist", UPDATER_PATH) updater_module = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(updater_module) def updater(tmp_path, runner=None, now=1_700_000_000): state_dir = tmp_path / "state" snippet_dir = tmp_path / "snippet" state_dir.mkdir(mode=0o700) snippet_dir.mkdir(mode=0o750) snippet = snippet_dir / "admin-denylist.caddy" snippet.write_text(updater_module.EMPTY_SNIPPET, encoding="ascii") instance = updater_module.DenylistUpdater( state_dir / "admin-denylist.json", snippet, project_directory=tmp_path / "project", env_file=tmp_path / "edge.env", runner=runner or (lambda command: True), clock=lambda: now, ) return instance, state_dir / "admin-denylist.json", snippet def test_updater_persists_canonical_state_and_expires_automatically(tmp_path): commands = [] instance, state_path, snippet_path = updater( tmp_path, runner=lambda command: commands.append(command) or True, ) result = instance.update("ban", "2001:db8:0:0::2") assert result["bans"] == {"2001:db8::2": 1_700_086_400} state = json.loads(state_path.read_text(encoding="ascii")) assert state["bans"] == result["bans"] assert re.fullmatch(r"[0-9a-f]{64}", state["applied_sha256"]) assert "2001:db8::2" in snippet_path.read_text(encoding="ascii") assert any("validate" in command for command in commands) assert any("reload" in command and "unix//run/caddy-admin.sock" in command for command in commands) commands.clear() persisted = updater_module.DenylistUpdater( state_path, snippet_path, runner=lambda command: commands.append(command) or True, clock=lambda: 1_700_086_401, ) status = persisted.update("status") assert status["expired"] == 1 assert status["bans"] == {} assert snippet_path.read_text(encoding="ascii") == updater_module.EMPTY_SNIPPET assert json.loads(state_path.read_text(encoding="ascii"))["bans"] == {} assert len(commands) == 2 def test_updater_reuses_persisted_applied_digest_without_reloading(tmp_path): instance, state_path, snippet_path = updater(tmp_path) instance.update("ban", "192.0.2.4") commands = [] persisted = updater_module.DenylistUpdater( state_path, snippet_path, runner=lambda command: commands.append(command) or True, clock=lambda: 1_700_000_001, ) assert persisted.update("status")["bans"] == {"192.0.2.4": 1_700_086_400} assert commands == [] def test_updater_rejects_corrupt_state_before_commands_or_snippet_changes(tmp_path): commands = [] instance, state_path, snippet_path = updater( tmp_path, runner=lambda command: commands.append(command) or True, ) state_path.write_text('{"version":1,"bans":[]}', encoding="ascii") before = snippet_path.read_bytes() with pytest.raises(updater_module.UpdateError): instance.update("status") assert commands == [] assert snippet_path.read_bytes() == before def test_updater_rejects_symlinked_snippet_when_supported(tmp_path): commands = [] instance, state_path, snippet_path = updater( tmp_path, runner=lambda command: commands.append(command) or True, ) target = snippet_path.with_name("target.caddy") target.write_text(updater_module.EMPTY_SNIPPET, encoding="ascii") snippet_path.unlink() try: snippet_path.symlink_to(target) except OSError: pytest.skip("symlink creation is unavailable") with pytest.raises(updater_module.UpdateError): instance.update("ban", "192.0.2.5") assert commands == [] assert not state_path.exists() @pytest.mark.parametrize("value", ["", "1.2.3.4/32", "1.2.3.4;touch /tmp/x", "::%eth0", "0.0.0.0", "ff02::1"]) def test_updater_rejects_non_ip_or_unsafe_addresses_without_commands(tmp_path, value): commands = [] instance, state_path, snippet_path = updater( tmp_path, runner=lambda command: commands.append(command) or True, ) before = snippet_path.read_bytes() with pytest.raises(updater_module.UpdateError): instance.update("ban", value) assert commands == [] assert not state_path.exists() assert snippet_path.read_bytes() == before def test_updater_renders_deterministically_and_bounds_matcher_lines(): bans = {"2001:db8::2": 4, "192.0.2.10": 3, "2001:db8::1": 2, "192.0.2.2": 1} rendered = updater_module.render_snippet(bans).decode("ascii") matcher = next(line for line in rendered.splitlines() if line.startswith("@truf_admin_denied_")) assert matcher.endswith("192.0.2.2 192.0.2.10 2001:db8::1 2001:db8::2") assert max(map(len, rendered.splitlines())) < 4096 assert "global" not in rendered and "iptables" not in rendered def test_updater_rolls_back_state_and_snippet_when_reload_fails(tmp_path): instance, state_path, snippet_path = updater(tmp_path) instance.update("status") old_state = state_path.read_bytes() old_snippet = snippet_path.read_bytes() reloads = 0 def fail_first_reload(command): nonlocal reloads if "reload" in command: reloads += 1 return reloads > 1 return True instance.runner = fail_first_reload with pytest.raises(updater_module.CommandFailure): instance.update("ban", "198.51.100.9") assert reloads == 2 assert state_path.read_bytes() == old_state assert snippet_path.read_bytes() == old_snippet def test_updater_rolls_back_without_reload_when_validation_fails(tmp_path): instance, state_path, snippet_path = updater(tmp_path) instance.update("status") old_state = state_path.read_bytes() old_snippet = snippet_path.read_bytes() commands = [] def fail_validation(command): commands.append(command) return "validate" not in command instance.runner = fail_validation with pytest.raises(updater_module.CommandFailure): instance.update("ban", "198.51.100.11") assert len(commands) == 1 assert state_path.read_bytes() == old_state assert snippet_path.read_bytes() == old_snippet def test_fail2ban_filter_counts_only_redacted_supplied_bad_credentials(): parser = configparser.ConfigParser(interpolation=None) parser.read(ROOT / "deploy" / "fail2ban" / "filter.d-truf-admin-auth.conf", encoding="ascii") failregex = parser["Definition"]["failregex"] host = r"(?P[0-9A-Fa-f:.]+)" pattern = re.compile(failregex.replace("", host)) bad = '{"level":"info","ts":1700000000.1,"logger":"http.log.access.admin_auth_failures","msg":"handled request","status":401,"event":"admin_auth_failure","remote_ip":"203.0.113.8"}' assert pattern.match(bad).group("host") == "203.0.113.8" assert not pattern.match(bad.replace('"status":401', '"status":200')) assert not pattern.match(bad.replace('"event":"admin_auth_failure",', "")) assert not pattern.match(bad.replace('"remote_ip":"203.0.113.8"', '"remote_ip":"203.0.113.8","uri":"/secret"')) challenge = '{"level":"info","ts":1700000000.1,"status":401,"remote_ip":"203.0.113.8"}' assert not pattern.match(challenge) def test_fail2ban_jail_is_persistent_bounded_and_admin_only(): jail = (ROOT / "deploy" / "fail2ban" / "jail.d-truf-admin-auth.local").read_text(encoding="ascii") persistence = (ROOT / "deploy" / "fail2ban" / "fail2ban.d-truf-persistence.local").read_text(encoding="ascii") action = (ROOT / "deploy" / "fail2ban" / "action.d-truf-caddy-admin-denylist.conf").read_text(encoding="ascii") assert "maxretry = 2" in jail assert "findtime = 10m" in jail assert "bantime = 24h" in jail assert "dbfile = /var/lib/fail2ban/fail2ban.sqlite3" in persistence assert "actionban = /usr/local/sbin/truf-caddy-admin-denylist ban ''" in action forbidden = ("iptables", "nft", "firewall-cmd", "ufw") assert not any(name in (jail + action).lower() for name in forbidden) def test_caddy_routes_and_failure_log_are_closed_and_redacted(): caddy = (ROOT / "deploy" / "edge" / "Caddyfile").read_text(encoding="ascii") assert "admin unix//run/caddy-admin.sock" in caddy assert "skip_install_trust" in caddy assert "trusted_proxies" not in caddy assert "import {$TRUF_EDGE_TLS_INCLUDE:/etc/caddy/tls/automatic.caddy}" in caddy site = caddy.split("{$TRUF_EDGE_HOST} {", 1)[1] assert site.index("import admin_security") < site.index("@worker path") assert "@worker path /api/v1/worker/*" in caddy assert "@admin_root path /{$TRUF_ADMIN_PREFIX}" in caddy assert "@admin path /{$TRUF_ADMIN_PREFIX}/*" in caddy assert "redir * /{$TRUF_ADMIN_PREFIX}/ 308" in caddy assert "header_down -Strict-Transport-Security" in caddy assert "uri strip_prefix /{$TRUF_ADMIN_PREFIX}" in caddy assert "request_header -X-Truf-Admin-Edge" in caddy assert "request_header -X-Truf-Admin-Operator" in caddy assert "header_up X-Truf-Admin-Edge {$TRUF_ADMIN_EDGE_MARKER}" in caddy assert "header_up X-Truf-Admin-Operator {http.auth.user.id}" in caddy assert caddy.count("header_up -X-Truf-Admin-Operator") == 1 assert "uri path_regexp ^ /admin-internal" in caddy assert "reverse_proxy 127.0.0.1:8766" in caddy assert "127.0.0.1:8767" not in caddy assert "import {$TRUF_ADMIN_DENYLIST_FILE:/etc/caddy/denylist/admin-denylist.caddy}" in caddy assert "basic_auth bcrypt" in caddy assert 'respond "" 404' in caddy assert "request delete" in caddy and "resp_headers delete" in caddy assert "log routine_access" in caddy and "output discard" in caddy assert "log_name admin_auth_failures" in caddy assert "header Authorization *" in caddy assert "@admin_unauthorized" in caddy assert caddy.count('WWW-Authenticate "Basic realm=\\"truf-admin\\""') == 2 for header in ("no-store", "same-origin", "Content-Security-Policy", "nosniff", "Strict-Transport-Security"): assert header in caddy assert "unsafe-inline" not in caddy assert not re.search(r"\bpath\s+/dashboard(?:\s|$)", caddy) assert not re.search(r"\bpath\s+/admin(?:\s|$)", caddy) def test_edge_compose_only_opts_runtime_namespace_into_https_publication(): base = yaml.safe_load((ROOT / "compose.yaml").read_text(encoding="ascii")) edge = yaml.safe_load((ROOT / "compose.edge.yaml").read_text(encoding="ascii")) assert "ports" not in base["services"]["runtime"] assert base["services"]["runtime"]["read_only"] is True assert edge["services"]["runtime"]["ports"] == [{ "target": 443, "published": "443", "protocol": "tcp", "mode": "host", }] service = edge["services"]["edge"] assert "TRUF_CADDY_ADMIN" not in service["environment"] assert service["network_mode"] == "service:runtime" assert "ports" not in service assert service["read_only"] is True assert service["cap_drop"] == ["ALL"] assert service["cap_add"] == ["NET_BIND_SERVICE"] assert service["build"]["target"] == "edge" config = yaml.safe_load((ROOT / "app" / "config.linux.yaml").read_text(encoding="utf-8")) assert config["supervisor"]["worker_api"]["enabled"] is False assert config["supervisor"]["worker_api"]["admin"]["enabled"] is False assert config["supervisor"]["worker_api"]["address"] == "127.0.0.1" assert config["supervisor"]["dashboard"]["address"] == "127.0.0.1" def test_edge_image_and_startup_require_pinned_caddy_hash_and_random_prefix(): dockerfile = (ROOT / "deploy" / "edge" / "Dockerfile").read_text(encoding="ascii") entrypoint = (ROOT / "deploy" / "edge" / "entrypoint.sh").read_text(encoding="ascii") assert re.search(r"^FROM caddy:2\.10\.2-alpine@sha256:[0-9a-f]{64} AS edge$", dockerfile, re.MULTILINE) assert "${#prefix}" in entrypoint and "^[0-9a-f]{64}$" in entrypoint assert "supported bcrypt hash" in entrypoint assert "TRUF_ADMIN_EDGE_MARKER must encode 256 random bits" in entrypoint assert "deploy/edge/automatic-tls.caddy /etc/caddy/tls/automatic.caddy" in dockerfile assert "localhost requires an explicit static TLS include" in entrypoint assert "TRUF_EDGE_TLS_INCLUDE must be an absolute Caddy TLS include" in entrypoint assert 'exec caddy run --config "$caddyfile"' in entrypoint assert "FROM edge AS edge-e2e" in dockerfile assert "admin 127.0.0.1:2019" in dockerfile def test_shared_host_edge_is_loopback_only_marker_gated_and_route_confined(): compose = yaml.safe_load((ROOT / "compose.shared-host.yaml").read_text(encoding="ascii")) provision = compose["services"]["provision"] runtime = compose["services"]["runtime"] edge = compose["services"]["edge"] assert provision == {"cpus": 0.9, "mem_limit": "720m"} assert runtime == { "network_mode": "host", "cpus": 0.9, "mem_limit": "720m", } assert edge["network_mode"] == "service:runtime" assert "ports" not in edge and "cap_add" not in edge assert edge["cap_drop"] == ["ALL"] assert edge["environment"]["TRUF_EDGE_MODE"] == "shared-host-edge-v1" assert "TRUF_SHARED_INGRESS_MARKER" in edge["environment"] assert compose["volumes"]["data"] == { "external": True, "name": "truf-remote-server-data", } caddy = (ROOT / "deploy" / "edge" / "Caddyfile.shared-host").read_text(encoding="ascii") assert "http://:18766" in caddy assert "\tbind 127.0.0.1" in caddy assert "http://127.0.0.1:18766" not in caddy assert "admin unix//run/caddy-admin.sock" in caddy assert "trusted_proxies static 127.0.0.1/32 ::1/128" in caddy assert "X-Truf-Shared-Ingress {$TRUF_SHARED_INGRESS_MARKER}" in caddy assert "request_header -X-Truf-Shared-Ingress" in caddy assert "log_append remote_ip {http.request.client_ip}" in caddy assert "tls " not in caddy.lower() snippet = (ROOT / "deploy" / "edge" / "host-caddy-shared.caddy").read_text(encoding="ascii") assert snippet.count("reverse_proxy 127.0.0.1:18766") == 2 assert "@truf_worker path /api/v1/worker/*" in snippet assert "@truf_admin path /{$TRUF_ADMIN_PREFIX}" in snippet assert snippet.count( "header_up X-Truf-Shared-Ingress {$TRUF_SHARED_INGRESS_MARKER}" ) == 2 assert "header_up -X-Truf-Shared-Ingress" not in snippet assert "tls" not in snippet.lower() assert "handle {" not in snippet def test_shared_host_denylist_uses_forwarded_client_identity_and_private_reload(tmp_path): instance, _, snippet = updater(tmp_path) instance = updater_module.DenylistUpdater( instance.state_path, snippet, project_directory=tmp_path / "project", env_file=tmp_path / "edge.env", profile=updater_module.SHARED_HOST_PROFILE, runner=lambda command: True, clock=lambda: 1_700_000_000, ) instance.update("ban", "198.51.100.7") assert "client_ip 198.51.100.7" in snippet.read_text(encoding="ascii") assert any(str(value).endswith("compose.shared-host.yaml") for value in instance.validate_command) assert "/etc/caddy/Caddyfile.shared-host" in instance.validate_command assert "unix//run/caddy-admin.sock" in instance.reload_command def test_edge_e2e_image_inputs_are_exact_and_host_orchestrator_stays_host_only(): dockerignore = (ROOT / ".dockerignore").read_text(encoding="ascii").splitlines() allowed = {line[1:] for line in dockerignore if line.startswith("!")} edge_e2e_inputs = {name for name in allowed if "edge_e2e" in name} assert edge_e2e_inputs == { "deploy/fail2ban/edge_e2e_docker_shim.py", "tests/edge_e2e_backend.py", "tests/edge_e2e_client.py", } assert "docker/verify_edge_e2e.py" not in allowed dockerfile = (ROOT / "Dockerfile").read_text(encoding="ascii") test_stage = dockerfile.split("FROM runtime-base AS test", 1)[1].split( "FROM runtime-base AS runtime", 1, )[0] assert "COPY --chown=10001:10001 tests/ /opt/truf/tests/" in test_stage assert "COPY --chown=10001:10001 docker/ /opt/truf/docker/" in test_stage assert "edge_e2e != {'edge_e2e_backend.py', 'edge_e2e_client.py'}" in test_stage assert "COPY app/" not in test_stage def test_production_server_omits_trufflehog_but_worker_and_test_retain_it(): dockerfile = (ROOT / "Dockerfile").read_text(encoding="ascii") server_native = dockerfile.split("FROM python-base AS native-dependencies", 1)[1].split( "FROM native-dependencies AS dependencies", 1, )[0] test_stage = dockerfile.split("FROM runtime-base AS test", 1)[1].split( "FROM runtime-base AS runtime", 1, )[0] worker_native = dockerfile.split("FROM python-base AS worker-native-dependencies", 1)[1].split( "FROM worker-native-dependencies AS worker", 1, )[0] assert "trufflehog" not in server_native.lower() assert "/usr/local/bin/trufflehog" in worker_native assert "/usr/local/bin/trufflehog" in test_stage def test_edge_e2e_orchestrator_is_private_labelled_and_uses_production_updater(): orchestrator = (ROOT / "docker" / "verify_edge_e2e.py").read_text(encoding="ascii") backend = (ROOT / "tests" / "edge_e2e_backend.py").read_text(encoding="ascii") assert "TEST_IMAGE = 'truf-edge-e2e-runtime:test'" in orchestrator assert "EDGE_IMAGE = 'truf-edge-e2e:test'" in orchestrator assert "FAIL2BAN_IMAGE = 'truf-fail2ban-edge-e2e:test'" in orchestrator assert "truf-local:edge" not in orchestrator assert "'network', 'create', '--driver', 'bridge', '--internal'" in orchestrator assert "'--network', 'container:' + self.names['backend']" in orchestrator assert "--publish" not in orchestrator and "--privileged" not in orchestrator assert "docker compose" not in orchestrator.lower() assert "DenylistUpdater(" not in orchestrator assert "update_denylist(" not in orchestrator assert "'/usr/bin/fail2ban-client'" in orchestrator assert "'--pid', 'container:' + self.names['edge']" in orchestrator assert "TRUF_CADDY_ADMIN" not in orchestrator assert "'--cap-add', 'KILL'" not in orchestrator assert "'--user', '10001:10001', '--read-only', '--cap-drop', 'ALL'" in orchestrator assert "/var/run/docker.sock" not in orchestrator assert "expected={direct_ip}" in orchestrator assert "['unbanip', direct_ip]" in orchestrator assert "['container', 'kill', '--signal', 'SIGKILL', fail2ban['id']]" in orchestrator assert "['gitlab', 'dockerhub', 'huggingface']" in backend assert "db.enqueue_targets('gitlab', 'gitlab'" in backend assert "baseline['operation_id']" in orchestrator def test_production_runbook_requires_fixed_host_agent_and_runtime_paths(): readme = (ROOT / "deploy" / "edge" / "README.md").read_text(encoding="ascii") for required in ( "truf_host_agent_install.py install", "truf_host_agent_install.py validate", "/run/truf/host-agent.sock", "/etc/truf/runtime/config.yaml", "/etc/truf/runtime", "/etc/truf/worker-packages", "/data/worker-packages", "root:root mode 0644", "/data/managed-files", "-f compose.yaml -f compose.edge.yaml", ): assert required in readme assert readme.index("Create `/etc/truf-edge/edge.env`") < readme.index( "truf_host_agent_install.py install" ) assert "build runtime edge" in readme def test_real_caddy_e2e_crawls_all_admin_routes_and_detail(): client = (ROOT / "tests" / "edge_e2e_client.py").read_text(encoding="ascii") for route in ( "'overview'", "'search'", "'supervisor'", "'logs'", "'config'", "'secrets'", "'files'", "'operations'", "'audit'", ): assert route in client assert "f'operations/{same_site_operation_id}'" in client def test_fail2ban_edge_e2e_image_is_pinned_minimal_and_uses_production_gate_files(): dockerfile = (ROOT / "deploy" / "fail2ban" / "Dockerfile.edge-e2e").read_text( encoding="ascii", ) assert re.search( r"^FROM caddy:2\.10\.2-alpine@sha256:[0-9a-f]{64} AS caddy-edge-e2e$", dockerfile, re.MULTILINE, ) assert re.search( r"^FROM debian:bookworm-slim@sha256:[0-9a-f]{64} AS fail2ban-edge-e2e$", dockerfile, re.MULTILINE, ) assert "fail2ban=1.0.2-2" in dockerfile for path in ( "filter.d-truf-admin-auth.conf", "jail.d-truf-admin-auth.local", "action.d-truf-caddy-admin-denylist.conf", "fail2ban.d-truf-persistence.local", "truf_caddy_admin_denylist.py", ): assert path in dockerfile assert "apt-get install -y --no-install-recommends" in dockerfile assert "docker-ce" not in dockerfile and "docker.io" not in dockerfile assert "admin 127.0.0.1:2019" in dockerfile runtime = (ROOT / "docker" / "Dockerfile.edge-e2e").read_text(encoding="ascii") assert runtime.startswith("FROM truf-worker-test:test AS edge-e2e-runtime\n") assert "tests/edge_e2e_backend.py tests/edge_e2e_client.py" in runtime def test_fail2ban_edge_e2e_reload_shim_accepts_only_production_updater_commands(tmp_path): path = ROOT / "deploy" / "fail2ban" / "edge_e2e_docker_shim.py" spec = importlib.util.spec_from_file_location("truf_edge_e2e_docker_shim", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) assert module.classify_command(module.VALIDATE_COMMAND) == "validate" assert module.classify_command(module.RELOAD_COMMAND) == "reload" for command in ( module.VALIDATE_COMMAND[:-1], module.RELOAD_COMMAND + ("other",), tuple("/var/run/docker.sock" if value == "edge" else value for value in module.RELOAD_COMMAND), ): with pytest.raises(ValueError): module.classify_command(command) env_file = tmp_path / "edge.env" env_file.write_text( "TRUF_EDGE_HOST=localhost\n" "TRUF_EDGE_TLS_INCLUDE=/etc/caddy/tls/static-tls.caddy\n" f"TRUF_ADMIN_PREFIX={'a' * 64}\n" "TRUF_ADMIN_USER=edge-e2e-admin\n" f"TRUF_ADMIN_PASSWORD_HASH=$2b$12${'A' * 53}\n" f"TRUF_ADMIN_EDGE_MARKER={'b' * 64}\n", encoding="ascii", ) values = module.load_environment(env_file) assert set(module.REQUIRED_ENV).issubset(values) assert values["PATH"] == "/usr/bin:/bin" module.VALIDATION_ERROR_PATH = tmp_path / "validation.error" secret = values["TRUF_ADMIN_PASSWORD_HASH"] module.record_validation_error( (secret + " " + "c" * 64 + "\n" + "x" * 8192).encode("ascii"), values, ) evidence = module.VALIDATION_ERROR_PATH.read_bytes() assert len(evidence) <= 4096 assert secret.encode("ascii") not in evidence assert ("c" * 64).encode("ascii") not in evidence def test_docker_e2e_verifiers_snapshot_foreign_state_and_revalidate_cleanup(): paths = ( ROOT / "docker" / "verify_packaged_workers.py", ROOT / "docker" / "verify_edge_e2e.py", ) for path in paths: source = path.read_text(encoding="ascii") assert "MAX_DOCKER_RESOURCES" in source assert "CONTAINER_METADATA_FORMAT" in source assert "VOLUME_METADATA_FORMAT" in source assert "snapshot_foreign()" in source assert "metadata_snapshot(exclude_owned=True)" in source assert "foreign_docker_state_changed" in source assert "def guarded_stop(self):" in source assert "inspect_owned('containers', name, expected)" in source assert "inspect_owned('volumes', name, expected)" in source assert "inspect_owned('networks', name, expected)" in source assert "def isolated_test_subnet(" in source assert "'--subnet', isolated_test_subnet(" in source assert "return f'198." in source assert "['container', 'rm', '--force'" not in source assert "system prune" not in source.lower() def test_e2e_failure_evidence_is_sanitized_metadata_only(): packaged = (ROOT / "docker" / "verify_packaged_workers.py").read_text(encoding="ascii") edge = (ROOT / "docker" / "verify_edge_e2e.py").read_text(encoding="ascii") for source in (packaged, edge): assert "'stage': label, 'class': failure_class, 'exit_code': exit_code" in source assert "'artifacts': os.fspath(verifier.run_root) if verifier is not None else None" not in source assert "'owned': verifier" not in source assert "clear_run_artifacts()" in source assert "secret_present_in_evidence" in source assert "'stderr': stderr.decode" not in edge assert "'stdout': stdout.decode" not in edge assert "client-" + "failure.json" not in edge assert "write_bytes(phase_root / 'client.log'" not in packaged assert "write_bytes(phase_root / 'server.log'" not in packaged assert "write_json(phase_root / 'completed.json'" not in packaged assert "retain_safe_evidence('summary.json', summary)" in packaged def test_edge_failure_stop_revalidates_then_targets_only_captured_id(): path = ROOT / "docker" / "verify_edge_e2e.py" spec = importlib.util.spec_from_file_location("truf_verify_edge_e2e_pure", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) run_id = "a" * 16 name = f"truf-edge-e2e-{run_id}-backend" identifier = "b" * 64 class Runner: def __init__(self): self.calls = [] def docker(self, label, arguments, **kwargs): self.calls.append((label, arguments)) if label == "inspect_owned_container": return 0, json.dumps({ "id": identifier, "name": "/" + name, "run": run_id, "kind": "backend", "running": True, "paused": False, "restarting": False, }).encode("ascii"), b"" assert label == "guarded_stop_owned_container" return 0, b"", b"" runner = Runner() resources = module.Resources(runner, run_id) resources.created["containers"][name] = {"id": identifier, "kind": "backend"} assert resources.guarded_stop() == 1 assert [label for label, _ in runner.calls] == [ "inspect_owned_container", "inspect_owned_container", "guarded_stop_owned_container", ] assert runner.calls[-1][1] == ["container", "stop", "--time", "30", identifier] @pytest.mark.parametrize( ("filename", "method"), [ ("verify_packaged_workers.py", "retain_safe_evidence"), ("verify_edge_e2e.py", "retain_failure_evidence"), ], ) def test_failure_evidence_removes_raw_artifacts_before_safe_write(tmp_path, filename, method): path = ROOT / "docker" / filename spec = importlib.util.spec_from_file_location("truf_" + path.stem + "_pure", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) verifier = object.__new__(module.Verifier) verifier.root = tmp_path build = tmp_path / "build" build.mkdir() prefix = "packaged-worker-e2e-" if "packaged" in filename else "edge-e2e-" verifier.run_root = build / (prefix + "a" * 16) verifier.run_root.mkdir() raw = verifier.run_root / "fixture" raw.mkdir() client_log = raw / "client.log" client_log.write_text("SECRET-SENTINEL", encoding="ascii") client_log.chmod(stat.S_IRUSR) raw.chmod(stat.S_IRUSR | stat.S_IXUSR) if "packaged" in filename: verifier.tokens = {"target": "SECRET-SENTINEL"} verifier.device_tokens = {} getattr(verifier, method)("failure.json", { "stage": "test_failure", "class": "Failure", "exit_code": 7, }) else: verifier.admin_password = "SECRET-SENTINEL" verifier.edge_marker = "edge-marker" verifier.prefix_secret = "prefix-secret" verifier.tokens = {} getattr(verifier, method)({ "stage": "test_failure", "class": "Failure", "exit_code": 7, }) assert [item.name for item in verifier.run_root.iterdir()] == ["failure.json"] failure = json.loads((verifier.run_root / "failure.json").read_text(encoding="ascii")) assert failure == {"stage": "test_failure", "class": "Failure", "exit_code": 7} def test_packaged_restart_proof_uses_stable_durable_state_not_log_timing(): source = (ROOT / "docker" / "verify_packaged_workers.py").read_text(encoding="ascii") assert "def assert_restart_stable(" in source assert "_restart_rescanned_or_rewrote_bundle" in source assert "checks >= 2" in source assert "time.monotonic() - started >= 2.5" in source assert "wait_windows_restart_attempts" not in source assert "wait_linux_restart_attempts" not in source def test_worker_packaging_boundary_allows_shared_db_free_modules_only(): readme = (ROOT / "docker" / "build-dependencies" / "README.md").read_text( encoding="ascii", ) assert "required shared DB-free modules" in readme assert "server runtime/control authority" in readme assert "provider and detailed" in readme and "keycheck authority" in readme assert "server credentials, and database credentials are absent" in readme assert "grants no provider or detailed keycheck authority" in readme assert "PostgreSQL tools, server/test code" not in readme def test_production_updater_cli_defaults_remain_unchanged(): args = updater_module.parse_args(["status"]) assert args.state_path == "/var/lib/truf-edge/admin-denylist.json" assert args.snippet_path == "/etc/truf-edge/denylist/admin-denylist.caddy" assert args.project_directory == "/opt/truf" assert args.env_file == "/etc/truf-edge/edge.env"