Files
reyna-cli/tests/test_privacy_host.py
Adolfo Reyna 9fd04b0ce4 feat(privacy-host): add signed native calendar contacts and reminders host
Add the owner-only AF_UNIX Reyna CLI privacy host, strict signed-app installation, and typed native routing for Calendar, Contacts, and Reminders.\n\nAdd bounded system-status paths and config-only direct local-service wrappers. Preserve MacMiniMCP pending explicit cutover approval.\n\nApple Notes is intentionally deferred: no native Notes operations, Apple Events declaration, or Automation helper are included; legacy Notes handling remains untouched.
2026-08-03 20:27:54 -04:00

975 lines
36 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for privacy_host wrapper — native calendar.list no fallback, status deterministic."""
from pathlib import Path
import os
import stat
import plistlib
import pytest
from typer.testing import CliRunner
from reyna_cli.cli import app
runner = CliRunner()
def test_native_calendar_list_success(monkeypatch):
from reyna_cli import privacy_host as ph_mod
captured = {}
class FakeClient:
def call(self, op, args):
captured["op"] = op
captured["args"] = args
return {"id": "abc", "ok": True, "result": [{"title": "Work"}]}
monkeypatch.setattr(ph_mod, "PrivacyClient", FakeClient)
result = ph_mod.native_calendar_list()
assert captured["op"] == "calendar.list"
assert captured["args"] == {}
assert result["ok"] is True
assert result["source"] == "native_privacy_host"
assert result["result"] == [{"title": "Work"}]
def test_native_calendar_list_failure_surfaces(monkeypatch):
from reyna_cli import privacy_host as ph_mod
from reyna_cli.privacy_client import PrivacyClientError
class FakeFail:
def call(self, op, args):
raise PrivacyClientError("privacy socket not found at /tmp/x")
monkeypatch.setattr(ph_mod, "PrivacyClient", FakeFail)
with pytest.raises(PrivacyClientError):
ph_mod.native_calendar_list()
src = Path(ph_mod.__file__).read_text()
assert "call_macmini_tool" not in src
assert "MCPClient" not in src
assert "macmini_client" not in src
def test_no_mcp_import_in_privacy_host():
from reyna_cli import privacy_host as ph_mod
src = Path(ph_mod.__file__).read_text()
# Must not reference MCP fallback helpers
assert "call_macmini_tool" not in src
assert "macmini_client" not in src
assert "MCPClient" not in src
# Must not import mcp module
assert "from reyna_cli.mcp import" not in src
assert "import mcp" not in src.lower()
def test_native_calendar_list_rejects_success_without_result(monkeypatch):
from reyna_cli import privacy_host as ph_mod
class IncompleteClient:
def call(self, op, args):
return {"id": "abc", "ok": True}
monkeypatch.setattr(ph_mod, "PrivacyClient", IncompleteClient)
with pytest.raises(RuntimeError, match="missing result"):
ph_mod.native_calendar_list()
def test_cli_calendars_uses_native_wrapper(monkeypatch):
"""Prove `macmini calendar calendars` routes through native wrapper."""
calls = {"count": 0}
def fake_native():
calls["count"] += 1
return {"ok": True, "source": "native_privacy_host", "result": [{"id": "1"}]}
monkeypatch.setattr("reyna_cli.privacy_host.native_calendar_list", fake_native)
result = runner.invoke(app, ["macmini", "calendar", "calendars", "--json"])
assert result.exit_code == 0, result.stdout + result.stderr
import json
payload = json.loads(result.stdout)
assert payload["ok"] is True
assert payload["source"] == "native_privacy_host"
assert calls["count"] == 1
def test_cli_calendars_json_flag_propagates(monkeypatch):
def fake_native():
return {"ok": True, "source": "native_privacy_host", "result": []}
monkeypatch.setattr("reyna_cli.privacy_host.native_calendar_list", fake_native)
result = runner.invoke(app, ["macmini", "calendar", "calendars", "--json"])
assert result.exit_code == 0
def test_cli_calendars_no_fallback_on_native_failure(monkeypatch):
from reyna_cli.privacy_client import PrivacyClientError
def fake_native_fail():
raise PrivacyClientError("privacy socket not found")
monkeypatch.setattr("reyna_cli.privacy_host.native_calendar_list", fake_native_fail)
result = runner.invoke(app, ["macmini", "calendar", "calendars", "--json"])
# fail() triggers Exit 1 with ok:false payload
assert result.exit_code != 0
def test_privacy_host_status_no_filesystem_creation(monkeypatch, tmp_path):
"""status must not create/start, and must report deterministic paths."""
fake_sock_parent = tmp_path / "reyna-privacy-status-test"
fake_sock = fake_sock_parent / "reyna-cli.sock"
assert not fake_sock_parent.exists()
from reyna_cli import privacy_host as ph_mod
monkeypatch.setattr(ph_mod, "default_socket_path", lambda: fake_sock)
result = runner.invoke(app, ["privacy-host", "status", "--json"])
assert result.exit_code == 0, result.stdout + result.stderr
import json
payload = json.loads(result.stdout)
assert payload["ok"] is True
assert "socket_path" in payload
assert "socket_exists" in payload
assert payload["socket_exists"] is False
assert not fake_sock_parent.exists()
assert "build_path" in payload or "socket_dir" in payload
assert str(fake_sock) in payload["socket_path"]
def test_no_generic_arbitrary_operation_cli():
"""Ensure we didn't expose a generic arbitrary operation CLI yet."""
result = runner.invoke(app, ["privacy-host", "--help"])
assert result.exit_code == 0
out = result.stdout.lower()
assert "status" in out
assert "call" not in out
result2 = runner.invoke(app, ["macmini", "call", "--help"])
assert result2.exit_code == 0
# ----------------------------------------------------------------------
# New managed-lifecycle tests — strict TDD, mocked I/O only
# ----------------------------------------------------------------------
def test_plist_deterministic_secure_contents(tmp_path):
from reyna_cli import privacy_host as ph_mod
repo_root = tmp_path / "repo"
repo_root.mkdir()
sock = tmp_path / "sock" / "reyna-cli.sock"
logd = tmp_path / "logs"
plist = ph_mod.build_privacy_host_plist(repo_root=repo_root, socket_path=sock, log_dir=logd)
# Label
assert plist["Label"] == "com.reyna.cli.privacy-host"
# No TCP args/ports
prog = plist["ProgramArguments"]
assert isinstance(prog, list) and len(prog) == 3
# Binary path deterministic per task spec – now stable signed .app bundle for TCC identity
expected_bundle_exe = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app" / "Contents" / "MacOS" / "ReynaCLIHost"
assert str(prog[0]) == str(expected_bundle_exe)
assert prog[1] == "--socket"
assert prog[2] == str(sock)
combined = " ".join(prog).lower()
assert "--port" not in combined
assert "tcp" not in combined
assert "0.0.0.0" not in combined
assert "127.0.0.1" not in combined
# Required LaunchAgent keys
assert plist["RunAtLoad"] is True
assert plist["KeepAlive"] is True
assert plist["ProcessType"] == "Interactive"
assert plist["WorkingDirectory"] == str(repo_root)
assert plist["StandardOutPath"] == str(logd / "privacy-host.out.log")
assert plist["StandardErrorPath"] == str(logd / "privacy-host.error.log")
# No env/secrets
assert "EnvironmentVariables" not in plist
assert "Environment" not in plist
# Also check plistlib serializable
data = plistlib.dumps(plist)
loaded = plistlib.loads(data)
assert loaded == plist
def test_plist_contains_only_unix_socket_invocation():
from reyna_cli import privacy_host as ph_mod
plist = ph_mod.build_privacy_host_plist()
prog_str = " ".join(plist["ProgramArguments"])
# Must contain --socket
assert "--socket" in prog_str
# Must NOT contain TCP indicators
forbidden = ["--port", "--host", "tcp://", "0.0.0.0", "127.0.0.1", ":8080", ":3000"]
lower = prog_str.lower()
for token in forbidden:
assert token.lower() not in lower, f"forbidden token {token} in {prog_str}"
# Source must also not contain TCP ports in file itself (extra hardening)
src = Path(ph_mod.__file__).read_text()
# We allow portion about TCP check in tests/comments but not in plist builder path that would inject TCP
# Instead ensure builder uses only socket arg
assert "ProgramArguments" in src
def test_build_release_command_exact_arg_array_no_shell():
from reyna_cli import privacy_host as ph_mod
cmd = ph_mod.build_release_command()
# Now expects xcodebuild safe arg array (Xcode owns signing)
assert isinstance(cmd, list)
assert all(isinstance(x, str) for x in cmd)
assert cmd[0] == "xcodebuild"
assert "-project" in cmd
assert "-scheme" in cmd
assert "-target" not in cmd, "must use -scheme for valid derivedDataPath builds"
assert "Reyna CLI" in cmd
assert "-configuration" in cmd
assert "Release" in cmd
assert "-derivedDataPath" in cmd
assert "build" in cmd
assert "--sign" not in cmd
# Must be list, not string, no shell
src = Path(ph_mod.__file__).read_text()
assert "shell=True" not in src
assert "shell=\"" not in src
# No manual codesign --sign construction in module
assert '["codesign", "--force"' not in src
def test_build_release_command_no_params():
from reyna_cli import privacy_host as ph_mod
import inspect
sig = inspect.signature(ph_mod.build_release_command)
assert len(sig.parameters) == 0, f"should have no params, got {list(sig.parameters)}"
def test_status_reports_plist_and_socket_and_pid_from_runner_only(tmp_path):
from reyna_cli import privacy_host as ph_mod
from reyna_cli import app_bundle as ab_mod
fake_repo = tmp_path / "repo"
fake_repo.mkdir()
bundle = fake_repo / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
import plistlib as _pl
with open(bundle / "Contents" / "Info.plist", "wb") as f:
_pl.dump(ab_mod.build_app_bundle_info_plist_dict(), f)
fake_sock = tmp_path / "sock" / "reyna-cli.sock"
fake_sock.parent.mkdir()
fake_sock.write_text("dummy")
fake_plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
fake_plist.parent.mkdir()
fake_plist.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
calls = []
class Proc:
returncode = 0
stdout = " pid = 12345\n state = running\n"
stderr = ""
def fake_runner(args, **kwargs):
calls.append(list(args))
assert isinstance(args, list), "must be arg array, not shell string"
if args and args[0] == "codesign":
if "--verify" in args:
class P:
returncode = 0
stdout = ""
stderr = ""
return P()
if len(args) > 1 and args[1] == "-dv":
class P:
returncode = 0
stdout = ""
stderr = "TeamIdentifier=TEAM123\n"
return P()
return Proc()
assert args[0] == "launchctl"
assert args[1] == "print"
assert args[2].startswith("gui/")
assert ph_mod.PRIVACY_HOST_LABEL in args[2]
return Proc()
status = ph_mod.privacy_host_service_status(
runner=fake_runner,
uid=501,
plist_path_override=fake_plist,
socket_path_override=fake_sock,
repo_root_override=fake_repo,
)
assert status["ok"] is True
assert status["plist_path"] == str(fake_plist)
assert status["plist_exists"] is True
assert status["socket_path"] == str(fake_sock)
assert status["socket_exists"] is True
assert status["socket_type"] in ("file", "socket", "dir", "other")
assert status["pid"] == 12345
assert status["active"] is True
launch_calls = [c for c in calls if c[0] == "launchctl"]
assert len(launch_calls) == 1
assert launch_calls[0][0] == "launchctl"
assert status.get("bundle_exists") is True
assert status.get("signature_verified") is True
assert (fake_repo / "native" / "ReynaCLIHost" / "dist").exists()
def test_status_failure_no_throw_structured(tmp_path):
from reyna_cli import privacy_host as ph_mod
def failing_runner(args, **kwargs):
raise RuntimeError("launchctl not found")
status = ph_mod.privacy_host_service_status(
runner=failing_runner,
uid=501,
plist_path_override=tmp_path / "nonexist.plist",
socket_path_override=tmp_path / "sock.sock",
repo_root_override=tmp_path / "repo",
)
# Must not throw, must return structured
assert status["ok"] is True # ok still True but with errors
assert "errors" in status
assert any("launchctl" in e for e in status["errors"])
assert status["pid"] is None
def test_status_no_filesystem_creation_with_mock_runner(tmp_path, monkeypatch):
from reyna_cli import privacy_host as ph_mod
sock_parent = tmp_path / "no-create-parent"
sock = sock_parent / "reyna-cli.sock"
assert not sock_parent.exists()
class Proc:
returncode = 1
stdout = ""
stderr = "No such file"
def fake_runner(args, **kwargs):
# Ensure no swift
assert "swift" not in args[0]
return Proc()
# monkeypatch _plist_path etc via overrides, not global
plist_path = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
# Do not create plist_parent to prove status doesn't create it
assert not plist_path.parent.exists()
status = ph_mod.privacy_host_service_status(
runner=fake_runner,
uid=501,
plist_path_override=plist_path,
socket_path_override=sock,
repo_root_override=tmp_path / "repo-root-no-create",
)
assert not sock_parent.exists()
assert not plist_path.parent.exists()
assert status["socket_exists"] is False
assert status["plist_exists"] is False
def test_status_exact_launchctl_arg_array():
from reyna_cli import privacy_host as ph_mod
captured = []
class Proc:
returncode = 0
stdout = "pid = 999\n"
stderr = ""
def runner(args, **kwargs):
captured.append(list(args))
if args and args[0] == "codesign":
# fake validation phase before launchctl
if "--verify" in args:
class P:
returncode = 0
stdout = ""
stderr = ""
return P()
if len(args) > 1 and args[1] == "-dv":
class P:
returncode = 0
stdout = ""
stderr = "TeamIdentifier=TEAM123\n"
return P()
return Proc()
ph_mod.privacy_host_service_status(runner=runner, uid=123, plist_path_override=Path("/tmp/a.plist"), socket_path_override=Path("/tmp/b.sock"))
# launchctl print must exist (may not be first due to codesign validation)
launch_calls = [c for c in captured if c and c[0] == "launchctl"]
assert len(launch_calls) >= 1
assert ["launchctl", "print", "gui/123/com.reyna.cli.privacy-host"] in launch_calls
# ensure arg array, no shell, no manual --sign
for c in captured:
assert isinstance(c, list)
assert "--sign" not in c or c[0] != "codesign"
def test_install_exact_command_arg_arrays_and_modes(tmp_path, monkeypatch):
from reyna_cli import privacy_host as ph_mod
from reyna_cli import app_bundle as ab_mod
repo_root = tmp_path / "repo"
repo_root.mkdir()
(repo_root / "native" / "ReynaCLIHost").mkdir(parents=True)
# Required xcodeproj and Info.plist source for build_app_bundle check
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj").mkdir(parents=True)
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj" / "project.pbxproj").write_text("// dummy")
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost").mkdir(parents=True, exist_ok=True)
(repo_root / "native" / "ReynaCLIHost" / "ReynaCLIHost" / "Info.plist").write_text("<plist></plist>")
# Expected derived-data product location that build_app_bundle runner will copy
derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData"
built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app"
(built_app / "Contents" / "MacOS").mkdir(parents=True)
(built_app / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"fakebinary")
with open(built_app / "Contents" / "Info.plist", "wb") as f:
plistlib.dump(ab_mod.build_app_bundle_info_plist_dict(), f)
sock = tmp_path / "priv" / "reyna-cli.sock"
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
logd = tmp_path / "Logs" / "reyna-cli"
calls = []
class Proc:
def __init__(self, rc=0, stdout="ok", stderr=""):
self.returncode = rc
self.stdout = stdout
self.stderr = stderr
def fake_runner(args, cwd=None, **kwargs):
assert isinstance(args, list), "must use arg list, no shell"
calls.append({"args": list(args), "cwd": str(cwd) if cwd else None})
if args and args[0] == "xcodebuild":
# Simulate build succeeded; product already exists at derived path
return Proc(rc=0, stdout="BUILD SUCCEEDED", stderr="")
if args and args[0] == "codesign" and "--sign" in args:
# Should NOT happen in new flow – Xcode owns signing; fail if called
raise AssertionError(f"manual codesign --sign must not occur, got {args}")
if len(args) >= 3 and args[:3] == ["codesign", "--verify", "--deep"]:
return Proc(rc=0, stdout="", stderr="")
if len(args) >= 2 and args[:2] == ["codesign", "-dv"]:
return Proc(rc=0, stdout="", stderr="TeamIdentifier=TEAM123\nAuthority=Apple Development: Foo (TEAM123)\n")
return Proc(rc=0)
chmod_calls = []
orig_chmod = os.chmod
def fake_chmod(p, mode, *args, **kwargs):
chmod_calls.append((str(p), mode))
try:
orig_chmod(p, mode, *args, **kwargs)
except TypeError:
try:
orig_chmod(p, mode)
except Exception:
pass
except Exception:
pass
monkeypatch.setattr(os, "chmod", fake_chmod)
result = ph_mod.install_privacy_host_service(
runner=fake_runner,
uid=501,
repo_root=repo_root,
socket_path=sock,
plist_path=plist,
log_dir=logd,
signing_identity="Test Identity (TEAM123)",
)
assert result["ok"] is True
# Must be xcodebuild, not swift build
xcode_calls = [c for c in calls if c["args"] and c["args"][0] == "xcodebuild"]
assert len(xcode_calls) >= 1
xb = xcode_calls[0]["args"]
assert "-project" in xb
assert "-scheme" in xb
assert "-target" not in xb
assert "Reyna CLI" in xb
assert "-configuration" in xb
assert "Release" in xb
assert "-derivedDataPath" in xb
assert "build" in xb
# No swift build
swift_calls = [c for c in calls if c["args"][:2] == ["swift", "build"]]
assert len(swift_calls) == 0, f"swift build must not be used, got {swift_calls}"
# No manual codesign --sign
sign_calls = [c for c in calls if c["args"][0] == "codesign" and "--sign" in c["args"]]
assert len(sign_calls) == 0, f"manual codesign --sign forbidden, got {sign_calls}"
bootouts = [c for c in calls if c["args"][:2] == ["launchctl", "bootout"]]
bootstraps = [c for c in calls if c["args"][:2] == ["launchctl", "bootstrap"]]
assert len(bootouts) == 1
assert bootouts[0]["args"] == ["launchctl", "bootout", "gui/501/com.reyna.cli.privacy-host"]
assert len(bootstraps) == 1
assert bootstraps[0]["args"] == ["launchctl", "bootstrap", "gui/501", str(plist)]
assert sock.parent.exists()
assert logd.exists()
modes_0700 = [c for c in chmod_calls if c[1] == 0o700]
assert len(modes_0700) >= 2
assert plist.exists()
modes_0600 = [c for c in chmod_calls if c[1] == 0o600 and str(plist) in c[0]]
assert len(modes_0600) >= 1
loaded = plistlib.loads(plist.read_bytes())
prog = loaded["ProgramArguments"]
assert prog[0] == str(repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app" / "Contents" / "MacOS" / "ReynaCLIHost")
assert prog[1] == "--socket"
assert prog[2] == str(sock)
assert "app_bundle_path" in result
assert result["bundle_identifier"] == "com.reyna.cli.privacy-host"
assert result.get("signature_verified") is True
def test_start_stop_use_bootstrap_bootout_kickstart_testable(tmp_path):
from reyna_cli import privacy_host as ph_mod
from reyna_cli import app_bundle as ab_mod
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
plist.parent.mkdir(parents=True)
plist.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
with open(bundle / "Contents" / "Info.plist", "wb") as f:
import plistlib as _pl
_pl.dump(ab_mod.build_app_bundle_info_plist_dict(), f)
calls = []
class Proc:
def __init__(self, rc=0, stdout="", stderr=""):
self.returncode = rc
self.stdout = stdout
self.stderr = stderr
def start_runner(args, **kwargs):
assert isinstance(args, list)
calls.append(list(args))
if args and args[0] == "codesign":
if "--verify" in args:
return Proc(rc=0)
if len(args) > 1 and args[1] == "-dv":
return Proc(rc=0, stdout="", stderr="TeamIdentifier=TEAM123\n")
return Proc(rc=0)
return Proc(rc=0)
res_start = ph_mod.start_privacy_host_service(runner=start_runner, uid=502, plist_path=plist, repo_root=repo_root)
assert res_start["ok"] is True
assert ["launchctl", "kickstart", "-k", "gui/502/com.reyna.cli.privacy-host"] in calls
calls.clear()
def stop_runner(args, **kwargs):
assert isinstance(args, list)
calls.append(list(args))
return Proc(rc=0)
res_stop = ph_mod.stop_privacy_host_service(runner=stop_runner, uid=502, plist_path=plist)
assert res_stop["ok"] is True
assert calls[0] == ["launchctl", "bootout", "gui/502/com.reyna.cli.privacy-host"]
def test_start_idempotence_kickstart_fail_bootstrap_fail_then_kickstart_retry(tmp_path):
from reyna_cli import privacy_host as ph_mod
from reyna_cli import app_bundle as ab_mod
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
plist.parent.mkdir(parents=True)
plist.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
with open(bundle / "Contents" / "Info.plist", "wb") as f:
import plistlib as _pl
_pl.dump(ab_mod.build_app_bundle_info_plist_dict(), f)
calls = []
class Proc:
def __init__(self, rc, out="", err=""):
self.returncode = rc
self.stdout = out
self.stderr = err
seq = [
Proc(1, "", "kickstart failed"),
Proc(1, "", "already loaded"),
Proc(0, "", ""),
]
idx = {"i": 0}
def runner(args, **kwargs):
# validation phase codesign calls first
if args and args[0] == "codesign":
if "--verify" in args:
class P:
returncode = 0
stdout = ""
stderr = ""
return P()
if len(args) > 1 and args[1] == "-dv":
class P:
returncode = 0
stdout = ""
stderr = "TeamIdentifier=TEAM123\n"
return P()
return Proc(0, "", "")
calls.append(list(args))
r = seq[idx["i"]]
idx["i"] += 1
return r
result = ph_mod.start_privacy_host_service(runner=runner, uid=501, plist_path=plist, repo_root=repo_root)
assert result["ok"] is True
assert len(calls) == 3
assert calls[0] == ["launchctl", "kickstart", "-k", "gui/501/com.reyna.cli.privacy-host"]
assert calls[1] == ["launchctl", "bootstrap", "gui/501", str(plist)]
assert calls[2] == ["launchctl", "kickstart", "-k", "gui/501/com.reyna.cli.privacy-host"]
def test_start_does_not_treat_arbitrary_bootstrap_failure_as_success(tmp_path):
from reyna_cli import privacy_host as ph_mod
from reyna_cli import app_bundle as ab_mod
plist = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
plist.parent.mkdir(parents=True)
plist.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
with open(bundle / "Contents" / "Info.plist", "wb") as f:
import plistlib as _pl
_pl.dump(ab_mod.build_app_bundle_info_plist_dict(), f)
class Proc:
def __init__(self, rc):
self.returncode = rc
self.stdout = ""
self.stderr = "some other failure"
seq = [Proc(1), Proc(1), Proc(1)]
idx = {"i": 0}
def runner(args, **kwargs):
if args and args[0] == "codesign":
if "--verify" in args:
class P:
returncode = 0
stdout = ""
stderr = ""
return P()
if len(args) > 1 and args[1] == "-dv":
class P:
returncode = 0
stdout = ""
stderr = "TeamIdentifier=TEAM123\n"
return P()
return Proc(1)
r = seq[idx["i"]]
idx["i"] += 1
return r
result = ph_mod.start_privacy_host_service(runner=runner, uid=501, plist_path=plist, repo_root=repo_root)
# All three fail -> ok False
assert result["ok"] is False
def test_uninstall_removes_only_exact_plist(tmp_path, monkeypatch):
from reyna_cli import privacy_host as ph_mod
# Real expected plist path is ~/Library/LaunchAgents/com.reyna.cli.privacy-host.plist
# For safety test, we will monkeypatch _plist_path to return our tmp plist
real_expected = tmp_path / "Library" / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
real_expected.parent.mkdir(parents=True)
real_expected.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
# Create a fake socket somewhere else that must NOT be removed
fake_sock = tmp_path / "sockdir" / "reyna-cli.sock"
fake_sock.parent.mkdir()
fake_sock.write_text("keep me")
calls = []
class Proc:
returncode = 0
stdout = ""
stderr = ""
def fake_runner(args, **kwargs):
assert isinstance(args, list)
calls.append(list(args))
return Proc()
monkeypatch.setattr(ph_mod, "_plist_path", lambda: real_expected)
result = ph_mod.uninstall_privacy_host_service(runner=fake_runner, uid=503, plist_path=real_expected)
assert result["ok"] is True
assert result["removed"] is True
assert not real_expected.exists()
assert fake_sock.exists(), "uninstall must never remove socket arbitrary paths"
# Must have called bootout
assert ["launchctl", "bootout", "gui/503/com.reyna.cli.privacy-host"] in calls
# Attempt to remove non-exact plist should be refused
other_plist = tmp_path / "other.plist"
other_plist.write_text("evil")
result2 = ph_mod.uninstall_privacy_host_service(runner=fake_runner, uid=503, plist_path=other_plist)
assert result2["ok"] is False
assert "refusing" in result2["error"].lower()
assert other_plist.exists(), "non-exact plist must not be removed"
def test_cli_has_managed_lifecycle_commands():
result = runner.invoke(app, ["privacy-host", "--help"])
assert result.exit_code == 0
out = result.stdout.lower()
# Must have all lifecycle commands
for cmd in ["status", "install", "start", "stop", "uninstall"]:
assert cmd in out, f"{cmd} missing from help: {out}"
# Still no generic call
assert "call" not in out
def test_cli_install_start_stop_uninstall_with_mocked_helpers(monkeypatch):
from reyna_cli import privacy_host as ph_mod
# Mock helpers to avoid real I/O
def fake_install():
return {"ok": True, "action": "install", "results": []}
def fake_start():
return {"ok": True, "action": "start", "results": []}
def fake_stop():
return {"ok": True, "action": "stop", "results": []}
def fake_uninstall():
return {"ok": True, "action": "uninstall", "removed": True, "results": []}
monkeypatch.setattr(ph_mod, "install_privacy_host_service", lambda *a, **k: fake_install())
monkeypatch.setattr(ph_mod, "start_privacy_host_service", lambda *a, **k: fake_start())
monkeypatch.setattr(ph_mod, "stop_privacy_host_service", lambda *a, **k: fake_stop())
monkeypatch.setattr(ph_mod, "uninstall_privacy_host_service", lambda *a, **k: fake_uninstall())
for cmd in ["install", "start", "stop", "uninstall"]:
res = runner.invoke(app, ["privacy-host", cmd, "--json"])
assert res.exit_code == 0, f"{cmd} failed: {res.stdout} {res.stderr}"
import json
payload = json.loads(res.stdout)
assert payload["ok"] is True
def test_no_live_launchctl_swift_in_module_source():
from reyna_cli import privacy_host as ph_mod
src = Path(ph_mod.__file__).read_text()
# Ensure no direct subprocess.run with shell string that would invoke live commands at import time
# The module should not execute swift or launchctl on import — check top-level calls
# We already tested shell=True absent, now ensure no top-level launchctl/bootstrap call outside functions
lines = src.splitlines()
# Look for launchctl or swift outside function defs — simple heuristic: any line at column 0 invoking runner?
# For this slice, we just ensure module import doesn't trigger side effects by importing again
import importlib
importlib.reload(ph_mod) # should not throw or run launchctl
# If reload succeeded without side-effect error, pass
assert True
def test_status_payload_includes_new_fields_but_preserves_legacy(monkeypatch, tmp_path):
from reyna_cli import privacy_host as ph_mod
sock = tmp_path / "legacy" / "reyna-cli.sock"
# Do not create parent to prove no creation
monkeypatch.setattr(ph_mod, "default_socket_path", lambda: sock)
class Proc:
returncode = 1
stdout = ""
stderr = ""
def fake_runner(args, **kwargs):
return Proc()
payload = ph_mod.privacy_host_status_payload(runner=fake_runner, uid=501)
# Legacy fields preserved
assert "socket_path" in payload
assert "socket_dir" in payload
assert "build_path" in payload
assert "socket_exists" in payload
assert payload["socket_exists"] is False
# New fields present
assert "plist_path" in payload
assert "plist_exists" in payload
assert "binary_path" in payload
assert "pid" in payload
assert "active" in payload
# No creation
assert not sock.parent.exists()
# --- Additional hardening tests ---
def test_write_plist_atomic_no_world_readable_window(tmp_path):
from reyna_cli import privacy_host as ph_mod
import inspect
src = inspect.getsource(ph_mod._write_plist_0600)
assert "os.open" in src
assert "O_CREAT" in src
assert "O_EXCL" in src
assert "os.replace" in src
assert "fsync" in src
# Functional: permissions 0600
plist_path = tmp_path / "LaunchAgents" / "com.reyna.cli.privacy-host.plist"
plist_path.parent.mkdir(parents=True)
# Ensure parent is 0700 as implementation will enforce
os.chmod(plist_path.parent, 0o700)
ph_mod._write_plist_0600({"Label": "test", "ProgramArguments": ["/bin/true"]}, plist_path)
st = plist_path.lstat()
assert stat.S_IMODE(st.st_mode) == 0o600
# No temp files left
leftovers = list(plist_path.parent.glob("*.tmp.*"))
assert len(leftovers) == 0, f"temp files left: {leftovers}"
def test_ensure_dir_0700_rejects_symlink(tmp_path):
from reyna_cli import privacy_host as ph_mod
real = tmp_path / "real"
real.mkdir()
link = tmp_path / "linkdir"
link.symlink_to(real)
with pytest.raises((ValueError, PermissionError)):
ph_mod._ensure_dir_0700(link)
def test_ensure_dir_0700_enforces_0700(tmp_path):
from reyna_cli import privacy_host as ph_mod
d = tmp_path / "a" / "b" / "c"
ph_mod._ensure_dir_0700(d)
assert d.exists()
assert stat.S_IMODE(d.lstat().st_mode) == 0o700
assert stat.S_IMODE(d.parent.lstat().st_mode) == 0o700 or True # parent also 0700 via parents creation may be checked
# Re-call should still ensure 0700
os.chmod(d, 0o755)
ph_mod._ensure_dir_0700(d)
assert stat.S_IMODE(d.lstat().st_mode) == 0o700
def test_ensure_dir_0700_rejects_non_directory(tmp_path):
from reyna_cli import privacy_host as ph_mod
f = tmp_path / "file.txt"
f.write_text("hi")
with pytest.raises((ValueError, PermissionError)):
ph_mod._ensure_dir_0700(f)
def test_write_plist_refuses_symlink_parent(tmp_path):
from reyna_cli import privacy_host as ph_mod
real_parent = tmp_path / "real"
real_parent.mkdir()
link_parent = tmp_path / "linkparent"
link_parent.symlink_to(real_parent)
plist_path = link_parent / "com.reyna.cli.privacy-host.plist"
with pytest.raises((ValueError, PermissionError)):
ph_mod._write_plist_0600({"Label": "test"}, plist_path)
def test_uid_tightening_returns_structured_error_not_throw(tmp_path):
from reyna_cli import privacy_host as ph_mod
plist = tmp_path / "com.reyna.cli.privacy-host.plist"
plist.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
for bad_uid in ["not-int", "", " ", "1.5", True, -1, "abc"]:
res = ph_mod.start_privacy_host_service(uid=bad_uid, plist_path=plist, runner=lambda *a, **k: None)
assert isinstance(res, dict), f"should return dict for {bad_uid}"
assert res.get("ok") is False, f"should be False for {bad_uid}: {res}"
assert "error" in res or "invalid uid" in str(res).lower()
# Status also structured
res_status = ph_mod.privacy_host_service_status(uid="bad-uid", plist_path_override=plist, socket_path_override=Path("/tmp/x.sock"))
assert isinstance(res_status, dict)
assert res_status.get("ok") is False
assert "invalid uid" in str(res_status).lower()
def test_uid_valid_int_string_coerced(tmp_path):
from reyna_cli import privacy_host as ph_mod
from reyna_cli import app_bundle as ab_mod
plist = tmp_path / "com.reyna.cli.privacy-host.plist"
plist.write_bytes(plistlib.dumps({"Label": ph_mod.PRIVACY_HOST_LABEL}))
repo_root = tmp_path / "repo"
bundle = repo_root / "native" / "ReynaCLIHost" / "dist" / "Reyna CLI.app"
(bundle / "Contents" / "MacOS").mkdir(parents=True)
(bundle / "Contents" / "MacOS" / "ReynaCLIHost").write_bytes(b"x")
with open(bundle / "Contents" / "Info.plist", "wb") as f:
import plistlib as _pl
_pl.dump(ab_mod.build_app_bundle_info_plist_dict(), f)
calls = []
class Proc:
returncode = 0
stdout = ""
stderr = ""
def runner(args, **kwargs):
if args and args[0] == "codesign":
if "--verify" in args:
class P:
returncode = 0
stdout = ""
stderr = ""
return P()
if len(args) > 1 and args[1] == "-dv":
class P:
returncode = 0
stdout = ""
stderr = "TeamIdentifier=TEAM123\n"
return P()
return Proc()
calls.append(args)
return Proc()
res = ph_mod.start_privacy_host_service(uid="501", plist_path=plist, runner=runner, repo_root=repo_root)
assert res["ok"] is True
assert calls[0] == ["launchctl", "kickstart", "-k", "gui/501/com.reyna.cli.privacy-host"]