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.
This commit is contained in:
@@ -0,0 +1,974 @@
|
||||
"""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"]
|
||||
Reference in New Issue
Block a user