9fd04b0ce4
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.
975 lines
36 KiB
Python
975 lines
36 KiB
Python
"""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"]
|