diff --git a/scripts/release_install_smoke.py b/scripts/release_install_smoke.py index b9cda3e2c..cdfb20f48 100644 --- a/scripts/release_install_smoke.py +++ b/scripts/release_install_smoke.py @@ -196,22 +196,26 @@ def _env_bin(env_root: Path, name: str) -> Path: def _run_command( - argv: Sequence[str], *, input_text: str | None = None + argv: Sequence[str], + *, + input_text: str | None = None, + env: Mapping[str, str] = SCRUBBED_COMMAND_ENV, ) -> CommandResult: + command_env = dict(env) result = subprocess.run( list(argv), input=input_text, capture_output=True, text=True, check=False, - env=dict(SCRUBBED_COMMAND_ENV), + env=command_env, ) return CommandResult( argv=tuple(argv), exit_code=result.returncode, stdout=result.stdout.strip(), stderr=result.stderr.strip(), - env=SCRUBBED_COMMAND_ENV, + env=command_env, ) @@ -815,19 +819,25 @@ def _forbidden_command_tokens(argv: Sequence[str]) -> list[str]: return forbidden -def _env_failures(label: str, env: Mapping[str, str]) -> list[Failure]: +def _env_failures( + label: str, + env: Mapping[str, str], + *, + expected: Mapping[str, str], +) -> list[Failure]: failures: list[Failure] = [] - if dict(env) != dict(SCRUBBED_COMMAND_ENV): + expected_env = dict(expected) + if dict(env) != expected_env: failures.append( _failure( f"{label} command environment is not scrubbed", - expected=repr(dict(SCRUBBED_COMMAND_ENV)), + expected=repr(expected_env), actual=repr(dict(env)), repair="python3 scripts/check_rust_release_manifest.py", ) ) for key in env: - if key in SCRUBBED_COMMAND_ENV and env[key] == SCRUBBED_COMMAND_ENV[key]: + if key in expected_env and env[key] == expected_env[key]: continue upper = key.upper() if any(part in upper for part in FORBIDDEN_ENV_KEY_PARTS): @@ -1065,7 +1075,13 @@ def _validate_observation( repair="python3 scripts/check_rust_release_manifest.py", ) ) - failures.extend(_env_failures("install proof install", observation.install.env)) + failures.extend( + _env_failures( + "install proof install", + observation.install.env, + expected=SCRUBBED_COMMAND_ENV, + ) + ) try: expected_distributions = _distribution_entries( expected_distribution_entries(install_paths) @@ -1216,7 +1232,13 @@ def _validate_observation( repair="python3 scripts/check_rust_release_manifest.py", ) ) - failures.extend(_env_failures("install proof smoke", result.env)) + failures.extend( + _env_failures( + "install proof smoke", + result.env, + expected=SCRUBBED_COMMAND_ENV, + ) + ) if result.exit_code != 0: failures.append( _failure( @@ -1506,7 +1528,13 @@ def _validate_command_payload(label: str, value: Any) -> list[Failure]: ) ) else: - failures.extend(_env_failures(f"install proof {label}", value["env"])) + failures.extend( + _env_failures( + f"install proof {label}", + value["env"], + expected=SCRUBBED_COMMAND_ENV, + ) + ) if not isinstance(value.get("exit_code"), int): failures.append( _failure( diff --git a/scripts/release_nvattest_proof.py b/scripts/release_nvattest_proof.py index 875208196..dfd8e2108 100644 --- a/scripts/release_nvattest_proof.py +++ b/scripts/release_nvattest_proof.py @@ -63,6 +63,7 @@ from solstone.think.providers.nvattest_install import ( SPP_NVATTEST_DIR_ENV, cache_root, ) +from solstone.think.providers.nvattest_loader import nvattest_library_env NVATTEST_PROOF_KIND = "solstone-nvattest-compatibility-receipt" NVATTEST_CACHE_ROOT = "NVATTEST_CACHE_ROOT" @@ -168,7 +169,7 @@ class NvattestProofServices: [Path, str, Sequence[FetchObservation], DriverObservation], Mapping[str, Any], ] - run_smoke: Callable[[Path], CommandResult] + run_smoke: Callable[[Path, Path], CommandResult] clock: Callable[[], datetime] cleanup: Callable[[Path], None] observe_host: Callable[[], HostObservation] @@ -390,8 +391,19 @@ def _default_integrity_recheck( } -def _default_run_smoke(nvattest_bin: Path) -> CommandResult: - return _run_command((str(nvattest_bin), "--help")) +def _nvattest_smoke_env(nvattest_root: Path) -> dict[str, str]: + return {**SCRUBBED_COMMAND_ENV, **nvattest_library_env(nvattest_root)} + + +def _expected_smoke_env() -> dict[str, str]: + return _nvattest_smoke_env(Path(NVATTEST_CACHE_ROOT)) + + +def _default_run_smoke(nvattest_root: Path, nvattest_bin: Path) -> CommandResult: + return _run_command( + (str(nvattest_bin), "--help"), + env=_nvattest_smoke_env(nvattest_root), + ) def default_services() -> NvattestProofServices: @@ -1333,6 +1345,7 @@ def _validate_observation( cache_root=observation.cache_root, site_roots=(), ), + expected_env=SCRUBBED_COMMAND_ENV, ) ) installed_authority_sha = hashlib.sha256( @@ -1739,7 +1752,16 @@ def _command_payload( ) for token in result.argv ], - "env": dict(result.env), + "env": { + key: _normalize_receipt_path( + value, + env_root=env_root, + candidate_dir=candidate_dir, + cache_root=cache_root, + site_roots=site_roots, + ) + for key, value in result.env.items() + }, "exit_code": result.exit_code, "stderr": _normalize_receipt_text( result.stderr, @@ -1825,7 +1847,12 @@ def _normalization_prefixes( return tuple(raw) -def _validate_command_payload(label: str, value: Any) -> list[Failure]: +def _validate_command_payload( + label: str, + value: Any, + *, + expected_env: Mapping[str, str], +) -> list[Failure]: failures: list[Failure] = [] if not isinstance(value, Mapping) or set(value) != { "argv", @@ -1870,7 +1897,13 @@ def _validate_command_payload(label: str, value: Any) -> list[Failure]: ) ) else: - failures.extend(_env_failures(f"nvattest proof {label}", env)) + failures.extend( + _env_failures( + f"nvattest proof {label}", + env, + expected=expected_env, + ) + ) if not isinstance(value.get("exit_code"), int): failures.append( _failure( @@ -2093,12 +2126,14 @@ def validate_nvattest_proof( _validate_command_payload( "package driver", cache_install.get("package_driver_command"), + expected_env=SCRUBBED_COMMAND_ENV, ) ) failures.extend( _validate_command_payload( "wheel install", cache_install.get("wheel_install_command"), + expected_env=SCRUBBED_COMMAND_ENV, ) ) failures.extend( @@ -2107,7 +2142,13 @@ def validate_nvattest_proof( expected_support_distributions=expected_support_distributions, ) ) - failures.extend(_validate_command_payload("smoke", proof.get("smoke"))) + failures.extend( + _validate_command_payload( + "smoke", + proof.get("smoke"), + expected_env=_expected_smoke_env(), + ) + ) failures.extend(validate_public_evidence_tree("nvattest_proof", proof)) return failures @@ -2469,13 +2510,20 @@ def _validate_authority_bound_sections( f"{NVATTEST_CACHE_ROOT}/{_nvattest_bin_relpath(authority_target).as_posix()}", "--help", ] + expected_smoke = { + "argv": expected_smoke_argv, + "env": _expected_smoke_env(), + "exit_code": 0, + } if not isinstance(smoke, Mapping) or ( - smoke.get("argv") != expected_smoke_argv or smoke.get("exit_code") != 0 + smoke.get("argv") != expected_smoke_argv + or smoke.get("env") != expected_smoke["env"] + or smoke.get("exit_code") != 0 ): failures.append( _failure( "nvattest proof smoke command is invalid", - expected=repr({"argv": expected_smoke_argv, "exit_code": 0}), + expected=repr(expected_smoke), actual=repr(smoke), ) ) @@ -2635,7 +2683,7 @@ def run_nvattest_proof( ), ) nvattest_bin = cache / _nvattest_bin_relpath(authority_target) - smoke = _call_stage("smoke", lambda: resolved.run_smoke(nvattest_bin)) + smoke = _call_stage("smoke", lambda: resolved.run_smoke(cache, nvattest_bin)) observation = NvattestProofObservation( env_root=env_root, journal_path=journal_path, diff --git a/solstone/think/providers/nvattest_loader.py b/solstone/think/providers/nvattest_loader.py new file mode 100644 index 000000000..372255b5b --- /dev/null +++ b/solstone/think/providers/nvattest_loader.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Dynamic-loader contract for the nvattest provider payload.""" + +from __future__ import annotations + +from pathlib import Path + +NVATTEST_LIB_RELPATH = Path("lib") + + +def nvattest_library_env(nvattest_dir: Path) -> dict[str, str]: + # The published 1.2.2-sol.2 payload's bin/nvattest carries + # RUNPATH: [/src/build/release/nv-attestation-sdk-build:] and NEEDED: + # libnvat.so.1. Without LD_LIBRARY_PATH pointing at the payload's own + # lib/ directory, the binary exits 127 with "error while loading shared + # libraries". + return {"LD_LIBRARY_PATH": str(nvattest_dir / NVATTEST_LIB_RELPATH)} + + +__all__ = ["NVATTEST_LIB_RELPATH", "nvattest_library_env"] diff --git a/solstone/think/services/spp_attest/nvgpu/binary.py b/solstone/think/services/spp_attest/nvgpu/binary.py index 5000ca7fb..83bcb2132 100644 --- a/solstone/think/services/spp_attest/nvgpu/binary.py +++ b/solstone/think/services/spp_attest/nvgpu/binary.py @@ -9,6 +9,10 @@ import os from dataclasses import dataclass from pathlib import Path +from solstone.think.providers.nvattest_loader import ( + NVATTEST_LIB_RELPATH, + nvattest_library_env, +) from solstone.think.services.spp_attest.nvgpu.errors import GpuAppraisalError from solstone.think.services.spp_attest.tlv import SPDM_NONCE_SIZE @@ -26,7 +30,7 @@ def locate_nvattest(nvattest_dir: Path) -> tuple[Path, Path, Path]: root = nvattest_dir.resolve() binary = root / "bin" / "nvattest" - lib_dir = root / "lib" + lib_dir = root / NVATTEST_LIB_RELPATH ca_bundle = root / CA_BUNDLE_RELATIVE_PATH if not root.is_dir(): raise GpuAppraisalError("nvattest_unavailable") @@ -81,5 +85,6 @@ def build_nvattest_attest_command( argv.extend(["--rim-dir", str(rim_dir)]) argv.extend(["--nonce", owner_nonce.hex()]) return NvattestCommand( - argv=argv, env={**os.environ, "LD_LIBRARY_PATH": str(lib_dir)} + argv=argv, + env={**os.environ, **nvattest_library_env(lib_dir.parent)}, ) diff --git a/tests/helpers/nvattest_fixtures.py b/tests/helpers/nvattest_fixtures.py index 8a743cb4c..50764ae75 100644 --- a/tests/helpers/nvattest_fixtures.py +++ b/tests/helpers/nvattest_fixtures.py @@ -7,6 +7,7 @@ import hashlib import io import shutil import tarfile +import urllib.request from dataclasses import dataclass, replace from pathlib import Path @@ -18,6 +19,8 @@ from solstone.think.providers.nvattest_authority import ( NvattestTargetEntry, ) +REAL_ARCHIVE_USER_AGENT = "solstone-nvattest-install-integration/1.0" + @dataclass(frozen=True, slots=True) class FixtureArchive: @@ -128,6 +131,32 @@ def _raw_download_fixture( ) +def download_real_archive(root: Path, entry: NvattestTargetEntry) -> Path: + root.mkdir(parents=True, exist_ok=True) + archive = root / entry.artifact.name + tmp = archive.with_name(f".{archive.name}.tmp") + digest = hashlib.sha256() + try: + request = urllib.request.Request( + entry.artifact.url, + headers={"User-Agent": REAL_ARCHIVE_USER_AGENT}, + ) + with urllib.request.urlopen(request, timeout=120) as response: + with tmp.open("wb") as handle: + while True: + chunk = response.read(1024 * 1024) + if not chunk: + break + digest.update(chunk) + handle.write(chunk) + actual = digest.hexdigest() + assert actual == entry.artifact.sha256 + tmp.replace(archive) + return archive + finally: + tmp.unlink(missing_ok=True) + + def _inject_failure( monkeypatch: pytest.MonkeyPatch, failure_point: str, diff --git a/tests/helpers/release_candidate_fixtures.py b/tests/helpers/release_candidate_fixtures.py index 4fd7942b1..a40548605 100644 --- a/tests/helpers/release_candidate_fixtures.py +++ b/tests/helpers/release_candidate_fixtures.py @@ -57,6 +57,7 @@ from solstone.think.providers.nvattest_authority import ( nvattest_target_key, ) from solstone.think.providers.nvattest_install import SIDECAR_SCHEMA_VERSION +from solstone.think.providers.nvattest_loader import nvattest_library_env SPEAKERS_ANALYZE_LINUX_X86_64_TAG = SOLSTONE_CORE_SPEAKERS_ANALYZE_PLATFORM_TAGS[ ("linux", "x86_64") @@ -664,13 +665,14 @@ def _nvattest_command_result( *, stdout: str = "", exit_code: int = 0, + env: Mapping[str, str] = SCRUBBED_COMMAND_ENV, ) -> CommandResult: return CommandResult( argv=tuple(argv), exit_code=exit_code, stdout=stdout, stderr="", - env=SCRUBBED_COMMAND_ENV, + env=env, ) @@ -792,9 +794,13 @@ def _nvattest_services( payload=payload, ) - def run_smoke(nvattest_bin: Path) -> CommandResult: + def run_smoke(nvattest_root: Path, nvattest_bin: Path) -> CommandResult: count("nvattest_run_smoke") - return _nvattest_command_result((str(nvattest_bin), "--help"), stdout="usage\n") + return _nvattest_command_result( + (str(nvattest_bin), "--help"), + stdout="usage\n", + env={**SCRUBBED_COMMAND_ENV, **nvattest_library_env(nvattest_root)}, + ) def integrity_recheck( _journal: Path, diff --git a/tests/services/test_spp_attest_nvgpu.py b/tests/services/test_spp_attest_nvgpu.py index ebb346a8e..1c270963c 100644 --- a/tests/services/test_spp_attest_nvgpu.py +++ b/tests/services/test_spp_attest_nvgpu.py @@ -15,6 +15,7 @@ from typing import Any import pytest import solstone.think.services.spp_attest.nvgpu.appraise as appraise_module +from solstone.think.providers.nvattest_loader import nvattest_library_env from solstone.think.services.spp_attest.nvgpu.binary import ( build_nvattest_attest_command, ) @@ -489,6 +490,21 @@ def test_nvattest_command_env_inherits_parent_and_sets_library_path( ) +def test_nvattest_command_uses_shared_library_env_overlay(tmp_path: Path) -> None: + nvattest_dir = _fake_nvattest_dir(tmp_path) + + command = build_nvattest_attest_command( + nvattest_dir=nvattest_dir, + evidence_file=tmp_path / "evidence.json", + owner_nonce=_owner_nonce(), + ) + + assert ( + command.env["LD_LIBRARY_PATH"] + == nvattest_library_env(nvattest_dir.resolve())["LD_LIBRARY_PATH"] + ) + + def test_nvattest_command_rejects_missing_ca_bundle(tmp_path: Path) -> None: nvattest_dir = _fake_nvattest_dir(tmp_path) (nvattest_dir / "share" / "ca" / "ca-bundle.pem").unlink() diff --git a/tests/test_nvattest_install.py b/tests/test_nvattest_install.py index 61a994af1..38a5d145c 100644 --- a/tests/test_nvattest_install.py +++ b/tests/test_nvattest_install.py @@ -7,7 +7,6 @@ import hashlib import json import os import tarfile -import urllib.request from collections.abc import Callable, Iterator from contextlib import contextmanager from dataclasses import replace @@ -33,6 +32,7 @@ from tests.helpers.nvattest_fixtures import ( _install_download_from_fixture, _raw_download_fixture, _write_payload_tarball, + download_real_archive, ) SIDECAR_KEYS = { @@ -42,7 +42,6 @@ SIDECAR_KEYS = { "tree_fingerprint_sha256", "version", } -REAL_ARCHIVE_USER_AGENT = "solstone-nvattest-install-integration/1.0" class DownloadSentinel(RuntimeError): @@ -104,7 +103,7 @@ def test_real_published_nvattest_archives_match_authority( target_key: NvattestTargetKey, ) -> None: entry = authority_entry(target_key) - archive = _download_real_archive(tmp_path / "downloads", entry) + archive = download_real_archive(tmp_path / "downloads", entry) raw_dir = tmp_path / target_key / "raw" nvattest_install._safe_extract_nvattest_tarball(archive, raw_dir) @@ -674,32 +673,6 @@ def _invalid_flat_archive( ) -def _download_real_archive(root: Path, entry: NvattestTargetEntry) -> Path: - root.mkdir(parents=True, exist_ok=True) - archive = root / entry.artifact.name - tmp = archive.with_name(f".{archive.name}.tmp") - digest = hashlib.sha256() - try: - request = urllib.request.Request( - entry.artifact.url, - headers={"User-Agent": REAL_ARCHIVE_USER_AGENT}, - ) - with urllib.request.urlopen(request, timeout=120) as response: - with tmp.open("wb") as handle: - while True: - chunk = response.read(1024 * 1024) - if not chunk: - break - digest.update(chunk) - handle.write(chunk) - actual = digest.hexdigest() - assert actual == entry.artifact.sha256 - tmp.replace(archive) - return archive - finally: - tmp.unlink(missing_ok=True) - - def _assert_payload_layout(root: Path, entry: NvattestTargetEntry) -> None: assert {path.name for path in root.iterdir()} >= { *_payload_top_level_names(), diff --git a/tests/test_release_nvattest_proof.py b/tests/test_release_nvattest_proof.py index 2b5a09e0a..741af03bf 100644 --- a/tests/test_release_nvattest_proof.py +++ b/tests/test_release_nvattest_proof.py @@ -6,6 +6,7 @@ from __future__ import annotations import copy import hashlib import json +import platform import shutil import zipfile from collections.abc import Callable, Mapping, Sequence @@ -17,10 +18,12 @@ from typing import Any import pytest import scripts.release_nvattest_proof as proof +import solstone.think.providers.nvattest_authority as nvattest_authority from scripts.check_rust_release_manifest import canonical_json_bytes from scripts.release_install_smoke import SCRUBBED_COMMAND_ENV from scripts.release_public_evidence import validate_public_evidence_tree from scripts.release_target_policy import TARGET_POLICY +from solstone.think.providers import nvattest_install from solstone.think.providers.nvattest_authority import ( TARGET_KEYS, NvattestTargetKey, @@ -28,7 +31,14 @@ from solstone.think.providers.nvattest_authority import ( authority_payload, ) from solstone.think.providers.nvattest_install import SIDECAR_SCHEMA_VERSION -from tests.helpers.nvattest_fixtures import _write_payload_tarball +from solstone.think.providers.nvattest_loader import ( + NVATTEST_LIB_RELPATH, + nvattest_library_env, +) +from tests.helpers.nvattest_fixtures import ( + _write_payload_tarball, + download_real_archive, +) SOURCE_COMMIT = "a" * 40 CORE_LOCK = "b" * 64 @@ -153,12 +163,19 @@ def test_synthetic_run_writes_canonical_public_receipt_for_target( == [] ) text = data.decode("utf-8") + assert payload["smoke"]["env"]["LD_LIBRARY_PATH"] in text for forbidden in ("/tmp", "/private", "/home", "site-packages", str(tmp_path)): assert forbidden not in text assert payload["smoke"]["argv"] == [ f"{proof.NVATTEST_CACHE_ROOT}/bin/nvattest", "--help", ] + assert payload["smoke"]["env"] == { + **SCRUBBED_COMMAND_ENV, + "LD_LIBRARY_PATH": ( + f"{proof.NVATTEST_CACHE_ROOT}/{NVATTEST_LIB_RELPATH.as_posix()}" + ), + } assert payload["cache_install"]["wheel_install_command"]["argv"][-8:] == [ f"{proof.SUPPORT}/{entry['filename']}" for entry in case.support_distributions ] @@ -200,6 +217,63 @@ def test_command_text_normalization_fails_closed_on_prefix_collision() -> None: } +def test_default_run_smoke_uses_payload_library_path_offline(tmp_path: Path) -> None: + root = tmp_path / "nvattest" + bin_dir = root / "bin" + lib_dir = root / "lib" + bin_dir.mkdir(parents=True) + lib_dir.mkdir() + nvattest_bin = bin_dir / "nvattest" + nvattest_bin.write_text( + "#!/bin/sh\n" + f'if [ "${{LD_LIBRARY_PATH:-}}" != "{lib_dir}" ]; then\n' + ' echo "error while loading shared libraries: libnvat.so.1: ' + 'cannot open shared object file: No such file or directory" >&2\n' + " exit 127\n" + "fi\n" + "exit 0\n", + encoding="utf-8", + ) + nvattest_bin.chmod(0o755) + + result = proof._default_run_smoke(root, nvattest_bin) + + assert result.exit_code == 0 + assert result.env == { + **SCRUBBED_COMMAND_ENV, + "LD_LIBRARY_PATH": str(lib_dir), + } + + +@pytest.mark.integration +def test_default_run_smoke_uses_payload_library_path_for_real_linux_archive( + tmp_path: Path, +) -> None: + if platform.system() != "Linux" or platform.machine() != "x86_64": + pytest.skip("real nvattest linux-x86_64 smoke requires linux/x86_64") + target_key = "linux-x86_64" + entry = authority_entry(target_key) + archive = download_real_archive(tmp_path / "downloads", entry) + authority_json = json.loads( + Path(nvattest_authority.__file__) + .with_name("nvattest_authority_v1.json") + .read_text(encoding="utf-8") + ) + expected_sha = authority_json["targets"][target_key]["artifact"]["sha256"] + assert hashlib.sha256(archive.read_bytes()).hexdigest() == expected_sha + raw_dir = tmp_path / "extract" + nvattest_install._safe_extract_nvattest_tarball(archive, raw_dir) + root = nvattest_install._find_extracted_root(raw_dir, entry) + + result = proof._default_run_smoke(root, root / "bin" / "nvattest") + + assert result.exit_code == 0 + assert result.env == { + **SCRUBBED_COMMAND_ENV, + "LD_LIBRARY_PATH": str(root / "lib"), + } + + @pytest.mark.parametrize( ("label", "mutate"), [ @@ -235,6 +309,17 @@ def test_command_text_normalization_fails_closed_on_prefix_collision() -> None: "smoke_argv", lambda receipt: receipt["smoke"].update({"argv": ["nvattest", "--help"]}), ), + ( + "smoke_env", + lambda receipt: receipt["smoke"].update( + { + "env": { + **SCRUBBED_COMMAND_ENV, + "LD_LIBRARY_PATH": "NVATTEST_CACHE_ROOT/not-lib", + } + } + ), + ), ("smoke_exit", lambda receipt: receipt["smoke"].update({"exit_code": 1})), ], ) @@ -424,7 +509,7 @@ def test_spoofed_or_near_miss_host_fails_before_reach( calls.append("driver") or pytest.fail("driver should not run") ), integrity_recheck=lambda *_args: {}, - run_smoke=lambda _path: pytest.fail("smoke should not run"), + run_smoke=lambda _root, _path: pytest.fail("smoke should not run"), clock=lambda: RECORDED_AT, cleanup=lambda _path: calls.append("cleanup"), observe_host=lambda: host, @@ -467,6 +552,14 @@ def test_receipt_validator_names_policy_negative_cases(tmp_path: Path) -> None: } ), lambda data: data["smoke"].update({"argv": ["nvattest", "--help"]}), + lambda data: data["smoke"].update( + { + "env": { + **SCRUBBED_COMMAND_ENV, + "LD_LIBRARY_PATH": "NVATTEST_CACHE_ROOT/not-lib", + } + } + ), lambda data: data["support_distributions"].pop(), ] for mutate in mutations: @@ -700,8 +793,12 @@ def _synthetic_services( payload=payload, ) - def run_smoke(nvattest_bin: Path) -> proof.CommandResult: - return _command_result((str(nvattest_bin), "--help"), stdout="usage\n") + def run_smoke(nvattest_root: Path, nvattest_bin: Path) -> proof.CommandResult: + return _command_result( + (str(nvattest_bin), "--help"), + stdout="usage\n", + env={**SCRUBBED_COMMAND_ENV, **nvattest_library_env(nvattest_root)}, + ) return proof.NvattestProofServices( create_environment=create_environment, @@ -830,13 +927,14 @@ def _command_result( *, stdout: str = "", exit_code: int = 0, + env: Mapping[str, str] = SCRUBBED_COMMAND_ENV, ) -> proof.CommandResult: return proof.CommandResult( argv=tuple(argv), exit_code=exit_code, stdout=stdout, stderr="", - env=SCRUBBED_COMMAND_ENV, + env=env, )