Files
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

127 lines
5.8 KiB
Swift

import XCTest
import Foundation
import Darwin
@testable import ReynaCLIHostCore
final class LStatFailClosedTests: XCTestCase {
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 file(uid: uid_t, mode: mode_t) -> LStatInfo {
// regular file: not symlink, not dir
return LStatInfo(uid: uid, mode: mode_t(mode), isSymlink: false, isDir: false, exists: true)
}
private func currentUID() -> uid_t { getuid() }
// RED: EACCES and ELOOP must not be treated as missing (fail-closed)
func testNonENOENTProviderFailureRejectsWithCode22() {
let uid = currentUID()
let socketPath = "/tmp/rhfail/reyna.sock"
// Map only root trusted; but intermediate component will fail with EACCES
var mapPresent: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/private": dir(uid: 0, mode: 0o40755),
"/private/tmp": dir(uid: 0, mode: 0o41777),
"/tmp": dir(uid: 0, mode: 0o120777, symlink: true)
]
let provider: LStatResultProvider = { p in
if p == "/tmp/rhfail" {
return .failed(errnoCode: EACCES)
}
if let v = mapPresent[p] { return .present(v) }
return .absent
}
XCTAssertThrowsError(try validateParentChainPureResultProvider(socketPath: socketPath, currentUID: uid, provider: provider)) { err in
XCTAssertEqual((err as NSError).code, 22, "EACCES must surface as lstat failure code 22, not absent")
}
let providerLoop: LStatResultProvider = { p in
if p == "/tmp/rhfail" { return .failed(errnoCode: ELOOP) }
if let v = mapPresent[p] { return .present(v) }
return .absent
}
XCTAssertThrowsError(try validateParentChainPureResultProvider(socketPath: socketPath, currentUID: uid, provider: providerLoop)) { err in
XCTAssertEqual((err as NSError).code, 22, "ELOOP must surface as code 22, not treated as ENOENT")
}
}
func testEAccesAnywhereInChainRejects() {
let uid = currentUID()
let socketPath = "/Users/\(NSUserName())/Library/reyna.sock"
let provider: LStatResultProvider = { p in
if p == "/Users" { return .failed(errnoCode: EACCES) }
return .absent
}
XCTAssertThrowsError(try validateParentChainPureResultProvider(socketPath: socketPath, currentUID: uid, provider: provider))
}
// RED: regular file at /tmp or /var must be rejected (old ensureParentDirectories had bug where it skipped directory check for those)
func testRegularFileAtTmpMustReject() throws {
let uid = currentUID()
let socketPath = "/tmp/reyna.sock"
// /tmp exists as regular file (not dir, not symlink)
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/tmp": file(uid: 0, mode: 0o100644) // regular file
]
let provider: LStatResultProvider = { p in
if let v = map[p] { return .present(v) }
return .absent
}
XCTAssertThrowsError(try validateParentChainPureResultProvider(socketPath: socketPath, currentUID: uid, provider: provider), "Regular file at /tmp must reject (not directory)")
// Also test direct single-component validator
XCTAssertThrowsError(try validateSingleLStatInfoOrThrow(path: "/tmp", info: map["/tmp"]!, currentUID: uid))
XCTAssertThrowsError(try validateSingleLStatInfoOrThrow(path: "/var", info: file(uid: 0, mode: 0o100644), currentUID: uid))
}
func testRegularFileAtIntermediateTrustedAliasMustReject() {
let uid = currentUID()
// /private/var exists as file
var map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/private": dir(uid: 0, mode: 0o40755),
"/private/var": file(uid: 0, mode: 0o100644)
]
let provider: LStatResultProvider = { p in
if let v = map[p] { return .present(v) }
return .absent
}
XCTAssertThrowsError(try validateParentChainPureResultProvider(socketPath: "/private/var/tmp/x/reyna.sock", currentUID: uid, provider: provider))
}
func testLiveProviderDoesNotSwallowNonENOENT() {
// liveLStatProvider legacy should now fail-closed sentinel, not nil
// Simulate by calling wrapper directly: we can't easily force EACCES without real FS,
// but we can assert that failed case in result provider is distinct from absent
let absent = LStatResult.absent
let failed = LStatResult.failed(errnoCode: EACCES)
switch absent {
case .absent: break
default: XCTFail()
}
switch failed {
case .failed(let c): XCTAssertEqual(c, EACCES)
default: XCTFail()
}
// legacy provider should return non-nil sentinel for failed case (so caller doesn't treat as missing)
// We test sentinel is non-nil and will be rejected by validator
// The new liveLStatResultProvider is tested via chain above
}
func testTmpSymlinkStillAllowed() throws {
let uid = currentUID()
let map: [String: LStatInfo] = [
"/": dir(uid: 0, mode: 0o40755),
"/private": dir(uid: 0, mode: 0o40755),
"/private/tmp": dir(uid: 0, mode: 0o41777),
"/tmp": dir(uid: 0, mode: 0o120777, symlink: true),
"/tmp/rh-test": dir(uid: uid, mode: 0o40700)
]
let provider: LStatResultProvider = { p in
if let v = map[p] { return .present(v) }
return .absent
}
XCTAssertNoThrow(try validateParentChainPureResultProvider(socketPath: "/tmp/rh-test/reyna.sock", currentUID: uid, provider: provider))
}
}