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:
Adolfo Reyna
2026-08-03 20:27:54 -04:00
parent 6e2117188e
commit 9fd04b0ce4
56 changed files with 14239 additions and 50 deletions
@@ -0,0 +1,332 @@
import XCTest
import Foundation
import Darwin
@testable import ReynaCLIHostCore
final class SocketPathValidationTests: XCTestCase {
// Helpers to build fake LStatInfo
private func dir(uid: uid_t, mode: mode_t, symlink: Bool = false) -> LStatInfo {
return LStatInfo(uid: uid, mode: mode_t(mode), isSymlink: symlink, isDir: !symlink, exists: true)
}
private func currentUID() -> uid_t { getuid() }
// Real default socket path: $HOME/Library/Application Support/reyna-cli/privacy/reyna-cli.sock
// Actual Mac modes from bug report:
// home /Users/adolforeyna = 0750, ~/Library = 0700, ~/Library/Application Support = 0700, privacy = 0700
// Tier 2 allows 0750 for intermediates, tier 3 requires 0700 for dedicated runtime parent.
func testRealDefaultHomeSocketPathValidationAccepts() throws {
let uid = currentUID()
let home = NSHomeDirectory() // /Users/adolforeyna
let socketPath = (home as NSString).appendingPathComponent("Library/Application Support/reyna-cli/privacy/reyna-cli.sock")
var map: [String: LStatInfo] = [:]
map["/"] = dir(uid: 0, mode: 0o40755)
map["/Users"] = dir(uid: 0, mode: 0o40755)
// helper to insert chain
func insertChain(upTo target: String, defaultMode: mode_t, overrides: [String: mode_t] = [:]) {
let url = URL(fileURLWithPath: target)
var cur = ""
for comp in url.pathComponents {
if comp == "/" { cur = "/"; continue }
if cur == "/" { cur = "/" + comp } else if cur.isEmpty { cur = comp } else { cur = cur + "/" + comp }
if map[cur] != nil { continue }
if cur == "/" || cur == "/Users" { continue }
let m = overrides[cur] ?? defaultMode
map[cur] = dir(uid: uid, mode: m)
}
}
// Home itself 0750 per actual system
map[home] = dir(uid: uid, mode: 0o40750)
// Library and subdirs 0700 except home already set
// Build full parent chain to privacy
let parentOfSocket = URL(fileURLWithPath: socketPath).deletingLastPathComponent().path
// For parent chain: home is 0750 override, others 0700
var chainCur = ""
for comp in URL(fileURLWithPath: parentOfSocket).pathComponents {
if comp == "/" { chainCur = "/"; continue }
if chainCur == "/" { chainCur = "/" + comp } else if chainCur.isEmpty { chainCur = comp } else { chainCur = chainCur + "/" + comp }
if map[chainCur] != nil { continue }
if chainCur == "/" || chainCur == "/Users" { continue }
if chainCur == home { continue } // already 0750
map[chainCur] = dir(uid: uid, mode: 0o40700)
}
let provider: LStatProvider = { path in map[path] }
XCTAssertNoThrow(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider),
"Real default home with 0750 home and 0700 Library/.../privacy must validate")
XCTAssertTrue(platformTrustedRootPaths.contains("/Users"))
XCTAssertTrue(platformTrustedRootPaths.contains("/"))
}
func testRealDefaultHomeSocketPathValidationAcceptsWithHome0755() throws {
// Also allow 0755 for home (some configs) – tier 2 should still allow as no write
let uid = currentUID()
let home = NSHomeDirectory()
let socketPath = (home as NSString).appendingPathComponent("Library/Application Support/reyna-cli/privacy/reyna-cli.sock")
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755)
]
map[home] = dir(uid: uid, mode: 0o40755)
let parentPath = URL(fileURLWithPath: socketPath).deletingLastPathComponent().path
var cur = ""
for comp in URL(fileURLWithPath: parentPath).pathComponents {
if comp == "/" { cur = "/"; continue }
if cur == "/" { cur = "/" + comp } else if cur.isEmpty { cur = comp } else { cur = cur + "/" + comp }
if map[cur] != nil { continue }
if cur == "/" || cur == "/Users" { continue }
map[cur] = dir(uid: uid, mode: 0o40700)
}
let provider: LStatProvider = { map[$0] }
XCTAssertNoThrow(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider),
"Home 0755 should also be allowed (no write)")
}
// Safe vs unsafe ancestor decision with fake stats
func testTrustedRootMustBeRootOwned() {
let uid = currentUID()
let socketPath = "/Users/\(NSUserName())/Library/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: uid, mode: 0o40755), // wrong: owned by current user, should fail - /Users must be uid 0
"/Users/\(NSUserName())": dir(uid: uid, mode: 0o40700)
]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider)) { err in
let msg = (err as NSError).localizedDescription
XCTAssertTrue(msg.contains("/Users") || msg.contains("current uid") || msg.contains("not owned"))
}
}
func testTrustedRootMustNotBeWorldWritable() {
let uid = currentUID()
let socketPath = "/Users/\(NSUserName())/Library/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40777), // world writable unsafe
"/Users/\(NSUserName())": dir(uid: uid, mode: 0o40700)
]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider)) { err in
XCTAssertTrue((err as NSError).code == 25 || (err as NSError).localizedDescription.contains("permissions"))
}
}
func testRejectsArbitraryRootOwnedIntermediatePath() {
// E.g. /tmp/root_owned_dir owned by root should be REJECTED because not in allowlist
let uid = currentUID()
let socketPath = "/tmp/root_owned_dir/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
// /tmp is symlink-allowed platform path
"/tmp": dir(uid: 0, mode: 0o120777, symlink: true), // symlink allowed
"/tmp/root_owned_dir": dir(uid: 0, mode: 0o40700) // root owned but not allowlisted
]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider),
"Arbitrary root-owned path /tmp/root_owned_dir must be rejected – only explicit allowlist trusted")
}
func testRejectsHomeNotOwnedByCurrentUID() {
let uid = currentUID()
let socketPath = "/Users/\(NSUserName())/Library/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
"/Users/\(NSUserName())": dir(uid: 0, mode: 0o40700) // wrong owner
]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider))
}
func testAllowsHome0750ForIntermediateButRejectsWritable() {
// Tier 2: intermediate ancestors allow 0750/0755, reject writable bits
let uid = currentUID()
let socketPath = "/Users/\(NSUserName())/Library/Application Support/reyna-cli/privacy/reyna.sock"
// 0750 for home should be accepted (intermediate)
var mapAllow: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
"/Users/\(NSUserName())": dir(uid: uid, mode: 0o40750),
"/Users/\(NSUserName())/Library": dir(uid: uid, mode: 0o40700),
"/Users/\(NSUserName())/Library/Application Support": dir(uid: uid, mode: 0o40700),
"/Users/\(NSUserName())/Library/Application Support/reyna-cli": dir(uid: uid, mode: 0o40700),
"/Users/\(NSUserName())/Library/Application Support/reyna-cli/privacy": dir(uid: uid, mode: 0o40700)
]
let providerAllow: LStatProvider = { mapAllow[$0] }
XCTAssertNoThrow(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: providerAllow),
"HOME 0750 as intermediate must be allowed (no write bits)")
// 0770 (group writable) for home must be rejected
var mapReject: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
"/Users/\(NSUserName())": dir(uid: uid, mode: 0o40770)
]
let socketPath2 = "/Users/\(NSUserName())/Library/reyna.sock"
let providerReject: LStatProvider = { mapReject[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath2, currentUID: uid, provider: providerReject),
"HOME 0770 (group writable) must be rejected even for intermediate")
// 0777 world writable must be rejected
var mapReject2: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
"/Users/\(NSUserName())": dir(uid: uid, mode: 0o40777)
]
let providerReject2: LStatProvider = { mapReject2[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath2, currentUID: uid, provider: providerReject2),
"HOME 0777 must be rejected")
}
func testRejectsDedicatedRuntimeParentWith0750or0755() {
let uid = currentUID()
let home = NSHomeDirectory()
let socketPath = (home as NSString).appendingPathComponent("Library/Application Support/reyna-cli/privacy/reyna-cli.sock")
// privacy dir 0750 must be rejected (tier 3 requires 0700)
var map0750: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
]
// build chain but make privacy 0750
var cur = ""
for comp in URL(fileURLWithPath: URL(fileURLWithPath: socketPath).deletingLastPathComponent().path).pathComponents {
if comp == "/" { cur = "/"; continue }
if cur == "/" { cur = "/" + comp } else if cur.isEmpty { cur = comp } else { cur = cur + "/" + comp }
if map0750[cur] != nil { continue }
if cur == "/" || cur == "/Users" { continue }
if cur.hasSuffix("/privacy") {
map0750[cur] = dir(uid: uid, mode: 0o40750)
} else if cur == home {
map0750[cur] = dir(uid: uid, mode: 0o40750) // home 0750 allowed
} else {
map0750[cur] = dir(uid: uid, mode: 0o40700)
}
}
let provider0750: LStatProvider = { map0750[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider0750),
"Dedicated runtime parent privacy with 0750 must be rejected – requires 0700")
// 0755 also rejected
var map0755 = map0750
let privacyPath = URL(fileURLWithPath: socketPath).deletingLastPathComponent().path
map0755[privacyPath] = dir(uid: uid, mode: 0o40755)
let provider0755: LStatProvider = { map0755[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider0755),
"Dedicated runtime parent privacy with 0755 must be rejected")
}
func testAllowsIntermediate0755ButRequiresPrivacy0700() {
let uid = currentUID()
let home = NSHomeDirectory()
let socketPath = (home as NSString).appendingPathComponent("Library/Application Support/reyna-cli/privacy/reyna-cli.sock")
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
]
var cur = ""
for comp in URL(fileURLWithPath: URL(fileURLWithPath: socketPath).deletingLastPathComponent().path).pathComponents {
if comp == "/" { cur = "/"; continue }
if cur == "/" { cur = "/" + comp } else if cur.isEmpty { cur = comp } else { cur = cur + "/" + comp }
if map[cur] != nil { continue }
if cur == "/" || cur == "/Users" { continue }
if cur == home {
map[cur] = dir(uid: uid, mode: 0o40755) // home 0755 allowed as intermediate
} else if cur.hasSuffix("/privacy") == false {
// intermediate Library etc can be 0750/0755
map[cur] = dir(uid: uid, mode: 0o40750)
} else {
map[cur] = dir(uid: uid, mode: 0o40700) // privacy must be 0700
}
}
let provider: LStatProvider = { map[$0] }
XCTAssertNoThrow(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider),
"Intermediate 0750/0755 allowed, privacy 0700 must validate")
}
func testRejectsSymlinkInUserChain() {
let uid = currentUID()
let socketPath = "/Users/\(NSUserName())/Library/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
"/Users/\(NSUserName())": dir(uid: uid, mode: 0o40700),
"/Users/\(NSUserName())/Library": dir(uid: uid, mode: 0o120777, symlink: true)
]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider),
"Symlink in user chain must be rejected")
}
func testAllowsPlatformSymlinksTmpVar() {
let uid = currentUID()
let socketPath = "/tmp/rh-test/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/private": dir(uid: 0, mode: 0o40755),
"/private/tmp": dir(uid: 0, mode: 0o41777), // /private/tmp typically 1777
"/tmp": dir(uid: 0, mode: 0o120777, symlink: true), // allowed symlink
"/tmp/rh-test": dir(uid: uid, mode: 0o40700)
]
let provider: LStatProvider = { map[$0] }
XCTAssertNoThrow(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider))
}
func testRejectsDotDot() {
let uid = currentUID()
let socketPath = "/tmp/rh/../evil.sock"
var map: [String: LStatInfo] = [:]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider))
}
func testNoBroadGlobalShortcutOnlyAllowlist() {
// Ensure implementation does not accept every root-owned path.
// For path /opt/rootdir where /opt is root-owned (simulating arbitrary root path), it must be rejected unless explicitly allowlisted.
let uid = currentUID()
let socketPath = "/opt/rootdir/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/opt": dir(uid: 0, mode: 0o40755), // root owned, not allowlisted
"/opt/rootdir": dir(uid: uid, mode: 0o40700)
]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider),
"Must not accept arbitrary root-owned /opt")
}
func testAllowsPrivateVarChain() {
// macOS: /var -> /private/var, /private/var/tmp etc are platform trusted
let uid = currentUID()
let socketPath = "/private/var/tmp/rh-test/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/private": dir(uid: 0, mode: 0o40755),
"/private/var": dir(uid: 0, mode: 0o40755),
"/private/var/tmp": dir(uid: 0, mode: 0o41777),
"/private/var/tmp/rh-test": dir(uid: uid, mode: 0o40700)
]
let provider: LStatProvider = { map[$0] }
XCTAssertNoThrow(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider))
}
func testRejectsGroupWritableIntermediate() {
let uid = currentUID()
let socketPath = "/Users/\(NSUserName())/Library/Application Support/reyna.sock"
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/Users": dir(uid: 0, mode: 0o40755),
"/Users/\(NSUserName())": dir(uid: uid, mode: 0o40755),
"/Users/\(NSUserName())/Library": dir(uid: uid, mode: 0o40770),
"/Users/\(NSUserName())/Library/Application Support": dir(uid: uid, mode: 0o40700)
]
let provider: LStatProvider = { map[$0] }
XCTAssertThrowsError(try validateParentChainPure(socketPath: socketPath, currentUID: uid, provider: provider),
"Intermediate Library 0770 group writable must be rejected")
}
}