From f2768079b02014351d30878fdb81e3239cdc68bd Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Sun, 11 Jan 2026 08:54:57 +0000 Subject: [PATCH 01/11] launcher: add measured PLATFORM mode for TEE vs non-TEE --- tee_launcher/launcher.py | 81 +++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/tee_launcher/launcher.py b/tee_launcher/launcher.py index 495dc4d15e..2f9a957004 100644 --- a/tee_launcher/launcher.py +++ b/tee_launcher/launcher.py @@ -1,6 +1,7 @@ from collections import deque import logging import os +import stat from typing import Dict, Union import requests from subprocess import CompletedProcess, run, check_output @@ -36,6 +37,13 @@ class RpcTimingConfig(NamedTuple): rpc_max_attempts: int +# +# Platform mode (MUST come from measured docker-compose env in TEE deployments) +# +ENV_VAR_PLATFORM = "PLATFORM" # expected values: "TEE" or "NONTEE" +PLATFORM_TEE = "TEE" +PLATFORM_NONTEE = "NONTEE" + # only considered if `IMAGE_DIGEST_FILE` does not exist. ENV_VAR_DEFAULT_IMAGE_DIGEST = "DEFAULT_IMAGE_DIGEST" @@ -270,6 +278,35 @@ def parse_env_file(path: str) -> dict: return parse_env_lines(f.readlines()) +def is_unix_socket(path: str) -> bool: + try: + st = os.stat(path) + return stat.S_ISSOCK(st.st_mode) + except FileNotFoundError: + return False + except Exception: + return False + + +def parse_platform() -> str: + """ + Platform selection MUST be a measured input in TEE deployments. + Therefore, we only read it from process env (docker-compose 'environment'), + and never from /tapp/user_config. + """ + raw = os.environ.get(ENV_VAR_PLATFORM) + if raw is None: + raise RuntimeError( + f"{ENV_VAR_PLATFORM} must be set to {PLATFORM_TEE} or {PLATFORM_NONTEE}." + ) + val = raw.strip().upper() + if val not in (PLATFORM_TEE, PLATFORM_NONTEE): + raise RuntimeError( + f"Invalid {ENV_VAR_PLATFORM}={raw!r}. Expected {PLATFORM_TEE} or {PLATFORM_NONTEE}." + ) + return val + + def load_rpc_timing_config(dstack_config: dict[str, str]) -> RpcTimingConfig: """ Loads dockerhub RPC timing configuration from dstack_config, @@ -440,7 +477,16 @@ def validate_image_hash( return False -def extend_rtmr3(valid_hash: str) -> None: +def extend_rtmr3(platform: str, valid_hash: str) -> None: + if platform == PLATFORM_NONTEE: + logging.info("PLATFORM=NONTEE → skipping RTMR3 extension step.") + return + + if not is_unix_socket(DSTACK_UNIX_SOCKET): + raise RuntimeError( + f"PLATFORM=TEE requires dstack unix socket at {DSTACK_UNIX_SOCKET}." + ) + bare = get_bare_digest(valid_hash) logging.info(f"Extending RTMR3 with validated hash: {bare}") @@ -460,11 +506,11 @@ def extend_rtmr3(valid_hash: str) -> None: raise RuntimeError("EmitEvent failed while extending RTMR3") -def launch_mpc_container(valid_hash: str, user_env: dict) -> None: +def launch_mpc_container(platform: str, valid_hash: str, user_env: dict) -> None: logging.info(f"Launching MPC node with validated hash: {valid_hash}") remove_existing_container() - docker_cmd = build_docker_cmd(user_env, valid_hash) + docker_cmd = build_docker_cmd(platform, user_env, valid_hash) proc = run(docker_cmd) if proc.returncode != 0: @@ -510,6 +556,14 @@ def curl_unix_socket_post( def main(): logging.info("start") + platform = parse_platform() + logging.info(f"Launcher platform: {platform}") + + if platform == PLATFORM_TEE and not is_unix_socket(DSTACK_UNIX_SOCKET): + raise RuntimeError( + f"PLATFORM=TEE requires dstack unix socket at {DSTACK_UNIX_SOCKET}." + ) + # DOCKER_CONTENT_TRUST must be enabled if os.environ.get(OS_ENV_DOCKER_CONTENT_TRUST, "0") != "1": raise RuntimeError( @@ -547,9 +601,9 @@ def main(): # Continue with validated hash logging.info(f"MPC image hash validated successfully: {selected_hash}") - extend_rtmr3(selected_hash) + extend_rtmr3(platform, selected_hash) - launch_mpc_container(selected_hash, dstack_config) + launch_mpc_container(platform, selected_hash, dstack_config) def request_until_success( @@ -685,7 +739,9 @@ def get_bare_digest(full_digest: str) -> str: return full_digest.split(":", 1)[1] -def build_docker_cmd(user_env: dict[str, str], image_digest: str) -> list[str]: +def build_docker_cmd( + platform: str, user_env: dict[str, str], image_digest: str +) -> list[str]: bare_digest = get_bare_digest(image_digest) docker_cmd = ["docker", "run"] @@ -693,7 +749,12 @@ def build_docker_cmd(user_env: dict[str, str], image_digest: str) -> list[str]: # Required environment variables docker_cmd += ["--env", f"MPC_IMAGE_HASH={bare_digest}"] docker_cmd += ["--env", f"MPC_LATEST_ALLOWED_HASH_FILE={IMAGE_DIGEST_FILE}"] - docker_cmd += ["--env", f"DSTACK_ENDPOINT={DSTACK_UNIX_SOCKET}"] + + if platform == PLATFORM_TEE: + docker_cmd += ["--env", "MPC_TEE_MODE=1"] + docker_cmd += ["--env", f"DSTACK_ENDPOINT={DSTACK_UNIX_SOCKET}"] + else: + docker_cmd += ["--env", "MPC_TEE_MODE=0"] # MPC env vars and extra config for key, value in user_env.items(): @@ -737,8 +798,6 @@ def build_docker_cmd(user_env: dict[str, str], image_digest: str) -> list[str]: "-v", "/tapp:/tapp:ro", "-v", - f"{DSTACK_UNIX_SOCKET}:{DSTACK_UNIX_SOCKET}", - "-v", "shared-volume:/mnt/shared", "-v", "mpc-data:/data", @@ -748,6 +807,10 @@ def build_docker_cmd(user_env: dict[str, str], image_digest: str) -> list[str]: image_digest, # IMPORTANT: Docker must get the FULL digest ] + # Mount dstack socket ONLY in TEE mode. + if platform == PLATFORM_TEE: + docker_cmd += ["-v", f"{DSTACK_UNIX_SOCKET}:{DSTACK_UNIX_SOCKET}"] + logging.info("docker cmd %s", " ".join(docker_cmd)) # Final LD_PRELOAD safeguard From c12dbe0a6627859b4540ef05e1182aff5c69291b Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Sun, 11 Jan 2026 12:36:02 +0000 Subject: [PATCH 02/11] add tests --- tee_launcher/test_launcher_config.py | 303 +++++++++++++++++++++++++-- 1 file changed, 287 insertions(+), 16 deletions(-) diff --git a/tee_launcher/test_launcher_config.py b/tee_launcher/test_launcher_config.py index 2aa2328251..1509918c5e 100644 --- a/tee_launcher/test_launcher_config.py +++ b/tee_launcher/test_launcher_config.py @@ -21,6 +21,9 @@ ENV_VAR_DEFAULT_IMAGE_DIGEST, ) +TEE = launcher.PLATFORM_TEE +NONTEE = launcher.PLATFORM_NONTEE + def make_digest_json(hashes): return json.dumps({JSON_KEY_APPROVED_HASHES: hashes}) @@ -124,7 +127,7 @@ def test_build_docker_cmd_sanitizes_ports_and_hosts(): "EXTRA_HOSTS": "node:192.168.1.1,--volume /:/mnt", "MPC_ACCOUNT_ID": "mpc-user-123", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") func_name = inspect.currentframe().f_code.co_name print(f"[{func_name}] CMD:", " ".join(cmd)) @@ -146,7 +149,7 @@ def test_extra_hosts_does_not_allow_ld_preload(): "EXTRA_HOSTS": "host:1.2.3.4,--env LD_PRELOAD=/evil.so", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") assert "host:1.2.3.4" in cmd assert not any("LD_PRELOAD" in arg for arg in cmd) @@ -157,7 +160,7 @@ def test_ports_does_not_allow_volume_injection(): "PORTS": "2200:2200,--volume /:/mnt", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") assert "2200:2200" in cmd assert not any("/:/mnt" in arg for arg in cmd) @@ -168,7 +171,7 @@ def test_invalid_env_key_is_ignored(): "BAD_KEY": "should_not_be_used", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") assert "should_not_be_used" not in " ".join(cmd) assert "MPC_ACCOUNT_ID=safe" in cmd @@ -178,7 +181,7 @@ def test_protocol_upgrade_override_is_allowed(): env = { "NEAR_TESTS_PROTOCOL_UPGRADE_OVERRIDE": "now", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") assert "NEAR_TESTS_PROTOCOL_UPGRADE_OVERRIDE=now" in " ".join(cmd) @@ -187,7 +190,7 @@ def test_mpc_backup_encryption_key_is_allowed(): env = { "MPC_BACKUP_ENCRYPTION_KEY_HEX": "0000000000000000000000000000000000000000000000000000000000000000", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") assert ( "MPC_BACKUP_ENCRYPTION_KEY_HEX=0000000000000000000000000000000000000000000000000000000000000000" @@ -200,7 +203,7 @@ def test_malformed_extra_host_is_ignored(): "EXTRA_HOSTS": "badhostentry,no-colon,also--bad", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") assert "--add-host" not in cmd # All malformed entries should be skipped @@ -209,7 +212,7 @@ def test_env_value_with_shell_injection_is_handled_safely(): env = { "MPC_ACCOUNT_ID": "safe; rm -rf /", } - cmd = build_docker_cmd(env, "sha256:abc123") + cmd = build_docker_cmd(TEE, env, "sha256:abc123") assert "--env" in cmd assert "MPC_ACCOUNT_ID=safe; rm -rf /" in cmd @@ -227,7 +230,7 @@ def test_parse_and_build_docker_cmd_full_flow(): env = parse_env_string(config_str) image_hash = env.get("IMAGE_HASH", "sha256:default") - cmd = build_docker_cmd(env, image_hash) + cmd = build_docker_cmd(TEE, env, image_hash) print(f"[{inspect.currentframe().f_code.co_name}] CMD: {' '.join(cmd)}") @@ -252,7 +255,7 @@ def test_ld_preload_injection_blocked1(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not included in the command assert "--env" in docker_cmd # Ensure there is an env var @@ -278,7 +281,7 @@ def test_ld_preload_in_extra_hosts1(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the extra hosts in the docker command assert "--add-host" in docker_cmd # Ensure extra hosts are included @@ -299,7 +302,7 @@ def test_ld_preload_in_ports1(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the port mappings in the docker command assert "-p" in docker_cmd # Ensure port mappings are included @@ -323,7 +326,7 @@ def test_ld_preload_in_mpc_account_id(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the extra hosts in the docker command assert "--add-host" in docker_cmd # Ensure extra hosts are included @@ -345,7 +348,7 @@ def test_ld_preload_injection_blocked2(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") assert ( "-e LD_PRELOAD" not in docker_cmd @@ -364,7 +367,7 @@ def test_ld_preload_in_extra_hosts2(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the extra hosts in the docker command assert "--add-host" in docker_cmd # Ensure extra hosts are included @@ -380,7 +383,7 @@ def test_ld_preload_in_ports2(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the port mappings in the docker command assert "-p" in docker_cmd # Ensure port mappings are included @@ -496,3 +499,271 @@ def test_validate_image_hash(): RPC_MAX_ATTEMPTS, ) assert result is True, "validate_image_hash() failed for test image" + + +# test launcher support for non TEE images. + + +class DummyProc: + def __init__(self, returncode=0, stdout=b"", stderr=b""): + self.returncode = returncode + self.stdout = stdout + self.stderr = stderr + + +@pytest.fixture +def base_env(monkeypatch): + # Required by launcher + monkeypatch.setenv(launcher.OS_ENV_DOCKER_CONTENT_TRUST, "1") + monkeypatch.setenv(launcher.ENV_VAR_DEFAULT_IMAGE_DIGEST, "sha256:" + "a" * 64) + + +def test_parse_platform_missing(monkeypatch, base_env): + monkeypatch.delenv(launcher.ENV_VAR_PLATFORM, raising=False) + with pytest.raises(RuntimeError): + launcher.parse_platform() + + +@pytest.mark.parametrize("val", ["", "foo", "TEE as", "NON_TEE", "1"]) +def test_parse_platform_invalid(monkeypatch, base_env, val): + monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, val) + with pytest.raises(RuntimeError): + launcher.parse_platform() + + +@pytest.mark.parametrize( + "val,expected", + [("tee", "TEE"), ("TEE", "TEE"), ("nontee", "NONTEE"), ("NONTEE", "NONTEE")], +) +def test_parse_platform_valid(monkeypatch, base_env, val, expected): + monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, val) + assert launcher.parse_platform() == expected + + +def test_extend_rtmr3_nontee_skips_dstack(monkeypatch, base_env): + called = {"count": 0} + + def fake_curl(*args, **kwargs): + called["count"] += 1 + return DummyProc(returncode=0) + + monkeypatch.setattr(launcher, "curl_unix_socket_post", fake_curl) + + launcher.extend_rtmr3(launcher.PLATFORM_NONTEE, "sha256:" + "b" * 64) + assert called["count"] == 0 + + +def test_extend_rtmr3_tee_requires_socket(monkeypatch, base_env): + monkeypatch.setattr(launcher, "is_unix_socket", lambda p: False) + with pytest.raises(RuntimeError): + launcher.extend_rtmr3(launcher.PLATFORM_TEE, "sha256:" + "b" * 64) + + +def test_extend_rtmr3_tee_getquote_fail(monkeypatch, base_env): + monkeypatch.setattr(launcher, "is_unix_socket", lambda p: True) + + def fake_curl(endpoint, payload, capture_output=False): + # Fail only GetQuote + if endpoint == "GetQuote": + return DummyProc(returncode=7) + return DummyProc(returncode=0) + + monkeypatch.setattr(launcher, "curl_unix_socket_post", fake_curl) + with pytest.raises(RuntimeError, match="GetQuote failed"): + launcher.extend_rtmr3(launcher.PLATFORM_TEE, "sha256:" + "b" * 64) + + +def test_extend_rtmr3_tee_emitevent_fail(monkeypatch, base_env): + monkeypatch.setattr(launcher, "is_unix_socket", lambda p: True) + + def fake_curl(endpoint, payload, capture_output=False): + if endpoint == "GetQuote": + return DummyProc(returncode=0) + if endpoint == "EmitEvent": + return DummyProc(returncode=55) + return DummyProc(returncode=0) + + monkeypatch.setattr(launcher, "curl_unix_socket_post", fake_curl) + with pytest.raises(RuntimeError, match="EmitEvent failed"): + launcher.extend_rtmr3(launcher.PLATFORM_TEE, "sha256:" + "b" * 64) + + +def test_build_docker_cmd_nontee_no_dstack_mount(base_env): + env = { + "MPC_ACCOUNT_ID": "x", + # launcher-only env should be ignored + launcher.ENV_VAR_RPC_MAX_ATTEMPTS: "5", + } + cmd = launcher.build_docker_cmd(launcher.PLATFORM_NONTEE, env, "sha256:" + "c" * 64) + s = " ".join(cmd) + + assert "MPC_TEE_MODE=0" in s + assert "DSTACK_ENDPOINT=" not in s + assert f"{launcher.DSTACK_UNIX_SOCKET}:{launcher.DSTACK_UNIX_SOCKET}" not in s + + +def test_build_docker_cmd_tee_has_dstack_mount(base_env): + env = {"MPC_ACCOUNT_ID": "x"} + cmd = launcher.build_docker_cmd(launcher.PLATFORM_TEE, env, "sha256:" + "c" * 64) + s = " ".join(cmd) + + assert "MPC_TEE_MODE=1" in s + assert f"DSTACK_ENDPOINT={launcher.DSTACK_UNIX_SOCKET}" in s + assert f"{launcher.DSTACK_UNIX_SOCKET}:{launcher.DSTACK_UNIX_SOCKET}" in s + + +def test_main_tee_fails_closed_before_launch(monkeypatch, base_env): + monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, "TEE") + monkeypatch.setattr(launcher, "is_unix_socket", lambda p: False) + + # prevent any real docker/network + monkeypatch.setattr( + launcher, "load_and_select_hash", lambda cfg: "sha256:" + "d" * 64 + ) + monkeypatch.setattr(launcher, "validate_image_hash", lambda *a, **k: True) + monkeypatch.setattr( + launcher, + "launch_mpc_container", + lambda *a, **k: (_ for _ in ()).throw(AssertionError("should not launch")), + ) + + with pytest.raises(RuntimeError, match="requires dstack unix socket"): + launcher.main() + + +def test_main_nontee_skips_extend_and_launches(monkeypatch, base_env): + monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, "NONTEE") + monkeypatch.setattr( + launcher, "is_unix_socket", lambda p: False + ) # should not matter + + monkeypatch.setattr( + launcher, "load_and_select_hash", lambda cfg: "sha256:" + "d" * 64 + ) + monkeypatch.setattr(launcher, "validate_image_hash", lambda *a, **k: True) + + called = {"extend": 0, "launch": 0} + monkeypatch.setattr( + launcher, + "extend_rtmr3", + lambda platform, h: called.__setitem__("extend", called["extend"] + 1), + ) + monkeypatch.setattr( + launcher, + "launch_mpc_container", + lambda platform, h, cfg: called.__setitem__("launch", called["launch"] + 1), + ) + + launcher.main() + assert called["extend"] == 1 + assert called["launch"] == 1 + + +def test_main_nontee_builds_expected_mpc_docker_cmd(monkeypatch, tmp_path): + """ + Verify that launcher.main() builds the correct MPC docker command in NONTEE mode. + + Steps: + 1. Configure the launcher to run with PLATFORM=NONTEE. + 2. Set required environment variables (DOCKER_CONTENT_TRUST, DEFAULT_IMAGE_DIGEST). + 3. Create a temporary user_config file with MPC env vars, ports, and extra hosts. + 4. Simulate a missing IMAGE_DIGEST_FILE so the launcher falls back to DEFAULT_IMAGE_DIGEST. + 5. Stub image validation and docker interactions to avoid real network or docker usage. + 6. Invoke launcher.main(). + 7. Capture the docker run command used to start the MPC container. + 8. Assert that the command: + - Includes expected MPC configuration (env vars, ports, hosts, volumes). + - Sets MPC_TEE_MODE=0. + - Does NOT include dstack socket mounts or DSTACK_ENDPOINT. + - Filters out injection attempts in ports and hosts. + - Uses the expected full image digest. + """ + # --- Arrange: environment (NONTEE) --- + monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, launcher.PLATFORM_NONTEE) + monkeypatch.setenv(launcher.OS_ENV_DOCKER_CONTENT_TRUST, "1") + + default_digest = "sha256:" + "a" * 64 + monkeypatch.setenv(launcher.ENV_VAR_DEFAULT_IMAGE_DIGEST, default_digest) + + # Provide a temp user config file so main() passes env into build_docker_cmd() + user_config = tmp_path / "user_config" + user_config.write_text( + "\n".join( + [ + "MPC_ACCOUNT_ID=mpc-user-123", + "PORTS=11780:11780,--env BAD=1", # BAD should be ignored + "EXTRA_HOSTS=node:192.168.1.1,--volume /:/mnt", # injection should be ignored + ] + ) + + "\n" + ) + + # Point launcher at our temp config + monkeypatch.setattr(launcher, "DSTACK_USER_CONFIG_FILE", str(user_config)) + + # Make IMAGE_DIGEST_FILE "missing" so DEFAULT_IMAGE_DIGEST is used + def fake_isfile(path: str) -> bool: + if path == launcher.IMAGE_DIGEST_FILE: + return False + if path == str(user_config): + return True + return os.path.isfile(path) + + monkeypatch.setattr(launcher.os.path, "isfile", fake_isfile) + + # Avoid network/docker verification in validate_image_hash + monkeypatch.setattr(launcher, "validate_image_hash", lambda *a, **k: True) + + # Avoid remove_existing_container touching real docker + monkeypatch.setattr(launcher, "check_output", lambda *a, **k: "") + + # Capture the docker run command used to launch MPC + captured = {"docker_run_cmd": None} + + def fake_run(cmd, *args, **kwargs): + # cmd is a list[str] + if ( + isinstance(cmd, list) + and len(cmd) >= 2 + and cmd[0] == "docker" + and cmd[1] == "run" + ): + captured["docker_run_cmd"] = cmd + return DummyProc(returncode=0) + return DummyProc(returncode=0) + + monkeypatch.setattr(launcher, "run", fake_run) + + # --- Act --- + launcher.main() + + # --- Assert --- + cmd = captured["docker_run_cmd"] + assert cmd is not None, "Expected launcher to invoke 'docker run' for MPC container" + + cmd_str = " ".join(cmd) + + # NONTEE invariants + assert "MPC_TEE_MODE=0" in cmd_str + assert "DSTACK_ENDPOINT=" not in cmd_str + assert f"{launcher.DSTACK_UNIX_SOCKET}:{launcher.DSTACK_UNIX_SOCKET}" not in cmd_str + + # Expected env propagation + sanitization + assert "MPC_ACCOUNT_ID=mpc-user-123" in cmd_str + assert "-p" in cmd and "11780:11780" in cmd_str + assert "--add-host" in cmd and "node:192.168.1.1" in cmd_str + + # Injection strings filtered out + assert "BAD=1" not in cmd_str + assert "/:/mnt" not in cmd_str + + # Required mounts / flags from build_docker_cmd + assert "--security-opt" in cmd_str + assert "no-new-privileges:true" in cmd_str + assert "/tapp:/tapp:ro" in cmd_str + assert "shared-volume:/mnt/shared" in cmd_str + assert "mpc-data:/data" in cmd_str + assert f"--name {launcher.MPC_CONTAINER_NAME}" in cmd_str + + # Image digest should be the final argument and should be the FULL digest + assert cmd[-1] == default_digest From d8985b4edc5cb798b48c508800e176ad519a4f56 Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Mon, 12 Jan 2026 11:51:17 +0000 Subject: [PATCH 03/11] update yamls --- tee_launcher/launcher_docker_compose.yaml | 1 + .../launcher_docker_compose_nontee.yaml | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tee_launcher/launcher_docker_compose_nontee.yaml diff --git a/tee_launcher/launcher_docker_compose.yaml b/tee_launcher/launcher_docker_compose.yaml index b055040a58..d5db93f7e7 100644 --- a/tee_launcher/launcher_docker_compose.yaml +++ b/tee_launcher/launcher_docker_compose.yaml @@ -7,6 +7,7 @@ services: container_name: launcher environment: + - PLATFORM=TEE - DOCKER_CONTENT_TRUST=1 - DEFAULT_IMAGE_DIGEST=sha256:00006c1059cc0219005b21956a4df8238b0cc33ad559a578a63169de4e28c81e diff --git a/tee_launcher/launcher_docker_compose_nontee.yaml b/tee_launcher/launcher_docker_compose_nontee.yaml new file mode 100644 index 0000000000..5286240272 --- /dev/null +++ b/tee_launcher/launcher_docker_compose_nontee.yaml @@ -0,0 +1,30 @@ +version: '3.8' + +services: + launcher: + image: nearone/mpc-launcher@sha256:72f16f1d1ca9119f5c19cb3ec7f6338949e2ad3082bd01ba2099c6649e845f61 + container_name: launcher + + environment: + - PLATFORM=NONTEE + - DOCKER_CONTENT_TRUST=1 + - DEFAULT_IMAGE_DIGEST=sha256:00006c1059cc0219005b21956a4df8238b0cc33ad559a578a63169de4e28c81e + + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./user-config.conf:/tapp/user_config:ro + - shared-volume:/mnt/shared + - mpc-data:/data + + security_opt: + - no-new-privileges:true + + read_only: true + tmpfs: + - /tmp + +volumes: + shared-volume: + name: shared-volume + mpc-data: + name: mpc-data From c7d8a89c0f6b39432d56536addb18795545cdf0d Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Tue, 13 Jan 2026 08:35:17 +0000 Subject: [PATCH 04/11] doc --- .../using_the_launcher_in_nontee_setup.md | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tee_launcher/using_the_launcher_in_nontee_setup.md diff --git a/tee_launcher/using_the_launcher_in_nontee_setup.md b/tee_launcher/using_the_launcher_in_nontee_setup.md new file mode 100644 index 0000000000..a1c87ecc3a --- /dev/null +++ b/tee_launcher/using_the_launcher_in_nontee_setup.md @@ -0,0 +1,148 @@ +# Running the MPC Launcher in Non-TEE Mode + +This guide describes the **high-level steps** required to run the MPC launcher **outside of a TEE**, using the same launcher and MPC Docker images as in TEE deployments. + +The goal is to allow developers and operators to exercise the **real production launcher flow** (image selection, validation, container launch, upgrades) **without relying on TEE / dstack infrastructure**. + +--- + +## Overview + +In non-TEE mode: +- The launcher runs with `PLATFORM=NONTEE` +- No dstack socket or attestation is used +- RTMR extensions are skipped +- The MPC node is launched with `MPC_TEE_MODE=0` +- Image hash validation and upgrade logic remain unchanged + +This provides maximum parity with production while keeping the setup simple. + +--- + +## Prerequisites + +- Docker installed and running +- Docker Compose (classic `docker-compose` or plugin `docker compose`) +- Network access to pull Docker images +- A valid MPC Docker image digest + +--- + +## Files Used + +- **Launcher Docker Compose (non-TEE)** + Example: + `./launcher_docker_compose_nontee.yaml` + +- **User configuration file** + Example: + `./user-config.conf` (mounted into the launcher container) + +--- + +## Step-by-Step Instructions + +### 1. Prepare the non-TEE docker-compose file + +Create or use a non-TEE launcher compose file with the following properties: + +- Set `PLATFORM=NONTEE` +- Mount `/var/run/docker.sock` +- Do **not** mount `/var/run/dstack.sock` +- Mount the user config file at `/tapp/user_config` +- Provide persistent volumes for shared state and MPC data + +See example: +``` +./tee_launcher/launcher_docker_compose_nontee.yaml +``` + +--- + +### 2. Prepare the user configuration file + +Create a `user-config.conf` file containing: +- MPC image selection parameters (`MPC_IMAGE_NAME`, `MPC_IMAGE_TAGS`, `MPC_REGISTRY`) +- MPC runtime configuration (account ID, contract ID, network, logging, etc.) +- Optional port mappings using `PORTS=` + +This file is read by the launcher and passed (safely) into the MPC container. + +--- + +### 3. Start the launcher + +From the directory containing the non-TEE compose file: + +```bash +docker-compose -f launcher_docker_compose_nontee.yaml up -d +``` + +--- + +### 4. Monitor launcher logs + +```bash +docker logs -f launcher +``` + +You should see: +- `Launcher platform: NONTEE` +- Image hash selection and validation +- `PLATFORM=NONTEE → skipping RTMR3 extension` +- A `docker run ...` command launching `mpc-node` + +--- + +### 5. Verify the MPC container + +Check that the MPC container is running: + +```bash +docker ps +``` + +Inspect the MPC container to confirm: +- `MPC_TEE_MODE=0` is set +- No `DSTACK_ENDPOINT` environment variable exists +- No dstack socket is mounted +- Expected ports are published + +Example: +```bash +docker inspect mpc-node +``` + +--- + + + +--- + +## Stopping and Restarting + +Stop and remove containers: +```bash +docker-compose -f launcher_docker_compose_nontee.yaml down +``` + +Stop and remove containers **and volumes** (full reset): +```bash +docker-compose -f launcher_docker_compose_nontee.yaml down -v +``` + +Restart: +```bash +docker-compose -f launcher_docker_compose_nontee.yaml up -d +``` + +--- + +## Notes + +- Non-TEE mode is intended for **testing, development, and debugging** +- Security-sensitive behavior for TEE deployments remains unchanged +- The same launcher image and MPC image hashes are used in both modes + +--- + From 4ae540c9555d84708279a47021dc934322ddc29b Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Tue, 13 Jan 2026 08:44:13 +0000 Subject: [PATCH 05/11] update launcher template --- crates/contract/assets/launcher_docker_compose.yaml.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/contract/assets/launcher_docker_compose.yaml.template b/crates/contract/assets/launcher_docker_compose.yaml.template index 0e0569eb1f..f8099904de 100644 --- a/crates/contract/assets/launcher_docker_compose.yaml.template +++ b/crates/contract/assets/launcher_docker_compose.yaml.template @@ -2,11 +2,12 @@ version: '3.8' services: launcher: - image: nearone/mpc-launcher@sha256:4065f2fce41415962be92471a4e793ff5147b00b2784617c7e8098be2761a875 + image: nearone/mpc-launcher@sha256:72f16f1d1ca9119f5c19cb3ec7f6338949e2ad3082bd01ba2099c6649e845f61 container_name: launcher environment: + - PLATFORM=TEE - DOCKER_CONTENT_TRUST=1 - DEFAULT_IMAGE_DIGEST=sha256:{{DEFAULT_IMAGE_DIGEST_HASH}} From 40f6ae223d0e359592304fcd80081a095a0b1a55 Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Wed, 14 Jan 2026 09:58:45 +0000 Subject: [PATCH 06/11] code review fixes --- tee_launcher/launcher.py | 46 ++++++++++---------- tee_launcher/test_launcher_config.py | 65 ++++++++++++++-------------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/tee_launcher/launcher.py b/tee_launcher/launcher.py index 2f9a957004..96a6f8eadc 100644 --- a/tee_launcher/launcher.py +++ b/tee_launcher/launcher.py @@ -3,6 +3,7 @@ import os import stat from typing import Dict, Union +from enum import Enum import requests from subprocess import CompletedProcess, run, check_output import sys @@ -40,9 +41,14 @@ class RpcTimingConfig(NamedTuple): # # Platform mode (MUST come from measured docker-compose env in TEE deployments) # -ENV_VAR_PLATFORM = "PLATFORM" # expected values: "TEE" or "NONTEE" -PLATFORM_TEE = "TEE" -PLATFORM_NONTEE = "NONTEE" +ENV_VAR_PLATFORM = "PLATFORM" + + +# do not change the string values - they are used in docker-compose files. +class Platform(Enum): + TEE = "TEE" + NONTEE = "NONTEE" + # only considered if `IMAGE_DIGEST_FILE` does not exist. ENV_VAR_DEFAULT_IMAGE_DIGEST = "DEFAULT_IMAGE_DIGEST" @@ -288,7 +294,7 @@ def is_unix_socket(path: str) -> bool: return False -def parse_platform() -> str: +def parse_platform() -> Platform: """ Platform selection MUST be a measured input in TEE deployments. Therefore, we only read it from process env (docker-compose 'environment'), @@ -297,14 +303,16 @@ def parse_platform() -> str: raw = os.environ.get(ENV_VAR_PLATFORM) if raw is None: raise RuntimeError( - f"{ENV_VAR_PLATFORM} must be set to {PLATFORM_TEE} or {PLATFORM_NONTEE}." + f"{ENV_VAR_PLATFORM} must be set to one of {[p.value for p in Platform]}" ) + val = raw.strip().upper() - if val not in (PLATFORM_TEE, PLATFORM_NONTEE): + try: + return Platform(val) + except ValueError: raise RuntimeError( - f"Invalid {ENV_VAR_PLATFORM}={raw!r}. Expected {PLATFORM_TEE} or {PLATFORM_NONTEE}." + f"Invalid {ENV_VAR_PLATFORM}={raw!r}. Expected one of {[p.value for p in Platform]}" ) - return val def load_rpc_timing_config(dstack_config: dict[str, str]) -> RpcTimingConfig: @@ -477,8 +485,8 @@ def validate_image_hash( return False -def extend_rtmr3(platform: str, valid_hash: str) -> None: - if platform == PLATFORM_NONTEE: +def extend_rtmr3(platform: Platform, valid_hash: str) -> None: + if platform == Platform.NONTEE: logging.info("PLATFORM=NONTEE → skipping RTMR3 extension step.") return @@ -506,7 +514,7 @@ def extend_rtmr3(platform: str, valid_hash: str) -> None: raise RuntimeError("EmitEvent failed while extending RTMR3") -def launch_mpc_container(platform: str, valid_hash: str, user_env: dict) -> None: +def launch_mpc_container(platform: Platform, valid_hash: str, user_env: dict) -> None: logging.info(f"Launching MPC node with validated hash: {valid_hash}") remove_existing_container() @@ -557,9 +565,9 @@ def main(): logging.info("start") platform = parse_platform() - logging.info(f"Launcher platform: {platform}") + logging.info(f"Launcher platform: {platform.value}") - if platform == PLATFORM_TEE and not is_unix_socket(DSTACK_UNIX_SOCKET): + if platform is Platform.TEE and not is_unix_socket(DSTACK_UNIX_SOCKET): raise RuntimeError( f"PLATFORM=TEE requires dstack unix socket at {DSTACK_UNIX_SOCKET}." ) @@ -740,7 +748,7 @@ def get_bare_digest(full_digest: str) -> str: def build_docker_cmd( - platform: str, user_env: dict[str, str], image_digest: str + platform: Platform, user_env: dict[str, str], image_digest: str ) -> list[str]: bare_digest = get_bare_digest(image_digest) @@ -750,11 +758,9 @@ def build_docker_cmd( docker_cmd += ["--env", f"MPC_IMAGE_HASH={bare_digest}"] docker_cmd += ["--env", f"MPC_LATEST_ALLOWED_HASH_FILE={IMAGE_DIGEST_FILE}"] - if platform == PLATFORM_TEE: - docker_cmd += ["--env", "MPC_TEE_MODE=1"] + if platform is Platform.TEE: docker_cmd += ["--env", f"DSTACK_ENDPOINT={DSTACK_UNIX_SOCKET}"] - else: - docker_cmd += ["--env", "MPC_TEE_MODE=0"] + docker_cmd += ["-v", f"{DSTACK_UNIX_SOCKET}:{DSTACK_UNIX_SOCKET}"] # MPC env vars and extra config for key, value in user_env.items(): @@ -807,10 +813,6 @@ def build_docker_cmd( image_digest, # IMPORTANT: Docker must get the FULL digest ] - # Mount dstack socket ONLY in TEE mode. - if platform == PLATFORM_TEE: - docker_cmd += ["-v", f"{DSTACK_UNIX_SOCKET}:{DSTACK_UNIX_SOCKET}"] - logging.info("docker cmd %s", " ".join(docker_cmd)) # Final LD_PRELOAD safeguard diff --git a/tee_launcher/test_launcher_config.py b/tee_launcher/test_launcher_config.py index 1509918c5e..2a82e7447e 100644 --- a/tee_launcher/test_launcher_config.py +++ b/tee_launcher/test_launcher_config.py @@ -21,9 +21,6 @@ ENV_VAR_DEFAULT_IMAGE_DIGEST, ) -TEE = launcher.PLATFORM_TEE -NONTEE = launcher.PLATFORM_NONTEE - def make_digest_json(hashes): return json.dumps({JSON_KEY_APPROVED_HASHES: hashes}) @@ -127,7 +124,7 @@ def test_build_docker_cmd_sanitizes_ports_and_hosts(): "EXTRA_HOSTS": "node:192.168.1.1,--volume /:/mnt", "MPC_ACCOUNT_ID": "mpc-user-123", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") func_name = inspect.currentframe().f_code.co_name print(f"[{func_name}] CMD:", " ".join(cmd)) @@ -149,7 +146,7 @@ def test_extra_hosts_does_not_allow_ld_preload(): "EXTRA_HOSTS": "host:1.2.3.4,--env LD_PRELOAD=/evil.so", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") assert "host:1.2.3.4" in cmd assert not any("LD_PRELOAD" in arg for arg in cmd) @@ -160,7 +157,7 @@ def test_ports_does_not_allow_volume_injection(): "PORTS": "2200:2200,--volume /:/mnt", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") assert "2200:2200" in cmd assert not any("/:/mnt" in arg for arg in cmd) @@ -171,7 +168,7 @@ def test_invalid_env_key_is_ignored(): "BAD_KEY": "should_not_be_used", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") assert "should_not_be_used" not in " ".join(cmd) assert "MPC_ACCOUNT_ID=safe" in cmd @@ -181,7 +178,7 @@ def test_protocol_upgrade_override_is_allowed(): env = { "NEAR_TESTS_PROTOCOL_UPGRADE_OVERRIDE": "now", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") assert "NEAR_TESTS_PROTOCOL_UPGRADE_OVERRIDE=now" in " ".join(cmd) @@ -190,7 +187,7 @@ def test_mpc_backup_encryption_key_is_allowed(): env = { "MPC_BACKUP_ENCRYPTION_KEY_HEX": "0000000000000000000000000000000000000000000000000000000000000000", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") assert ( "MPC_BACKUP_ENCRYPTION_KEY_HEX=0000000000000000000000000000000000000000000000000000000000000000" @@ -203,7 +200,7 @@ def test_malformed_extra_host_is_ignored(): "EXTRA_HOSTS": "badhostentry,no-colon,also--bad", "MPC_ACCOUNT_ID": "safe", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") assert "--add-host" not in cmd # All malformed entries should be skipped @@ -212,7 +209,7 @@ def test_env_value_with_shell_injection_is_handled_safely(): env = { "MPC_ACCOUNT_ID": "safe; rm -rf /", } - cmd = build_docker_cmd(TEE, env, "sha256:abc123") + cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") assert "--env" in cmd assert "MPC_ACCOUNT_ID=safe; rm -rf /" in cmd @@ -230,7 +227,7 @@ def test_parse_and_build_docker_cmd_full_flow(): env = parse_env_string(config_str) image_hash = env.get("IMAGE_HASH", "sha256:default") - cmd = build_docker_cmd(TEE, env, image_hash) + cmd = build_docker_cmd(launcher.Platform.TEE, env, image_hash) print(f"[{inspect.currentframe().f_code.co_name}] CMD: {' '.join(cmd)}") @@ -255,7 +252,7 @@ def test_ld_preload_injection_blocked1(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(launcher.Platform.TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not included in the command assert "--env" in docker_cmd # Ensure there is an env var @@ -281,7 +278,7 @@ def test_ld_preload_in_extra_hosts1(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(launcher.Platform.TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the extra hosts in the docker command assert "--add-host" in docker_cmd # Ensure extra hosts are included @@ -302,7 +299,7 @@ def test_ld_preload_in_ports1(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(launcher.Platform.TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the port mappings in the docker command assert "-p" in docker_cmd # Ensure port mappings are included @@ -326,7 +323,7 @@ def test_ld_preload_in_mpc_account_id(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(launcher.Platform.TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the extra hosts in the docker command assert "--add-host" in docker_cmd # Ensure extra hosts are included @@ -348,7 +345,7 @@ def test_ld_preload_injection_blocked2(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(launcher.Platform.TEE, malicious_env, "sha256:abc123") assert ( "-e LD_PRELOAD" not in docker_cmd @@ -367,7 +364,7 @@ def test_ld_preload_in_extra_hosts2(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(launcher.Platform.TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the extra hosts in the docker command assert "--add-host" in docker_cmd # Ensure extra hosts are included @@ -383,7 +380,7 @@ def test_ld_preload_in_ports2(): } # Call build_docker_cmd to generate the docker command - docker_cmd = build_docker_cmd(TEE, malicious_env, "sha256:abc123") + docker_cmd = build_docker_cmd(launcher.Platform.TEE, malicious_env, "sha256:abc123") # Check that LD_PRELOAD is not part of the port mappings in the docker command assert "-p" in docker_cmd # Ensure port mappings are included @@ -533,11 +530,16 @@ def test_parse_platform_invalid(monkeypatch, base_env, val): @pytest.mark.parametrize( "val,expected", - [("tee", "TEE"), ("TEE", "TEE"), ("nontee", "NONTEE"), ("NONTEE", "NONTEE")], + [ + ("tee", launcher.Platform.TEE), + ("TEE", launcher.Platform.TEE), + ("nontee", launcher.Platform.NONTEE), + ("NONTEE", launcher.Platform.NONTEE), + ], ) def test_parse_platform_valid(monkeypatch, base_env, val, expected): monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, val) - assert launcher.parse_platform() == expected + assert launcher.parse_platform() is expected def test_extend_rtmr3_nontee_skips_dstack(monkeypatch, base_env): @@ -549,14 +551,14 @@ def fake_curl(*args, **kwargs): monkeypatch.setattr(launcher, "curl_unix_socket_post", fake_curl) - launcher.extend_rtmr3(launcher.PLATFORM_NONTEE, "sha256:" + "b" * 64) + launcher.extend_rtmr3(launcher.Platform.NONTEE, "sha256:" + "b" * 64) assert called["count"] == 0 def test_extend_rtmr3_tee_requires_socket(monkeypatch, base_env): monkeypatch.setattr(launcher, "is_unix_socket", lambda p: False) with pytest.raises(RuntimeError): - launcher.extend_rtmr3(launcher.PLATFORM_TEE, "sha256:" + "b" * 64) + launcher.extend_rtmr3(launcher.Platform.TEE, "sha256:" + "b" * 64) def test_extend_rtmr3_tee_getquote_fail(monkeypatch, base_env): @@ -570,7 +572,7 @@ def fake_curl(endpoint, payload, capture_output=False): monkeypatch.setattr(launcher, "curl_unix_socket_post", fake_curl) with pytest.raises(RuntimeError, match="GetQuote failed"): - launcher.extend_rtmr3(launcher.PLATFORM_TEE, "sha256:" + "b" * 64) + launcher.extend_rtmr3(launcher.Platform.TEE, "sha256:" + "b" * 64) def test_extend_rtmr3_tee_emitevent_fail(monkeypatch, base_env): @@ -585,7 +587,7 @@ def fake_curl(endpoint, payload, capture_output=False): monkeypatch.setattr(launcher, "curl_unix_socket_post", fake_curl) with pytest.raises(RuntimeError, match="EmitEvent failed"): - launcher.extend_rtmr3(launcher.PLATFORM_TEE, "sha256:" + "b" * 64) + launcher.extend_rtmr3(launcher.Platform.TEE, "sha256:" + "b" * 64) def test_build_docker_cmd_nontee_no_dstack_mount(base_env): @@ -594,26 +596,25 @@ def test_build_docker_cmd_nontee_no_dstack_mount(base_env): # launcher-only env should be ignored launcher.ENV_VAR_RPC_MAX_ATTEMPTS: "5", } - cmd = launcher.build_docker_cmd(launcher.PLATFORM_NONTEE, env, "sha256:" + "c" * 64) + cmd = launcher.build_docker_cmd(launcher.Platform.NONTEE, env, "sha256:" + "c" * 64) s = " ".join(cmd) - assert "MPC_TEE_MODE=0" in s assert "DSTACK_ENDPOINT=" not in s assert f"{launcher.DSTACK_UNIX_SOCKET}:{launcher.DSTACK_UNIX_SOCKET}" not in s def test_build_docker_cmd_tee_has_dstack_mount(base_env): env = {"MPC_ACCOUNT_ID": "x"} - cmd = launcher.build_docker_cmd(launcher.PLATFORM_TEE, env, "sha256:" + "c" * 64) + cmd = launcher.build_docker_cmd(launcher.Platform.TEE, env, "sha256:" + "c" * 64) s = " ".join(cmd) - assert "MPC_TEE_MODE=1" in s assert f"DSTACK_ENDPOINT={launcher.DSTACK_UNIX_SOCKET}" in s assert f"{launcher.DSTACK_UNIX_SOCKET}:{launcher.DSTACK_UNIX_SOCKET}" in s def test_main_tee_fails_closed_before_launch(monkeypatch, base_env): - monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, "TEE") + monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, launcher.Platform.TEE.value) + monkeypatch.setattr(launcher, "is_unix_socket", lambda p: False) # prevent any real docker/network @@ -673,13 +674,12 @@ def test_main_nontee_builds_expected_mpc_docker_cmd(monkeypatch, tmp_path): 7. Capture the docker run command used to start the MPC container. 8. Assert that the command: - Includes expected MPC configuration (env vars, ports, hosts, volumes). - - Sets MPC_TEE_MODE=0. - Does NOT include dstack socket mounts or DSTACK_ENDPOINT. - Filters out injection attempts in ports and hosts. - Uses the expected full image digest. """ # --- Arrange: environment (NONTEE) --- - monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, launcher.PLATFORM_NONTEE) + monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, launcher.Platform.NONTEE.value) monkeypatch.setenv(launcher.OS_ENV_DOCKER_CONTENT_TRUST, "1") default_digest = "sha256:" + "a" * 64 @@ -744,7 +744,6 @@ def fake_run(cmd, *args, **kwargs): cmd_str = " ".join(cmd) # NONTEE invariants - assert "MPC_TEE_MODE=0" in cmd_str assert "DSTACK_ENDPOINT=" not in cmd_str assert f"{launcher.DSTACK_UNIX_SOCKET}:{launcher.DSTACK_UNIX_SOCKET}" not in cmd_str From b4eedb4b3345fc333d9933bab6502bc8c17b47ae Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Wed, 14 Jan 2026 10:17:48 +0000 Subject: [PATCH 07/11] review fixes --- tee_launcher/launcher_docker_compose.yaml | 2 +- tee_launcher/test_launcher_config.py | 65 +++++++++++++++++------ 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/tee_launcher/launcher_docker_compose.yaml b/tee_launcher/launcher_docker_compose.yaml index d5db93f7e7..7a16d2db4d 100644 --- a/tee_launcher/launcher_docker_compose.yaml +++ b/tee_launcher/launcher_docker_compose.yaml @@ -2,7 +2,7 @@ version: '3.8' services: launcher: - image: nearone/mpc-launcher@sha256:4065f2fce41415962be92471a4e793ff5147b00b2784617c7e8098be2761a875 + image: nearone/mpc-launcher@sha256:72f16f1d1ca9119f5c19cb3ec7f6338949e2ad3082bd01ba2099c6649e845f61 container_name: launcher diff --git a/tee_launcher/test_launcher_config.py b/tee_launcher/test_launcher_config.py index 2a82e7447e..a58022468a 100644 --- a/tee_launcher/test_launcher_config.py +++ b/tee_launcher/test_launcher_config.py @@ -1,4 +1,5 @@ # test_launcher_config.py +import cmd import inspect import json import tempfile @@ -22,6 +23,15 @@ ) +# Test constants for user_config content +TEST_MPC_ACCOUNT_ID = "mpc-user-123" + +TEST_PORTS_WITH_INJECTION = "11780:11780,--env BAD=1" + +TEST_EXTRA_HOSTS_WITH_IP = "node:192.168.1.1" +TEST_EXTRA_HOSTS_WITH_INJECTION = f"{TEST_EXTRA_HOSTS_WITH_IP},--volume /:/mnt" + + def make_digest_json(hashes): return json.dumps({JSON_KEY_APPROVED_HASHES: hashes}) @@ -120,9 +130,9 @@ def test_valid_port_mapping(): def test_build_docker_cmd_sanitizes_ports_and_hosts(): env = { - "PORTS": "11780:11780,--env BAD=1", - "EXTRA_HOSTS": "node:192.168.1.1,--volume /:/mnt", - "MPC_ACCOUNT_ID": "mpc-user-123", + "PORTS": TEST_PORTS_WITH_INJECTION, + "EXTRA_HOSTS": TEST_EXTRA_HOSTS_WITH_INJECTION, + "MPC_ACCOUNT_ID": TEST_MPC_ACCOUNT_ID, } cmd = build_docker_cmd(launcher.Platform.TEE, env, "sha256:abc123") @@ -130,11 +140,11 @@ def test_build_docker_cmd_sanitizes_ports_and_hosts(): print(f"[{func_name}] CMD:", " ".join(cmd)) assert "--env" in cmd - assert "MPC_ACCOUNT_ID=mpc-user-123" in cmd + assert f"MPC_ACCOUNT_ID={TEST_MPC_ACCOUNT_ID}" in cmd assert "-p" in cmd assert "11780:11780" in cmd assert "--add-host" in cmd - assert "node:192.168.1.1" in cmd + assert TEST_EXTRA_HOSTS_WITH_IP in cmd # Make sure injection strings were filtered assert not any("BAD=1" in arg for arg in cmd) @@ -247,7 +257,7 @@ def test_parse_and_build_docker_cmd_full_flow(): def test_ld_preload_injection_blocked1(): # Set up the environment variable with a dangerous LD_PRELOAD value malicious_env = { - "MPC_ACCOUNT_ID": "mpc-user-123", + "MPC_ACCOUNT_ID": TEST_MPC_ACCOUNT_ID, "--env LD_PRELOAD": "/path/to/my/malloc.so", # The dangerous value } @@ -273,7 +283,7 @@ def test_ld_preload_injection_blocked1(): def test_ld_preload_in_extra_hosts1(): # Set up environment with malicious EXRA_HOSTS containing LD_PRELOAD malicious_env = { - "MPC_ACCOUNT_ID": "mpc-user-123", + "MPC_ACCOUNT_ID": TEST_MPC_ACCOUNT_ID, "EXTRA_HOSTS": "host1:192.168.0.1,host2:192.168.0.2,--env LD_PRELOAD=/path/to/my/malloc.so", } @@ -294,7 +304,7 @@ def test_ld_preload_in_extra_hosts1(): def test_ld_preload_in_ports1(): # Set up environment with malicious PORTS containing LD_PRELOAD malicious_env = { - "MPC_ACCOUNT_ID": "mpc-user-123", + "MPC_ACCOUNT_ID": TEST_MPC_ACCOUNT_ID, "PORTS": "11780:11780,--env LD_PRELOAD=/path/to/my/malloc.so", } @@ -318,7 +328,7 @@ def test_ld_preload_in_ports1(): def test_ld_preload_in_mpc_account_id(): # Set up environment with malicious EXRA_HOSTS containing LD_PRELOAD malicious_env = { - "MPC_ACCOUNT_ID": "mpc-user-123, --env LD_PRELOAD=/path/to/my/malloc.so", + "MPC_ACCOUNT_ID": f"{TEST_MPC_ACCOUNT_ID}, --env LD_PRELOAD=/path/to/my/malloc.so", "EXTRA_HOSTS": "host1:192.168.0.1,host2:192.168.0.2", } @@ -340,7 +350,7 @@ def test_ld_preload_in_mpc_account_id(): def test_ld_preload_injection_blocked2(): # Set up the environment variable with a dangerous LD_PRELOAD value malicious_env = { - "MPC_ACCOUNT_ID": "mpc-user-123", + "MPC_ACCOUNT_ID": TEST_MPC_ACCOUNT_ID, "-e LD_PRELOAD": "/path/to/my/malloc.so", # The dangerous value } @@ -359,7 +369,7 @@ def test_ld_preload_injection_blocked2(): def test_ld_preload_in_extra_hosts2(): # Set up environment with malicious EXRA_HOSTS containing LD_PRELOAD malicious_env = { - "MPC_ACCOUNT_ID": "mpc-user-123", + "MPC_ACCOUNT_ID": TEST_MPC_ACCOUNT_ID, "EXTRA_HOSTS": "host1:192.168.0.1,host2:192.168.0.2,-e LD_PRELOAD=/path/to/my/malloc.so", } @@ -375,7 +385,7 @@ def test_ld_preload_in_extra_hosts2(): def test_ld_preload_in_ports2(): # Set up environment with malicious PORTS containing LD_PRELOAD malicious_env = { - "MPC_ACCOUNT_ID": "mpc-user-123", + "MPC_ACCOUNT_ID": TEST_MPC_ACCOUNT_ID, "PORTS": "11780:11780,-e LD_PRELOAD=/path/to/my/malloc.so", } @@ -660,6 +670,16 @@ def test_main_nontee_skips_extend_and_launches(monkeypatch, base_env): assert called["launch"] == 1 +def assert_subsequence(seq, subseq): + it = iter(seq) + for x in subseq: + for y in it: + if y == x: + break + else: + raise AssertionError(f"Missing subsequence item: {x}\nseq={seq}") + + def test_main_nontee_builds_expected_mpc_docker_cmd(monkeypatch, tmp_path): """ Verify that launcher.main() builds the correct MPC docker command in NONTEE mode. @@ -690,9 +710,9 @@ def test_main_nontee_builds_expected_mpc_docker_cmd(monkeypatch, tmp_path): user_config.write_text( "\n".join( [ - "MPC_ACCOUNT_ID=mpc-user-123", - "PORTS=11780:11780,--env BAD=1", # BAD should be ignored - "EXTRA_HOSTS=node:192.168.1.1,--volume /:/mnt", # injection should be ignored + f"MPC_ACCOUNT_ID={TEST_MPC_ACCOUNT_ID}", + f"PORTS={TEST_PORTS_WITH_INJECTION}", # injection should be ignored + f"EXTRA_HOSTS={TEST_EXTRA_HOSTS_WITH_INJECTION}", # injection should be ignored ] ) + "\n" @@ -748,9 +768,9 @@ def fake_run(cmd, *args, **kwargs): assert f"{launcher.DSTACK_UNIX_SOCKET}:{launcher.DSTACK_UNIX_SOCKET}" not in cmd_str # Expected env propagation + sanitization - assert "MPC_ACCOUNT_ID=mpc-user-123" in cmd_str + assert f"MPC_ACCOUNT_ID={TEST_MPC_ACCOUNT_ID}" in cmd_str assert "-p" in cmd and "11780:11780" in cmd_str - assert "--add-host" in cmd and "node:192.168.1.1" in cmd_str + assert "--add-host" in cmd and TEST_EXTRA_HOSTS_WITH_IP in cmd_str # Injection strings filtered out assert "BAD=1" not in cmd_str @@ -766,3 +786,14 @@ def fake_run(cmd, *args, **kwargs): # Image digest should be the final argument and should be the FULL digest assert cmd[-1] == default_digest + + expected_core = [ + "docker", "run", + "--security-opt", "no-new-privileges:true", + "-v", "/tapp:/tapp:ro", + "-v", "shared-volume:/mnt/shared", + "-v", "mpc-data:/data", + "--name", launcher.MPC_CONTAINER_NAME, + "--detach", + ] + assert_subsequence(cmd, expected_core) From 3b79d9a722a19ff045fd5090454e3ba0ac82170a Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Wed, 14 Jan 2026 11:47:47 +0000 Subject: [PATCH 08/11] stricter inputs --- tee_launcher/launcher.py | 9 +++++---- tee_launcher/test_launcher_config.py | 26 +++++++++++++++----------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/tee_launcher/launcher.py b/tee_launcher/launcher.py index 96a6f8eadc..418b76ed7b 100644 --- a/tee_launcher/launcher.py +++ b/tee_launcher/launcher.py @@ -306,13 +306,14 @@ def parse_platform() -> Platform: f"{ENV_VAR_PLATFORM} must be set to one of {[p.value for p in Platform]}" ) - val = raw.strip().upper() + val = raw.strip() try: return Platform(val) - except ValueError: + except ValueError as e: + allowed = ", ".join(p.value for p in Platform) raise RuntimeError( - f"Invalid {ENV_VAR_PLATFORM}={raw!r}. Expected one of {[p.value for p in Platform]}" - ) + f"Invalid {ENV_VAR_PLATFORM}={raw!r}. Expected one of: {allowed}" + ) from e def load_rpc_timing_config(dstack_config: dict[str, str]) -> RpcTimingConfig: diff --git a/tee_launcher/test_launcher_config.py b/tee_launcher/test_launcher_config.py index a58022468a..3184b6e165 100644 --- a/tee_launcher/test_launcher_config.py +++ b/tee_launcher/test_launcher_config.py @@ -531,7 +531,7 @@ def test_parse_platform_missing(monkeypatch, base_env): launcher.parse_platform() -@pytest.mark.parametrize("val", ["", "foo", "TEE as", "NON_TEE", "1"]) +@pytest.mark.parametrize("val", ["", "foo", "TEE as", "NON_TEE", "1", "tee", "nontee"]) def test_parse_platform_invalid(monkeypatch, base_env, val): monkeypatch.setenv(launcher.ENV_VAR_PLATFORM, val) with pytest.raises(RuntimeError): @@ -541,9 +541,7 @@ def test_parse_platform_invalid(monkeypatch, base_env, val): @pytest.mark.parametrize( "val,expected", [ - ("tee", launcher.Platform.TEE), ("TEE", launcher.Platform.TEE), - ("nontee", launcher.Platform.NONTEE), ("NONTEE", launcher.Platform.NONTEE), ], ) @@ -678,7 +676,7 @@ def assert_subsequence(seq, subseq): break else: raise AssertionError(f"Missing subsequence item: {x}\nseq={seq}") - + def test_main_nontee_builds_expected_mpc_docker_cmd(monkeypatch, tmp_path): """ @@ -788,12 +786,18 @@ def fake_run(cmd, *args, **kwargs): assert cmd[-1] == default_digest expected_core = [ - "docker", "run", - "--security-opt", "no-new-privileges:true", - "-v", "/tapp:/tapp:ro", - "-v", "shared-volume:/mnt/shared", - "-v", "mpc-data:/data", - "--name", launcher.MPC_CONTAINER_NAME, - "--detach", + "docker", + "run", + "--security-opt", + "no-new-privileges:true", + "-v", + "/tapp:/tapp:ro", + "-v", + "shared-volume:/mnt/shared", + "-v", + "mpc-data:/data", + "--name", + launcher.MPC_CONTAINER_NAME, + "--detach", ] assert_subsequence(cmd, expected_core) From 68faa7f48234756b55647b0d0352ab388cd95163 Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Wed, 14 Jan 2026 11:59:43 +0000 Subject: [PATCH 09/11] ruff --- tee_launcher/test_launcher_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tee_launcher/test_launcher_config.py b/tee_launcher/test_launcher_config.py index 3184b6e165..a211090ee6 100644 --- a/tee_launcher/test_launcher_config.py +++ b/tee_launcher/test_launcher_config.py @@ -1,5 +1,5 @@ # test_launcher_config.py -import cmd + import inspect import json import tempfile From 6fdf87a71f96853576dee94916ea5d068104e360 Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Wed, 14 Jan 2026 12:08:22 +0000 Subject: [PATCH 10/11] update launcher hash --- crates/contract/assets/launcher_docker_compose.yaml.template | 2 +- tee_launcher/launcher_docker_compose.yaml | 2 +- tee_launcher/launcher_docker_compose_nontee.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/contract/assets/launcher_docker_compose.yaml.template b/crates/contract/assets/launcher_docker_compose.yaml.template index f8099904de..f358be2826 100644 --- a/crates/contract/assets/launcher_docker_compose.yaml.template +++ b/crates/contract/assets/launcher_docker_compose.yaml.template @@ -2,7 +2,7 @@ version: '3.8' services: launcher: - image: nearone/mpc-launcher@sha256:72f16f1d1ca9119f5c19cb3ec7f6338949e2ad3082bd01ba2099c6649e845f61 + image: nearone/mpc-launcher@sha256:17243e65f47c69a4735c507ea91aefa0bdd761d9a267e28221232d748518bad3 container_name: launcher diff --git a/tee_launcher/launcher_docker_compose.yaml b/tee_launcher/launcher_docker_compose.yaml index 7a16d2db4d..5c8310f1ff 100644 --- a/tee_launcher/launcher_docker_compose.yaml +++ b/tee_launcher/launcher_docker_compose.yaml @@ -2,7 +2,7 @@ version: '3.8' services: launcher: - image: nearone/mpc-launcher@sha256:72f16f1d1ca9119f5c19cb3ec7f6338949e2ad3082bd01ba2099c6649e845f61 + image: nearone/mpc-launcher@sha256:17243e65f47c69a4735c507ea91aefa0bdd761d9a267e28221232d748518bad3 container_name: launcher diff --git a/tee_launcher/launcher_docker_compose_nontee.yaml b/tee_launcher/launcher_docker_compose_nontee.yaml index 5286240272..ad4935e794 100644 --- a/tee_launcher/launcher_docker_compose_nontee.yaml +++ b/tee_launcher/launcher_docker_compose_nontee.yaml @@ -2,7 +2,7 @@ version: '3.8' services: launcher: - image: nearone/mpc-launcher@sha256:72f16f1d1ca9119f5c19cb3ec7f6338949e2ad3082bd01ba2099c6649e845f61 + image: nearone/mpc-launcher@sha256:17243e65f47c69a4735c507ea91aefa0bdd761d9a267e28221232d748518bad3 container_name: launcher environment: From d61897615fb43da7bbcf57a5bd710de96448066a Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Wed, 14 Jan 2026 13:15:33 +0000 Subject: [PATCH 11/11] doc cleanup --- tee_launcher/using_the_launcher_in_nontee_setup.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tee_launcher/using_the_launcher_in_nontee_setup.md b/tee_launcher/using_the_launcher_in_nontee_setup.md index a1c87ecc3a..a5d66bb416 100644 --- a/tee_launcher/using_the_launcher_in_nontee_setup.md +++ b/tee_launcher/using_the_launcher_in_nontee_setup.md @@ -12,8 +12,8 @@ In non-TEE mode: - The launcher runs with `PLATFORM=NONTEE` - No dstack socket or attestation is used - RTMR extensions are skipped -- The MPC node is launched with `MPC_TEE_MODE=0` - Image hash validation and upgrade logic remain unchanged +- The MPC container is launched with DSTACK_ENDPOINT set to dstack.sock This provides maximum parity with production while keeping the setup simple. @@ -103,7 +103,6 @@ docker ps ``` Inspect the MPC container to confirm: -- `MPC_TEE_MODE=0` is set - No `DSTACK_ENDPOINT` environment variable exists - No dstack socket is mounted - Expected ports are published