fix(macos): keep elevation host CUA-free (#125408)

* fix(macos): isolate elevation host from CUA

* fix(macos): fail closed on unsafe elevation rollback

* fix(macos): quarantine unsafe elevation state before recovery

* fix(macos): bind elevation recovery ownership
This commit is contained in:
Peter Steinberger
2026-08-17 17:03:04 -07:00
committed by GitHub
parent a996ea25d9
commit 04c9924c45
15 changed files with 1158 additions and 44 deletions
@@ -190,6 +190,10 @@ struct AppLaunchRuntimePlan: Equatable {
!self.isElevationHost
}
var allowsCuaComputerControl: Bool {
!self.isElevationHost
}
func shouldAutoOpenChat(arguments: [String]) -> Bool {
self.allowsAutomaticPresentation &&
(arguments.contains("--chat") || arguments.contains("--webchat"))
+4 -1
View File
@@ -376,6 +376,7 @@ final class AppState {
self.ifNotPreview {
let computerControlEnabled = isComputerControlEnabled()
let provider = ComputerControlProvider.current()
let launchPlan = AppLaunchRuntimePlan.current
let peekabooBridgeEnabled = self.peekabooBridgeEnabled
self.computerControlHostGeneration &+= 1
let generation = self.computerControlHostGeneration
@@ -389,7 +390,9 @@ final class AppState {
guard generation == self.computerControlHostGeneration else { return }
await CuaDriverHostCoordinator.shared.setEnabled(true)
case .peekaboo:
await CuaDriverHostCoordinator.shared.setEnabled(false)
if launchPlan.allowsCuaComputerControl {
await CuaDriverHostCoordinator.shared.setEnabled(false)
}
guard generation == self.computerControlHostGeneration else { return }
await PeekabooBridgeHostCoordinator.shared.setEnabled(
peekabooBridgeEnabled && computerControlEnabled)
@@ -52,8 +52,10 @@ enum ComputerControlProvider: String, CaseIterable, Sendable {
static func current(
defaults: UserDefaults = AppDefaults.standard,
cuaAvailable: Bool = CuaDriverArtifact.bundledExecutableURL != nil) -> Self
cuaAvailable: Bool = CuaDriverArtifact.bundledExecutableURL != nil,
launchPlan: AppLaunchRuntimePlan = .current) -> Self
{
guard launchPlan.allowsCuaComputerControl else { return .peekaboo }
guard let rawValue = defaults.string(forKey: computerControlProviderKey),
let provider = Self(rawValue: rawValue)
else { return .peekaboo }
@@ -200,6 +200,7 @@ final class CuaDriverHostCoordinator {
static let shared = CuaDriverHostCoordinator(
observeNotifications: true,
enablementAllowed: { AppLaunchRuntimePlan.current.allowsCuaComputerControl },
beforeDaemonStop: {
await MacNodeModeCoordinator.shared.prepareForCuaDaemonStop()
})
@@ -229,6 +230,7 @@ final class CuaDriverHostCoordinator {
private let readinessProbe: ReadinessProbe
private let restartSleep: @Sendable (Duration) async -> Void
private let permissionSnapshot: @MainActor () async -> [Capability: CapabilityAuthorizationStatus]
private let enablementAllowed: @MainActor () -> Bool
private let beforeDaemonStop: @MainActor () async -> Void
private var desiredEnabled = false
@@ -261,6 +263,7 @@ final class CuaDriverHostCoordinator {
permissionSnapshot: @escaping @MainActor () async -> [Capability: CapabilityAuthorizationStatus] = {
await PermissionManager.authorizationStatus([.accessibility, .screenRecording])
},
enablementAllowed: @escaping @MainActor () -> Bool = { true },
beforeDaemonStop: @escaping @MainActor () async -> Void = {})
{
self.notificationCenter = notificationCenter
@@ -271,6 +274,7 @@ final class CuaDriverHostCoordinator {
self.readinessProbe = readinessProbe
self.restartSleep = restartSleep
self.permissionSnapshot = permissionSnapshot
self.enablementAllowed = enablementAllowed
self.beforeDaemonStop = beforeDaemonStop
guard observeNotifications else { return }
@@ -297,12 +301,13 @@ final class CuaDriverHostCoordinator {
}
func setEnabled(_ enabled: Bool) async {
let effectiveEnabled = enabled && self.enablementAllowed()
let wasEnabled = self.desiredEnabled
self.desiredEnabled = enabled
if enabled, !wasEnabled {
self.desiredEnabled = effectiveEnabled
if effectiveEnabled, !wasEnabled {
self.restartAttempt = 0
}
if !enabled {
if !effectiveEnabled {
self.restartTask?.cancel()
self.restartTask = nil
self.restartAttempt = 0
+3 -1
View File
@@ -432,7 +432,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
var nodeTerminationCleanup: @MainActor () async -> Void = {
// CUA shutdown drains the worker before closing the daemon socket; run it
// first so other cleanup cannot consume the app termination deadline.
await CuaDriverHostCoordinator.shared.shutdown()
if AppLaunchRuntimePlan.current.allowsCuaComputerControl {
await CuaDriverHostCoordinator.shared.shutdown()
}
await TalkMLXSpeechSynthesizer.shared.shutdown()
await MacNodeModeCoordinator.shared.stopAndWait()
}
@@ -84,6 +84,7 @@ struct AppLaunchRuntimePlanTests {
#expect(policy.allowsUpdater)
#expect(policy.allowsDockIcon)
#expect(policy.allowsInteractiveServices)
#expect(policy.allowsCuaComputerControl)
#expect(policy.shouldAutoOpenChat(arguments: ["OpenClaw", "--chat"]))
#expect(policy.shouldAutoOpenDashboard(arguments: ["OpenClaw", "--dashboard"]))
}
@@ -99,6 +100,7 @@ struct AppLaunchRuntimePlanTests {
#expect(policy.allowsUpdater)
#expect(policy.allowsDockIcon)
#expect(policy.allowsInteractiveServices)
#expect(policy.allowsCuaComputerControl)
#expect(!policy.shouldAutoOpenChat(arguments: arguments))
#expect(!policy.shouldAutoOpenDashboard(arguments: arguments))
}
@@ -115,6 +117,7 @@ struct AppLaunchRuntimePlanTests {
#expect(!policy.allowsUpdater)
#expect(!policy.allowsDockIcon)
#expect(!policy.allowsInteractiveServices)
#expect(!policy.allowsCuaComputerControl)
#expect(!policy.shouldAutoOpenChat(arguments: arguments))
#expect(!policy.shouldAutoOpenDashboard(arguments: arguments))
#expect(DockIconManager.activationPolicy(
@@ -31,6 +31,24 @@ struct ComputerControlSettingsTests {
#expect(ComputerControlProvider.current(defaults: defaults, cuaAvailable: true) == .peekaboo)
}
@Test func `elevation host ignores enabled CUA defaults while normal launches preserve them`() throws {
let suiteName = "ComputerControlElevationHostTests.\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: suiteName))
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.set(true, forKey: computerControlEnabledKey)
defaults.set(ComputerControlProvider.cua.rawValue, forKey: computerControlProviderKey)
#expect(isComputerControlEnabled(defaults: defaults))
#expect(ComputerControlProvider.current(
defaults: defaults,
cuaAvailable: true,
launchPlan: AppLaunchRuntimePlan(arguments: ["OpenClaw"])) == .cua)
#expect(ComputerControlProvider.current(
defaults: defaults,
cuaAvailable: true,
launchPlan: AppLaunchRuntimePlan(arguments: ["OpenClaw", "--elevation-host"])) == .peekaboo)
}
@Test func `bundled CUA locator accepts only a regular executable and never follows a symlink`() throws {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("openclaw-cua-artifact-\(UUID().uuidString)", isDirectory: true)
@@ -58,6 +58,43 @@ struct CuaDriverHostCoordinatorTests {
#expect(launcher.processes.allSatisfy { !$0.isRunning })
}
@Test func `elevation host refuses CUA enablement before spawning a child`() async throws {
let root = self.shortTemporaryDirectory("elevation-host")
defer { try? FileManager.default.removeItem(at: root) }
let launcher = CuaProcessLauncherProbe()
let coordinator = CuaDriverHostCoordinator(
artifactURL: { root.appendingPathComponent("cua-driver") },
applicationSupportURL: { root },
bundleIdentifier: { "ai.openclaw.test" },
processLauncher: { launch, onTermination in
launcher.launch(launch, onTermination: onTermination)
},
readinessProbe: { _ in true },
enablementAllowed: {
AppLaunchRuntimePlan(arguments: ["OpenClaw", "--elevation-host"]).allowsCuaComputerControl
})
await coordinator.setEnabled(true)
#expect(launcher.launches.isEmpty)
#expect(coordinator.workerEndpoint == nil)
let normalCoordinator = CuaDriverHostCoordinator(
artifactURL: { root.appendingPathComponent("cua-driver") },
applicationSupportURL: { root },
bundleIdentifier: { "ai.openclaw.test" },
processLauncher: { launch, onTermination in
launcher.launch(launch, onTermination: onTermination)
},
readinessProbe: { _ in true },
enablementAllowed: {
AppLaunchRuntimePlan(arguments: ["OpenClaw"]).allowsCuaComputerControl
})
await normalCoordinator.setEnabled(true)
#expect(await self.waitForReadyLaunch(1, launcher: launcher, coordinator: normalCoordinator))
await normalCoordinator.setEnabled(false)
}
@Test func `socket directory is random owner-only and cleanup removes only its owned leaf`() throws {
let root = self.shortTemporaryDirectory("socket")
defer { try? FileManager.default.removeItem(at: root) }
@@ -311,6 +311,36 @@ struct MacNodeHostWorkerTests {
workerManifest: cua) == descriptor)
}
@Test func `elevation host never advertises a persisted CUA provider`() throws {
let suiteName = "MacNodeElevationHostProviderTests.\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: suiteName))
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.set(true, forKey: computerControlEnabledKey)
defaults.set(ComputerControlProvider.cua.rawValue, forKey: computerControlProviderKey)
let provider = ComputerControlProvider.current(
defaults: defaults,
cuaAvailable: true,
launchPlan: AppLaunchRuntimePlan(arguments: ["OpenClaw", "--elevation-host"]))
#expect(provider == .peekaboo)
let cuaDescriptor = OpenClawProtocol.AnyCodable(["provider": "cua"])
let manifest = MacNodeHostManifest(
version: "test",
caps: ["screen", "computer"],
commands: [MacNodeScreenCommand.snapshot.rawValue, OpenClawComputerCommand.act.rawValue],
computerUse: cuaDescriptor,
pathEnv: "/usr/bin:/bin")
let workerManifest = try #require(MacNodeModeCoordinator.workerManifest(manifest, for: provider))
#expect(workerManifest.computerUse == nil)
let advertised = try #require(MacNodeModeCoordinator.computerUseDescriptor(
provider: provider,
commands: manifest.commands,
workerManifest: workerManifest))
let data = try JSONEncoder().encode(advertised)
let object = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect((object["provider"] as? [String: Any])?["id"] as? String == "peekaboo")
}
@Test func `stale route updates cannot replace newer worker authority`() {
#expect(MacNodeHostWorker.routeUpdateIsCurrent(candidateGeneration: 4, currentGeneration: 4))
#expect(MacNodeHostWorker.routeUpdateIsCurrent(candidateGeneration: 5, currentGeneration: 4))
+11
View File
@@ -329,11 +329,21 @@ verify_team_ids() {
fi
}
assert_no_elevation_cua_driver() {
[[ "$SIGNING_VARIANT" == "elevation-host" ]] || return 0
local cua_driver="$APP_BUNDLE/Contents/Resources/cua-driver"
if [[ -e "$cua_driver" || -L "$cua_driver" ]]; then
echo "ERROR: Elevation host must not contain bundled CUA driver: $cua_driver" >&2
exit 1
fi
}
# Sign-time twin of verify_elevation_app in mac-elevation-host.sh, which asserts the same identity
# invariants but requires an already notarized and stapled bundle. Dropping this check defers every
# elevation identity failure until after an Apple notarization submission has been spent.
verify_elevation_signature() {
[[ "$SIGNING_VARIANT" == "elevation-host" ]] || return 0
assert_no_elevation_cua_driver
local actual_team
actual_team="$(team_id_for "$APP_BUNDLE" || true)"
@@ -368,6 +378,7 @@ verify_elevation_signature() {
}
# Sign bundled helper binaries before signing the app bundle.
assert_no_elevation_cua_driver
MLX_TTS_HELPER="$APP_BUNDLE/Contents/MacOS/openclaw-mlx-tts"
if [ -f "$MLX_TTS_HELPER" ]; then
echo "Signing MLX TTS helper"; sign_plain_item "$MLX_TTS_HELPER"
+337 -34
View File
@@ -105,6 +105,9 @@ RECOVERY_CURRENT_RECEIPT_SHA=""
RECOVERY_RESTORED_MIGRATION_IDENTITY=""
RECOVERY_RELAUNCHED_ADOPTED_PID=""
RECOVERY_RESUMED=0
UNSAFE_ELEVATION_APP_QUARANTINE=""
UNSAFE_ELEVATION_APP_WAS_QUARANTINED=0
ELEVATION_APP_OWNER_WAS_EVIDENCED=0
OPENCLAW_CLI=()
fail() {
@@ -360,12 +363,189 @@ verify_universal_machos() {
done < <(find "$app" -type f -perm -111 -print0)
}
elevation_app_is_cua_free() {
local app="$1"
local cua_driver="$app/Contents/Resources/cua-driver"
[[ ! -e "$cua_driver" && ! -L "$cua_driver" ]]
}
elevation_plist_binds_app() {
local plist="$1" args executable label program
[[ -n "$plist" && -f "$plist" && ! -L "$plist" ]] || return 1
label="$(plist_file_value "$plist" Label)"
[[ "$label" == "$ELEVATION_LABEL" ]] || return 1
executable="$APP_PATH/Contents/MacOS/OpenClaw"
if program="$(plutil -extract Program raw -o - "$plist" 2>/dev/null)"; then
[[ "$program" == "$executable" ]] || return 1
elif plutil -extract Program xml1 -o /dev/null "$plist" 2>/dev/null; then
return 1
fi
args="$(plutil -extract ProgramArguments json -o - "$plist" 2>/dev/null)" || return 1
[[ "$(jq -c . <<<"$args")" == \
"$(jq -cn --arg executable "$executable" '[$executable,"--elevation-host"]')" ]]
}
elevation_receipt_binds_app() {
local receipt="$1"
[[ -n "$receipt" && -f "$receipt" && ! -L "$receipt" ]] || return 1
jq -e \
--arg appPath "$APP_PATH" \
--arg plistPath "$PLIST_PATH" '
type == "object" and
.appPath == $appPath and
.plistPath == $plistPath and
(
(.schemaVersion == 3 and .kind == "openclaw-elevation-install") or
(
(has("schemaVersion") | not) and (has("kind") | not) and
keys == ["appPath","archiveSha256","backupPath","peekabooCommit","plistPath","previousPlist","sourceCommit"]
)
)
' "$receipt" >/dev/null 2>&1
}
loaded_elevation_job_binds_app() {
local snapshot program
snapshot="$(job_snapshot "$job_domain")"
[[ -n "$snapshot" ]] || return 1
program="$(awk -F' = ' '/^[[:space:]]*program = / {print $2; exit}' <<<"$snapshot")"
[[ "$program" == "$APP_PATH/Contents/MacOS/OpenClaw" ]] || return 1
grep -Eq '^[[:space:]]*--elevation-host[[:space:]]*$' <<<"$snapshot"
}
elevation_ownership_is_evidenced() {
local candidate
loaded_elevation_job_binds_app && return 0
for candidate in "$PLIST_PATH" "$ROLLBACK_ELEVATION_PLIST" "$RECOVERY_CURRENT_PLIST"; do
elevation_plist_binds_app "$candidate" && return 0
done
for candidate in \
"$RECEIPT_PATH" \
"$FINAL_RECEIPT_PATH" \
"$PENDING_RECEIPT_PATH" \
"$ROLLBACK_INSTALL_RECEIPT" \
"$RECOVERY_CURRENT_RECEIPT"
do
elevation_receipt_binds_app "$candidate" && return 0
done
return 1
}
quarantine_elevation_plist() {
local description="$1" quarantine_path source_identity source_sha="" source_kind
[[ -e "$PLIST_PATH" || -L "$PLIST_PATH" ]] || return 0
source_identity="$(path_identity "$PLIST_PATH")" || return 1
if [[ -f "$PLIST_PATH" && ! -L "$PLIST_PATH" ]]; then
source_kind="file"
source_sha="$(shasum -a 256 "$PLIST_PATH" | awk '{print $1}')" || return 1
[[ "$source_sha" =~ ^[0-9a-f]{64}$ ]] || return 1
elif [[ -L "$PLIST_PATH" ]]; then
source_kind="symlink"
else
return 1
fi
quarantine_path="$(mktemp -u "$STATE_DIR/elevation-host.quarantined-launch-agent.XXXXXX")" || return 1
[[ ! -e "$quarantine_path" && ! -L "$quarantine_path" ]] || return 1
rename_app_exclusively "$PLIST_PATH" "$quarantine_path" || return 1
[[ ! -e "$PLIST_PATH" && ! -L "$PLIST_PATH" ]] || return 1
[[ "$(path_identity "$quarantine_path")" == "$source_identity" ]] || return 1
if [[ "$source_kind" == "file" ]]; then
backup_file_matches "$quarantine_path" "$source_sha" || return 1
else
[[ -L "$quarantine_path" ]] || return 1
fi
fsync_parent "$PLIST_PATH" || return 1
printf 'Quarantined %s elevation LaunchAgent outside launchd discovery at %s\n' \
"$description" "$quarantine_path" >&2
}
ensure_elevation_job_absent() {
local state
state="$(job_loaded_state "$job_domain")"
if [[ "$state" != "absent" ]]; then
launchctl bootout "$job_domain" >/dev/null 2>&1 || true
state="$(job_loaded_state "$job_domain")"
fi
[[ "$state" == "absent" ]]
}
neutralize_unsafe_elevation_launch_agent() {
local description="$1" evidence_path="$2" evidence_sha="$3" neutralization_failed=0
if ! quarantine_elevation_plist "$description"; then
if [[ -e "$PLIST_PATH" || -L "$PLIST_PATH" ]]; then
rm -f -- "$PLIST_PATH" || neutralization_failed=1
fi
fsync_parent "$PLIST_PATH" || neutralization_failed=1
printf 'Removed unquarantinable %s elevation LaunchAgent; exact evidence remains at %s\n' \
"$description" "$evidence_path" >&2
fi
[[ ! -e "$PLIST_PATH" && ! -L "$PLIST_PATH" ]] || neutralization_failed=1
ensure_elevation_job_absent || neutralization_failed=1
if [[ -n "$evidence_path" ]]; then
backup_file_matches "$evidence_path" "$evidence_sha" || neutralization_failed=1
fi
[[ "$neutralization_failed" == "0" ]]
}
quarantine_entry_unsafe_elevation_app() {
local app_identity app_kind quarantine_container quarantine_app quarantine_failed=0
ELEVATION_APP_OWNER_WAS_EVIDENCED=0
elevation_ownership_is_evidenced || return 0
# Keep this fact across neutralization: launchd and its plist may be gone before
# rollback decides whether the displaced CUA-bearing app can return to APP_PATH.
ELEVATION_APP_OWNER_WAS_EVIDENCED=1
[[ -e "$APP_PATH" || -L "$APP_PATH" ]] || return 0
elevation_app_is_cua_free "$APP_PATH" && return 0
neutralize_unsafe_elevation_launch_agent 'entry for unsafe elevation app' '' '' ||
quarantine_failed=1
if [[ -L "$APP_PATH" ]]; then
app_kind="symlink"
app_identity="$(path_identity "$APP_PATH")" || quarantine_failed=1
elif [[ -d "$APP_PATH" ]]; then
app_kind="bundle"
app_identity="$(durable_path_identity "$APP_PATH")" || quarantine_failed=1
else
quarantine_failed=1
fi
if [[ "$quarantine_failed" == "0" ]]; then
if quarantine_container="$(mktemp -d "$STATE_DIR/elevation-host.quarantined-app.XXXXXX")"; then
quarantine_app="$quarantine_container/OpenClaw.app"
rename_app_exclusively "$APP_PATH" "$quarantine_app" || quarantine_failed=1
if path_matches_identity "$quarantine_app" "$app_identity" &&
{ [[ "$app_kind" == "symlink" && -L "$quarantine_app" ]] ||
{ [[ "$app_kind" == "bundle" && -d "$quarantine_app" && ! -L "$quarantine_app" ]] &&
! elevation_app_is_cua_free "$quarantine_app"; }; } &&
[[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]]
then
UNSAFE_ELEVATION_APP_QUARANTINE="$quarantine_app"
UNSAFE_ELEVATION_APP_WAS_QUARANTINED=1
fsync_parent "$APP_PATH" || quarantine_failed=1
else
quarantine_failed=1
fi
else
quarantine_failed=1
fi
fi
[[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] || quarantine_failed=1
[[ ! -e "$PLIST_PATH" && ! -L "$PLIST_PATH" ]] || quarantine_failed=1
if [[ "$UNSAFE_ELEVATION_APP_WAS_QUARANTINED" == "1" ]]; then
printf 'Quarantined CUA-bearing elevation app outside its launchd path at %s\n' \
"$UNSAFE_ELEVATION_APP_QUARANTINE" >&2
fi
[[ "$quarantine_failed" == "0" ]]
}
# Canonical elevation identity check: a strict superset of verify_elevation_signature in
# codesign-mac-app.sh, and the only one that runs post-notarization and on the target Mac at install
# time. Dropping it lets the portable installer accept an archive nobody re-verified after signing.
verify_elevation_app() {
local app="$1"
[[ -d "$app" && ! -L "$app" ]] || fail "elevation app not found or symlinked: $app"
local cua_driver="$app/Contents/Resources/cua-driver"
elevation_app_is_cua_free "$app" ||
fail "elevation app must not contain bundled CUA driver: $cua_driver"
[[ "$(plist_value "$app" CFBundleIdentifier)" == "$EXPECTED_BUNDLE_ID" ]] ||
fail "elevation app bundle id must be $EXPECTED_BUNDLE_ID"
local source_commit peekaboo_commit
@@ -531,6 +711,7 @@ restore_install_receipt_after_rollback() {
"$ROLLBACK_INSTALL_RECEIPT_SHA" \
600
elif [[ -e "$RECEIPT_PATH" || -L "$RECEIPT_PATH" ]]; then
[[ "$ELEVATION_APP_OWNER_WAS_EVIDENCED" == "1" ]] || return 1
[[ -f "$RECEIPT_PATH" && ! -L "$RECEIPT_PATH" ]] || return 1
rm "$RECEIPT_PATH"
fi
@@ -2163,12 +2344,28 @@ install_host() {
recover_install() {
local recovery_failed=0 elevation_state restored_state prior_owner_state
local unsafe_previous_elevation=0 rollback_app_candidate=""
quarantine_entry_unsafe_elevation_app || return 1
[[ "$UNSAFE_ELEVATION_APP_WAS_QUARANTINED" == "0" ]] || recovery_failed=1
if [[ "$CUTOVER_APP_MUTATED" == "1" && -n "$ROLLBACK_APP_PATH" ]]; then
verify_recorded_rollback_app "$APP_PATH" ||
verify_recorded_rollback_app "$ROLLBACK_APP_PATH" || return 1
fi
[[ -z "$ROLLBACK_ELEVATION_PLIST" ]] ||
backup_file_matches "$ROLLBACK_ELEVATION_PLIST" "$ROLLBACK_ELEVATION_PLIST_SHA" || return 1
if [[ "$ELEVATION_APP_OWNER_WAS_EVIDENCED" == "1" && -n "$ROLLBACK_APP_PATH" ]]
then
if [[ -d "$ROLLBACK_APP_PATH" && ! -L "$ROLLBACK_APP_PATH" ]] &&
! elevation_app_is_cua_free "$ROLLBACK_APP_PATH"
then
rollback_app_candidate="$ROLLBACK_APP_PATH"
elif [[ -d "$APP_PATH" && ! -L "$APP_PATH" ]] && ! elevation_app_is_cua_free "$APP_PATH"; then
rollback_app_candidate="$APP_PATH"
fi
[[ -z "$rollback_app_candidate" ]] || unsafe_previous_elevation=1
fi
if [[ "$unsafe_previous_elevation" == "0" ]]; then
[[ -z "$ROLLBACK_ELEVATION_PLIST" ]] ||
backup_file_matches "$ROLLBACK_ELEVATION_PLIST" "$ROLLBACK_ELEVATION_PLIST_SHA" || return 1
fi
[[ -z "$ROLLBACK_MIGRATION_PLIST" ]] ||
backup_file_matches "$ROLLBACK_MIGRATION_PLIST" "$ROLLBACK_MIGRATION_PLIST_SHA" || return 1
[[ -z "$ROLLBACK_INSTALL_RECEIPT" ]] ||
@@ -2221,7 +2418,21 @@ recover_install() {
[[ -n "$ROLLBACK_APP_PATH" ]] &&
verify_recorded_rollback_app "$APP_PATH" || return 1
fi
if [[ -n "$ROLLBACK_APP_PATH" && -d "$ROLLBACK_APP_PATH" ]]; then
if [[ "$unsafe_previous_elevation" == "1" ]]; then
if [[ -d "$ROLLBACK_APP_PATH" && ! -L "$ROLLBACK_APP_PATH" ]]; then
verify_recorded_rollback_app "$ROLLBACK_APP_PATH" || recovery_failed=1
[[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] || recovery_failed=1
elif verify_recorded_rollback_app "$APP_PATH"; then
[[ ! -e "$ROLLBACK_APP_PATH" && ! -L "$ROLLBACK_APP_PATH" ]] || recovery_failed=1
if [[ "$recovery_failed" == "0" ]]; then
rename_app_exclusively "$APP_PATH" "$ROLLBACK_APP_PATH" || true
verify_recorded_rollback_app "$ROLLBACK_APP_PATH" || recovery_failed=1
[[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] || recovery_failed=1
fi
else
recovery_failed=1
fi
elif [[ -n "$ROLLBACK_APP_PATH" && -d "$ROLLBACK_APP_PATH" ]]; then
[[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] || return 1
rename_app_exclusively "$ROLLBACK_APP_PATH" "$APP_PATH" || true
verify_recorded_rollback_app "$APP_PATH" || recovery_failed=1
@@ -2229,23 +2440,42 @@ recover_install() {
elif [[ -n "$ROLLBACK_APP_PATH" ]]; then
verify_recorded_rollback_app "$APP_PATH" || recovery_failed=1
fi
if [[ -n "$ROLLBACK_ELEVATION_PLIST" && -f "$ROLLBACK_ELEVATION_PLIST" ]]; then
restore_file_atomically \
"$ROLLBACK_ELEVATION_PLIST" \
"$PLIST_PATH" \
"$ROLLBACK_ELEVATION_PLIST_SHA" \
644 || recovery_failed=1
restored_state="$(job_loaded_state "$job_domain")"
[[ "$restored_state" != 'unknown' ]] || recovery_failed=1
if [[ "$recovery_failed" == "0" &&
"$ROLLBACK_ELEVATION_WAS_LOADED" == "1" && "$restored_state" == 'absent' ]]
then
launchctl bootstrap "$launch_domain" "$PLIST_PATH" >/dev/null 2>&1 || recovery_failed=1
elif [[ "$ROLLBACK_ELEVATION_WAS_LOADED" == "0" && "$restored_state" != 'absent' ]]; then
recovery_failed=1
if [[ "$unsafe_previous_elevation" == "1" ]]; then
if [[ -n "$ROLLBACK_ELEVATION_PLIST" ]]; then
neutralize_unsafe_elevation_launch_agent \
'replacement for unsafe previous' \
"$ROLLBACK_ELEVATION_PLIST" \
"$ROLLBACK_ELEVATION_PLIST_SHA" || recovery_failed=1
printf 'Preserved previous elevation app with bundled CUA driver at %s and LaunchAgent evidence at %s; refusing to restore it as elevation host\n' \
"$ROLLBACK_APP_PATH" "$ROLLBACK_ELEVATION_PLIST" >&2
else
neutralize_unsafe_elevation_launch_agent \
'replacement for unsafe previous' \
'' \
'' || recovery_failed=1
printf 'Preserved previous elevation app with bundled CUA driver at %s; no prior elevation LaunchAgent was recorded, and it will not be restored as elevation host\n' \
"$ROLLBACK_APP_PATH" >&2
fi
else
recovery_failed=1
elif [[ -n "$ROLLBACK_ELEVATION_PLIST" && -f "$ROLLBACK_ELEVATION_PLIST" ]]; then
restore_file_atomically \
"$ROLLBACK_ELEVATION_PLIST" \
"$PLIST_PATH" \
"$ROLLBACK_ELEVATION_PLIST_SHA" \
644 || recovery_failed=1
restored_state="$(job_loaded_state "$job_domain")"
[[ "$restored_state" != 'unknown' ]] || recovery_failed=1
if [[ "$recovery_failed" == "0" &&
"$ROLLBACK_ELEVATION_WAS_LOADED" == "1" && "$restored_state" == 'absent' ]]
then
launchctl bootstrap "$launch_domain" "$PLIST_PATH" >/dev/null 2>&1 || recovery_failed=1
elif [[ "$ROLLBACK_ELEVATION_WAS_LOADED" == "0" && "$restored_state" != 'absent' ]]; then
recovery_failed=1
fi
elif [[ "$ELEVATION_APP_OWNER_WAS_EVIDENCED" == "1" ]]; then
[[ ! -f "$PLIST_PATH" ]] || rm -f "$PLIST_PATH" || recovery_failed=1
elif [[ -e "$PLIST_PATH" || -L "$PLIST_PATH" ]]; then
recovery_failed=1
fi
if [[ -n "$ROLLBACK_MIGRATION_SOURCE" && -f "$ROLLBACK_MIGRATION_PLIST" ]]; then
if [[ "$CUTOVER_MIGRATION_REMOVED" != "1" &&
@@ -2365,6 +2595,13 @@ recover_install() {
restore_current_generation_after_recovery_failure() {
local restore_failed=0 app_restore_failed=0 state
local unsafe_current_elevation=0 current_app_evidence_path=""
quarantine_entry_unsafe_elevation_app || return 1
if [[ "$UNSAFE_ELEVATION_APP_WAS_QUARANTINED" == "1" ]]; then
unsafe_current_elevation=1
current_app_evidence_path="$UNSAFE_ELEVATION_APP_QUARANTINE"
app_restore_failed=1
fi
if [[ "$RECOVERY_RELAUNCHED_ADOPTED_PID" =~ ^[0-9]+$ ]]; then
ADOPTION_PID="$RECOVERY_RELAUNCHED_ADOPTED_PID"
@@ -2414,7 +2651,35 @@ restore_current_generation_after_recovery_failure() {
[[ "$(job_loaded_state "$launch_domain/$ROLLBACK_MIGRATION_LABEL")" == "absent" ]] || return 1
fi
if [[ "$RECOVERY_CURRENT_APP_STATE" == "absent" ]]; then
if [[ "$unsafe_current_elevation" == "0" &&
( "$RECOVERY_CURRENT_APP_STATE" == "valid" ||
"$RECOVERY_CURRENT_APP_STATE" == "damaged" ) ]]
then
if [[ -n "$RECOVERED_FAILED_APP_PATH" && -d "$RECOVERED_FAILED_APP_PATH" &&
! -L "$RECOVERED_FAILED_APP_PATH" ]] &&
! elevation_app_is_cua_free "$RECOVERED_FAILED_APP_PATH"
then
unsafe_current_elevation=1
current_app_evidence_path="$RECOVERED_FAILED_APP_PATH"
elif [[ -d "$APP_PATH" && ! -L "$APP_PATH" ]] && ! elevation_app_is_cua_free "$APP_PATH"; then
unsafe_current_elevation=1
if preserve_current_app_for_recovery 'unsafe current elevation app'; then
current_app_evidence_path="$RECOVERED_FAILED_APP_PATH"
elif [[ -n "$RECOVERED_FAILED_APP_PATH" ]] &&
verify_recorded_current_app "$RECOVERED_FAILED_APP_PATH"
then
current_app_evidence_path="$RECOVERED_FAILED_APP_PATH"
app_restore_failed=1
else
current_app_evidence_path="$APP_PATH"
app_restore_failed=1
fi
fi
fi
if [[ "$unsafe_current_elevation" == "1" && "$app_restore_failed" != "0" ]]; then
: # Preserve the unsafe classification and skip every normal restoration path.
elif [[ "$RECOVERY_CURRENT_APP_STATE" == "absent" ]]; then
if [[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]]; then
[[ -z "$ROLLBACK_APP_PATH" ]] ||
verify_recorded_rollback_app "$ROLLBACK_APP_PATH" || app_restore_failed=1
@@ -2439,14 +2704,19 @@ restore_current_generation_after_recovery_failure() {
! -L "$RECOVERED_FAILED_APP_PATH" ]] &&
path_matches_identity "$RECOVERED_FAILED_APP_PATH" "$RECOVERY_CURRENT_APP_IDENTITY"
then
rename_app_exclusively "$RECOVERED_FAILED_APP_PATH" "$APP_PATH" || true
path_matches_identity "$APP_PATH" "$RECOVERY_CURRENT_APP_IDENTITY" || app_restore_failed=1
if [[ "$app_restore_failed" == "0" && "$RECOVERY_CURRENT_APP_STATE" == "valid" ]]; then
verify_recorded_current_app "$APP_PATH" || app_restore_failed=1
if [[ "$unsafe_current_elevation" == "1" ]]; then
verify_recorded_current_app "$RECOVERED_FAILED_APP_PATH" || app_restore_failed=1
[[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] || app_restore_failed=1
else
rename_app_exclusively "$RECOVERED_FAILED_APP_PATH" "$APP_PATH" || true
path_matches_identity "$APP_PATH" "$RECOVERY_CURRENT_APP_IDENTITY" || app_restore_failed=1
if [[ "$app_restore_failed" == "0" && "$RECOVERY_CURRENT_APP_STATE" == "valid" ]]; then
verify_recorded_current_app "$APP_PATH" || app_restore_failed=1
fi
[[ ! -e "$RECOVERED_FAILED_APP_PATH" && ! -L "$RECOVERED_FAILED_APP_PATH" ]] ||
app_restore_failed=1
rmdir "$(dirname "$RECOVERED_FAILED_APP_PATH")" 2>/dev/null || true
fi
[[ ! -e "$RECOVERED_FAILED_APP_PATH" && ! -L "$RECOVERED_FAILED_APP_PATH" ]] ||
app_restore_failed=1
rmdir "$(dirname "$RECOVERED_FAILED_APP_PATH")" 2>/dev/null || true
else
app_restore_failed=1
fi
@@ -2454,18 +2724,49 @@ restore_current_generation_after_recovery_failure() {
case "$RECOVERY_CURRENT_APP_STATE" in
absent) [[ ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] || app_restore_failed=1 ;;
damaged)
[[ -d "$APP_PATH" && ! -L "$APP_PATH" ]] &&
path_matches_identity "$APP_PATH" "$RECOVERY_CURRENT_APP_IDENTITY" || app_restore_failed=1
if [[ "$unsafe_current_elevation" == "1" ]]; then
[[ -n "$current_app_evidence_path" && ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] &&
path_matches_identity "$current_app_evidence_path" "$RECOVERY_CURRENT_APP_IDENTITY" &&
verify_recorded_current_app "$current_app_evidence_path" || app_restore_failed=1
else
[[ -d "$APP_PATH" && ! -L "$APP_PATH" ]] &&
path_matches_identity "$APP_PATH" "$RECOVERY_CURRENT_APP_IDENTITY" || app_restore_failed=1
fi
;;
valid)
path_matches_identity "$APP_PATH" "$RECOVERY_CURRENT_APP_IDENTITY" &&
verify_recorded_current_app "$APP_PATH" || app_restore_failed=1
if [[ "$unsafe_current_elevation" == "1" ]]; then
[[ -n "$current_app_evidence_path" && ! -e "$APP_PATH" && ! -L "$APP_PATH" ]] &&
path_matches_identity "$current_app_evidence_path" "$RECOVERY_CURRENT_APP_IDENTITY" &&
verify_recorded_current_app "$current_app_evidence_path" || app_restore_failed=1
else
path_matches_identity "$APP_PATH" "$RECOVERY_CURRENT_APP_IDENTITY" &&
verify_recorded_current_app "$APP_PATH" || app_restore_failed=1
fi
;;
*) app_restore_failed=1 ;;
esac
[[ "$app_restore_failed" == "0" ]] || return 1
if [[ "$app_restore_failed" != "0" && "$unsafe_current_elevation" == "0" ]]; then
return 1
fi
if [[ -n "$RECOVERY_CURRENT_PLIST" ]]; then
if [[ "$unsafe_current_elevation" == "1" ]]; then
if [[ -n "$RECOVERY_CURRENT_PLIST" ]]; then
neutralize_unsafe_elevation_launch_agent \
'replacement for unsafe current' \
"$RECOVERY_CURRENT_PLIST" \
"$RECOVERY_CURRENT_PLIST_SHA" || restore_failed=1
printf 'Preserved current elevation app with bundled CUA driver at %s and LaunchAgent evidence at %s; refusing to restore it as elevation host\n' \
"$current_app_evidence_path" "$RECOVERY_CURRENT_PLIST" >&2
else
neutralize_unsafe_elevation_launch_agent \
'replacement for unsafe current' \
'' \
'' || restore_failed=1
printf 'Preserved current elevation app with bundled CUA driver at %s; no current elevation LaunchAgent was recorded, and it will not be restored as elevation host\n' \
"$current_app_evidence_path" >&2
fi
restore_failed=1
elif [[ -n "$RECOVERY_CURRENT_PLIST" ]]; then
restore_file_atomically \
"$RECOVERY_CURRENT_PLIST" \
"$PLIST_PATH" \
@@ -2686,6 +2987,10 @@ recover_host() {
fail 'recovery migration transaction binding is invalid'
RECOVERY_RESTORED_MIGRATION_IDENTITY="$migration_identity"
fi
if [[ "$INSTALL_RECEIPT_SCHEMA" != "legacy" ]]; then
RECOVERY_CURRENT_APP_CDHASH_ARM64="$(jq -r '.cdhashes.arm64' "$RECEIPT_PATH")"
RECOVERY_CURRENT_APP_CDHASH_X86_64="$(jq -r '.cdhashes.x86_64' "$RECEIPT_PATH")"
fi
if [[ "$INSTALL_RECEIPT_SCHEMA" == 'legacy' || "$RECOVERY_CURRENT_APP_STATE" != "valid" ||
("$RECOVERY_PENDING_INSTALL" == "1" && "$current_app_matches_receipt" != "1") ]]
then
@@ -2713,8 +3018,6 @@ recover_host() {
[[ -z "$ARCHIVE" && -z "$ARTIFACT_RECEIPT" && -z "$EXPECTED_ARTIFACT_RECEIPT_SHA256" ]] ||
fail 'artifact helper inputs are valid only for legacy recovery'
CONFIG_PATH="$(jq -r '.configPath' "$RECEIPT_PATH")"
RECOVERY_CURRENT_APP_CDHASH_ARM64="$(jq -r '.cdhashes.arm64' "$RECEIPT_PATH")"
RECOVERY_CURRENT_APP_CDHASH_X86_64="$(jq -r '.cdhashes.x86_64' "$RECEIPT_PATH")"
prepare_current_app_rename_helper
fi
if [[ "$pending_migration_identity_needs_record" == "1" ]]; then
+14 -2
View File
@@ -25,6 +25,14 @@ MLX_TTS_HELPER_BUILD_ROOT="$MLX_TTS_HELPER_ROOT/.build"
BUNDLE_ID="${BUNDLE_ID:-ai.openclaw.mac.debug}"
PKG_VERSION="$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")"
BUILD_CONFIG="${BUILD_CONFIG:-debug}"
SIGNING_VARIANT="${OPENCLAW_MAC_SIGNING_VARIANT:-standard}"
case "$SIGNING_VARIANT" in
standard | elevation-host) ;;
*)
echo "ERROR: Unknown OPENCLAW_MAC_SIGNING_VARIANT value: $SIGNING_VARIANT (use standard|elevation-host)" >&2
exit 1
;;
esac
# OPENCLAW_SKIP_MLX_TTS=1 packages the app without the local MLX voice helper.
# The helper pulls in the full mlx-swift Metal shader stack, which some beta
# Xcode toolchains cannot compile (flaky `metal` diagnostics), needlessly
@@ -886,8 +894,12 @@ fi
rm -rf "$APP_ROOT/Contents/Resources/ProviderIcons"
cp -R "$PROVIDER_ICONS_SRC" "$APP_ROOT/Contents/Resources/ProviderIcons"
echo "🖥 Staging embedded CUA driver"
"$ROOT_DIR/scripts/stage-cua-driver-macos.sh" "$APP_ROOT/Contents/Resources/cua-driver"
if [[ "$SIGNING_VARIANT" == "elevation-host" ]]; then
echo "🖥 Omitting embedded CUA driver from elevation-host package"
else
echo "🖥 Staging embedded CUA driver"
"$ROOT_DIR/scripts/stage-cua-driver-macos.sh" "$APP_ROOT/Contents/Resources/cua-driver"
fi
echo "📦 Copying CLI installer"
INSTALL_CLI_SRC="$ROOT_DIR/scripts/install-cli.sh"
+34
View File
@@ -6,6 +6,7 @@ import {
mkdirSync,
readFileSync,
readdirSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import path from "node:path";
@@ -294,6 +295,39 @@ describe("codesign-mac-app temp file hygiene", () => {
expect(script).toContain('assert_no_apple_events_entitlement "$APP_BUNDLE"');
});
it.each(["file", "symlink"])("rejects an elevation-host CUA driver %s before signing", (kind) => {
const tempRoot = tempDirs.make(`openclaw-codesign-elevation-cua-${kind}-`);
const app = path.join(tempRoot, "Fake.app");
const binDir = path.join(tempRoot, "bin");
const resources = path.join(app, "Contents", "Resources");
mkdirSync(path.join(app, "Contents", "MacOS"), { recursive: true });
mkdirSync(resources, { recursive: true });
mkdirSync(binDir);
writeFileSync(path.join(app, "Contents", "MacOS", "OpenClaw"), "#!/bin/sh\n");
const cuaDriver = path.join(resources, "cua-driver");
if (kind === "file") {
writeFileSync(cuaDriver, "driver\n");
} else {
symlinkSync("/missing/cua-driver", cuaDriver);
}
installElevationFakeCodesign(binDir);
const result = spawnSync("bash", [scriptPath, app], {
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
OPENCLAW_MAC_SIGNING_VARIANT: "elevation-host",
PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}`,
SIGN_IDENTITY: "Developer ID Application: OpenClaw Foundation (FWJYW4S8P8)",
TMPDIR: tempRoot,
},
});
expect(result.status).toBe(1);
expect(result.stderr).toContain("must not contain bundled CUA driver");
});
it("consumes complete codesign metadata under pipefail before validating authority", () => {
const tempRoot = tempDirs.make("openclaw-codesign-elevation-metadata-");
const app = path.join(tempRoot, "Fake.app");
+631 -2
View File
@@ -57,6 +57,45 @@ function receiptDigestArgs(receiptPath: string): string[] {
return ["--receipt-sha256", sha256(readFileSync(receiptPath))];
}
function quarantinedElevationAppPath(stateDir: string): string | undefined {
const container = readdirSync(stateDir).find((name) =>
name.startsWith("elevation-host.quarantined-app."),
);
return container ? path.join(stateDir, container, "OpenClaw.app") : undefined;
}
function preservedCuaAppPath(harness: {
appPath: string;
env: NodeJS.ProcessEnv;
stateDir: string;
}): string | undefined {
const quarantined = quarantinedElevationAppPath(harness.stateDir);
if (quarantined) {
const driver = path.join(quarantined, "Contents", "Resources", "cua-driver");
if (existsSync(driver) || lstatSync(driver, { throwIfNoEntry: false })?.isSymbolicLink()) {
return quarantined;
}
}
const home = harness.env.HOME!;
for (const entry of readdirSync(home)) {
if (
!entry.startsWith(`${path.basename(harness.appPath)}.rollback-elevation-host-`) &&
!entry.startsWith(`${path.basename(harness.appPath)}.failed-elevation-host-`)
) {
continue;
}
const container = path.join(home, entry);
const candidate = entry.includes(".failed-elevation-host-")
? path.join(container, "OpenClaw.app")
: container;
const driver = path.join(candidate, "Contents", "Resources", "cua-driver");
if (existsSync(driver) || lstatSync(driver, { throwIfNoEntry: false })?.isSymbolicLink()) {
return candidate;
}
}
return undefined;
}
function runInstaller(
installerPath: string,
args: string[],
@@ -536,7 +575,13 @@ function createArtifactVerificationHarness() {
' : >"$TEST_PENDING_KILL_MARKER"',
' kill -KILL "$PPID"',
"fi",
'if [ "${1:-}" = "--elevation-sync-file" ] && [ "${TEST_CORRUPT_ROLLBACK_PLIST_BACKUP_ON_SYNC:-0}" = "1" ] && echo "${2:-}" | grep -q \'elevation-host[.]previous-plist[.]\'; then',
" printf '%s\\n' corrupt >\"$2\"",
"fi",
'if [ "${1:-}" = "--elevation-rename-exclusive" ]; then',
' if [ "${TEST_FAIL_ELEVATION_QUARANTINE_RENAME:-0}" = "1" ] && echo "$3" | grep -q \'elevation-host[.]quarantined-launch-agent[.]\'; then',
" exit 7",
" fi",
' if [ "${TEST_DANGLING_ROLLBACK_DURING_MOVE:-0}" = "1" ] && echo "$3" | grep -q \'[.]rollback-elevation-host-\'; then',
' ln -s /missing/openclaw-rollback-target "$3"',
" fi",
@@ -570,6 +615,12 @@ function createArtifactVerificationHarness() {
" fi",
' if [ -e "$3" ] || [ -L "$3" ]; then exit 1; fi',
' /bin/mv "$2" "$3" || exit $?',
' if [ "${TEST_REMOVE_CUA_DRIVER_AFTER_UNSAFE_ENTRY_MOVE:-0}" = "1" ] && echo "$3" | grep -q \'elevation-host[.]quarantined-app[.].*[/]OpenClaw[.]app$\' && [ -L "$3" ]; then',
' /bin/rm -f -- "$(readlink "$3")/Contents/Resources/cua-driver"',
" fi",
' if [ "${TEST_RELOAD_ELEVATION_AFTER_QUARANTINE:-0}" = "1" ] && echo "$3" | grep -q \'elevation-host[.]quarantined-launch-agent[.]\'; then',
" printf '%s\\n' elevation-loaded >\"$TEST_LAUNCH_STATE_FILE\"",
" fi",
' if [ "${TEST_KILL_AFTER_INITIAL_MIGRATION_CUSTODY:-0}" = "1" ] && echo "$3" | grep -q \'[.]custody[.]\'; then',
' kill -KILL "$PPID"',
" fi",
@@ -595,6 +646,12 @@ function createArtifactVerificationHarness() {
"APP_HELPER",
'printf helper >"$app/Contents/MacOS/openclaw-mlx-tts"',
'chmod 755 "$app/Contents/MacOS/OpenClaw" "$app/Contents/MacOS/openclaw-mlx-tts"',
'case "${TEST_CUA_DRIVER_KIND:-none}" in',
' file) mkdir -p "$app/Contents/Resources"; printf driver >"$app/Contents/Resources/cua-driver"; chmod 755 "$app/Contents/Resources/cua-driver" ;;',
' symlink) mkdir -p "$app/Contents/Resources"; ln -s /missing/cua-driver "$app/Contents/Resources/cua-driver" ;;',
" none) ;;",
" *) exit 64 ;;",
"esac",
"",
].join("\n"),
);
@@ -613,6 +670,9 @@ function createArtifactVerificationHarness() {
'if [[ "$*" == *"--verify"* && "${TEST_FINAL_SIGNATURE_INVALID:-0}" == "1" && "$target" == "${TEST_INSTALLED_APP_PATH:-}" && -f "${TEST_LAUNCH_STATE_FILE:-}" && "$(tr -d \'\\n\' <"$TEST_LAUNCH_STATE_FILE")" == "elevation-loaded" ]]; then',
" exit 1",
"fi",
'if [[ "$*" == *"--verify"* && "${TEST_CURRENT_CUA_SIGNATURE_INVALID:-0}" == "1" && "$target" == "${TEST_INSTALLED_APP_PATH:-}" && ( -e "$target/Contents/Resources/cua-driver" || -L "$target/Contents/Resources/cua-driver" ) ]]; then',
" exit 1",
"fi",
'if [[ "$*" == *"--verify"* && -e "$target/Contents/invalid-signature" ]]; then',
" exit 1",
"fi",
@@ -693,6 +753,8 @@ function createArtifactVerificationHarness() {
function createInstallRollbackHarness(
options: {
danglingRollbackDuringMove?: boolean;
corruptRollbackPlistBackupOnSync?: boolean;
currentCuaSignatureInvalid?: boolean;
failCurrentReceiptRestoreCopy?: boolean;
failAfterReceiptCommitMove?: boolean;
failRecoveryXattrRead?: boolean;
@@ -700,8 +762,11 @@ function createInstallRollbackHarness(
finalSignatureInvalid?: boolean;
failLsofInspection?: boolean;
failPgrepInspection?: boolean;
failUnsafeEntryIdentity?: boolean;
failUnsafeEntryMktemp?: boolean;
hupDuringCustody?: boolean;
launchdBootstrapFails?: boolean;
failElevationQuarantineRename?: boolean;
killDuringMigrationRestoreBootstrapOnce?: boolean;
killAfterMigrationRestoreBootstrapOnce?: boolean;
killAfterInitialMigrationCustody?: boolean;
@@ -709,7 +774,9 @@ function createInstallRollbackHarness(
killAfterRollbackAppCustody?: boolean;
migrationRestoreBootstrapFails?: boolean;
raceMigrationCustodyDestination?: boolean;
reloadElevationAfterQuarantine?: boolean;
removeInstalledExecutableAfterReadiness?: boolean;
existingElevationLoaded?: boolean;
recreateAppDuringDamagedCustody?: boolean;
recreateSourceDuringBootout?: boolean;
recreateSourceOnFailure?: boolean;
@@ -720,6 +787,8 @@ function createInstallRollbackHarness(
replaceMigrationSourceSameContentBeforeInitialCustody?: boolean;
restartAppDuringBootout?: boolean;
rollbackNonNativeSignatureInvalid?: boolean;
rollbackCuaDriverKind?: "file" | "symlink";
removeCuaDriverAfterUnsafeEntryMove?: boolean;
signalDuringCustody?: boolean;
signalDuringRecoveryAppMove?: boolean;
signalDuringReceiptCommit?: boolean;
@@ -728,6 +797,14 @@ function createInstallRollbackHarness(
symlinkDamagedAppBeforeCustody?: boolean;
sameSourceExistingApp?: boolean;
transientAppRestartReloadsJob?: boolean;
unsafeEntryEvidence?:
| "job"
| "plist"
| "plist-program"
| "receipt"
| "unrelated-plist"
| "unrelated-program"
| "unrelated-receipt";
} = {},
) {
const artifact = createArtifactVerificationHarness();
@@ -741,6 +818,7 @@ function createInstallRollbackHarness(
const label = "ai.openclaw.mac.node-fixture";
const launchAgentsDir = path.join(tempRoot, "Library", "LaunchAgents");
const sourcePlist = path.join(launchAgentsDir, `${label}.plist`);
const elevationPlist = path.join(launchAgentsDir, "ai.openclaw.mac.elevation-host.plist");
const launchStateFile = path.join(tempRoot, "launch-state");
const nodeGenerationFile = path.join(tempRoot, "node-generation");
mkdirSync(path.join(stateDir, "state"), { recursive: true });
@@ -750,6 +828,16 @@ function createInstallRollbackHarness(
writeAppInfoPlist(appPath, oldSourceCommit, oldPeekabooCommit);
writeExecutable(path.join(appPath, "Contents", "MacOS", "OpenClaw"), "#!/bin/sh\nexit 0\n");
writeFileSync(path.join(appPath, "Contents", "old-fixture"), "old\n", "utf8");
if (options.rollbackCuaDriverKind) {
const resources = path.join(appPath, "Contents", "Resources");
const cuaDriver = path.join(resources, "cua-driver");
mkdirSync(resources, { recursive: true });
if (options.rollbackCuaDriverKind === "file") {
writeExecutable(cuaDriver, "#!/bin/sh\nexit 0\n");
} else {
symlinkSync("/missing/cua-driver", cuaDriver);
}
}
const sourceContents = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<plist version="1.0"><dict>',
@@ -764,10 +852,48 @@ function createInstallRollbackHarness(
"</dict></dict></plist>",
"",
].join("\n");
writeFileSync(sourcePlist, sourceContents, "utf8");
writeFileSync(launchStateFile, "source-loaded\n", "utf8");
const elevationPlistContents = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<plist version="1.0"><dict>',
"<key>Label</key><string>ai.openclaw.mac.elevation-host</string>",
`<key>ProgramArguments</key><array><string>${appPath}/Contents/MacOS/OpenClaw</string><string>--elevation-host</string></array>`,
"<key>EnvironmentVariables</key><dict>",
`<key>OPENCLAW_STATE_DIR</key><string>${stateDir}</string>`,
`<key>OPENCLAW_CONFIG_PATH</key><string>${configPath}</string>`,
"</dict></dict></plist>",
"",
].join("\n");
if (options.existingElevationLoaded) {
writeFileSync(elevationPlist, elevationPlistContents, "utf8");
writeFileSync(launchStateFile, "elevation-loaded\n", "utf8");
} else {
writeFileSync(sourcePlist, sourceContents, "utf8");
writeFileSync(launchStateFile, "source-loaded\n", "utf8");
}
writeFileSync(nodeGenerationFile, "0\n", "utf8");
writeExecutable(path.join(binDir, "defaults"), "#!/bin/sh\nprintf '%s\\n' primary\n");
writeExecutable(
path.join(binDir, "df"),
[
"#!/usr/bin/env bash",
'if [[ "${TEST_FAIL_UNSAFE_ENTRY_IDENTITY:-0}" == "1" && "${!#}" == "${TEST_INSTALLED_APP_PATH:-}" ]]; then',
" exit 7",
"fi",
'exec /bin/df "$@"',
"",
].join("\n"),
);
writeExecutable(
path.join(binDir, "mktemp"),
[
"#!/usr/bin/env bash",
'if [[ "${TEST_FAIL_UNSAFE_ENTRY_MKTEMP:-0}" == "1" && "$*" == *"elevation-host.quarantined-app."* ]]; then',
" exit 7",
"fi",
'exec /usr/bin/mktemp "$@"',
"",
].join("\n"),
);
if (options.replaceAuthenticatedRenameHelperBeforeUse) {
writeExecutable(
path.join(binDir, "shasum"),
@@ -912,6 +1038,8 @@ function createInstallRollbackHarness(
" fi",
' if [[ "$target" == */ai.openclaw.mac.elevation-host && "$state" == "elevation-loaded" ]]; then',
" printf '%s\\n' ' pid = 555555'",
" printf ' program = %s/Contents/MacOS/OpenClaw\\n' \"$TEST_INSTALLED_APP_PATH\"",
" printf '%s\\n' ' arguments = {' ' --elevation-host' ' }'",
" exit 0",
" fi",
" printf '%s\\n' 'Could not find service in domain' >&2",
@@ -932,6 +1060,27 @@ function createInstallRollbackHarness(
' plist="${3:-}"',
' if [[ "$plist" == *ai.openclaw.mac.elevation-host.plist ]]; then',
' if [[ "$TEST_LAUNCHD_BOOTSTRAP_FAILS" == "1" ]]; then',
' if [[ -n "$TEST_UNSAFE_ENTRY_EVIDENCE" ]]; then',
' rollback_app="$(find "$(dirname "$TEST_INSTALLED_APP_PATH")" -maxdepth 1 -type d -name "$(basename "$TEST_INSTALLED_APP_PATH").rollback-elevation-host-*" -print -quit)"',
' [[ -n "$rollback_app" ]] || exit 71',
' /bin/rm -rf -- "$TEST_INSTALLED_APP_PATH"',
' if [[ "$TEST_UNSAFE_ENTRY_EVIDENCE" == unrelated-* ]]; then',
' /bin/mv "$rollback_app" "$TEST_INSTALLED_APP_PATH"',
" else",
' ln -s "$rollback_app" "$TEST_INSTALLED_APP_PATH"',
" fi",
' pending_receipt="$TEST_STATE_DIR/elevation-host-install.pending.json"',
' case "$TEST_UNSAFE_ENTRY_EVIDENCE" in',
' job) /bin/rm -f -- "$TEST_ELEVATION_PLIST" "$pending_receipt"; printf \'%s\\n\' elevation-loaded >"$TEST_LAUNCH_STATE_FILE" ;;',
' plist) /bin/rm -f -- "$pending_receipt"; printf \'%s\\n\' elevation-absent >"$TEST_LAUNCH_STATE_FILE" ;;',
' plist-program) /bin/rm -f -- "$pending_receipt"; /usr/bin/plutil -insert Program -string "$TEST_INSTALLED_APP_PATH/Contents/MacOS/OpenClaw" "$TEST_ELEVATION_PLIST"; printf \'%s\\n\' elevation-absent >"$TEST_LAUNCH_STATE_FILE" ;;',
' receipt) /bin/rm -f -- "$TEST_ELEVATION_PLIST"; printf \'%s\\n\' elevation-absent >"$TEST_LAUNCH_STATE_FILE" ;;',
' unrelated-plist) /bin/rm -f -- "$pending_receipt"; /usr/bin/plutil -replace ProgramArguments.0 -string "$TEST_UNRELATED_APP_PATH/Contents/MacOS/OpenClaw" "$TEST_ELEVATION_PLIST"; printf \'%s\\n\' elevation-absent >"$TEST_LAUNCH_STATE_FILE" ;;',
' unrelated-program) /bin/rm -f -- "$pending_receipt"; /usr/bin/plutil -insert Program -string "$TEST_UNRELATED_APP_PATH/Contents/MacOS/OpenClaw" "$TEST_ELEVATION_PLIST"; printf \'%s\\n\' elevation-absent >"$TEST_LAUNCH_STATE_FILE" ;;',
' unrelated-receipt) /bin/rm -f -- "$TEST_ELEVATION_PLIST"; jq --arg appPath "$TEST_UNRELATED_APP_PATH" \'.appPath = $appPath\' "$pending_receipt" >"$pending_receipt.tmp"; /bin/mv "$pending_receipt.tmp" "$TEST_STATE_DIR/elevation-host-install.json"; /bin/rm -f -- "$pending_receipt"; printf \'%s\\n\' elevation-absent >"$TEST_LAUNCH_STATE_FILE" ;;',
" *) exit 72 ;;",
" esac",
" fi",
' if [[ "$TEST_RECREATE_SOURCE_ON_FAILURE" == "1" ]]; then',
" printf '%s\\n' replacement-owner >\"$TEST_SOURCE_PLIST\"",
" fi",
@@ -1000,6 +1149,8 @@ function createInstallRollbackHarness(
...artifact,
appPath,
configPath,
elevationPlist,
elevationPlistContents,
label,
launchStateFile,
sourceContents,
@@ -1008,15 +1159,26 @@ function createInstallRollbackHarness(
env: {
...artifact.env,
TEST_DANGLING_ROLLBACK_DURING_MOVE: options.danglingRollbackDuringMove ? "1" : "0",
TEST_CORRUPT_ROLLBACK_PLIST_BACKUP_ON_SYNC: options.corruptRollbackPlistBackupOnSync
? "1"
: "0",
TEST_FAIL_CURRENT_RECEIPT_RESTORE_COPY: options.failCurrentReceiptRestoreCopy ? "1" : "0",
TEST_FAIL_AFTER_RECEIPT_COMMIT_MOVE: options.failAfterReceiptCommitMove ? "1" : "0",
TEST_FAIL_ELEVATION_QUARANTINE_RENAME: options.failElevationQuarantineRename ? "1" : "0",
TEST_FAIL_LSOF_INSPECTION: options.failLsofInspection ? "1" : "0",
TEST_FAIL_PGREP_INSPECTION: options.failPgrepInspection ? "1" : "0",
TEST_FAIL_RECOVERY_XATTR_READ: options.failRecoveryXattrRead ? "1" : "0",
TEST_FAIL_UNSAFE_ENTRY_IDENTITY: options.failUnsafeEntryIdentity ? "1" : "0",
TEST_FAIL_UNSAFE_ENTRY_MKTEMP: options.failUnsafeEntryMktemp ? "1" : "0",
TEST_FINAL_CDHASH_MISMATCH: options.finalCDHashMismatch ? "1" : "0",
TEST_FINAL_SIGNATURE_INVALID: options.finalSignatureInvalid ? "1" : "0",
TEST_INSTALLED_APP_PATH: appPath,
TEST_ELEVATION_PLIST: elevationPlist,
TEST_STATE_DIR: stateDir,
TEST_UNRELATED_APP_PATH: path.join(tempRoot, "UnrelatedOpenClaw.app"),
TEST_UNSAFE_ENTRY_EVIDENCE: options.unsafeEntryEvidence ?? "",
TEST_CUSTODY_SIGNAL: options.hupDuringCustody ? "HUP" : "TERM",
TEST_CURRENT_CUA_SIGNATURE_INVALID: options.currentCuaSignatureInvalid ? "1" : "0",
TEST_LAUNCHD_BOOTSTRAP_FAILS: options.launchdBootstrapFails === false ? "0" : "1",
TEST_LIVE_PID: String(process.pid),
TEST_LAUNCH_STATE_FILE: launchStateFile,
@@ -1037,6 +1199,7 @@ function createInstallRollbackHarness(
TEST_RECREATE_APP_DURING_DAMAGED_CUSTODY: options.recreateAppDuringDamagedCustody ? "1" : "0",
TEST_RECREATE_SOURCE_DURING_BOOTOUT: options.recreateSourceDuringBootout ? "1" : "0",
TEST_RECREATE_SOURCE_ON_FAILURE: options.recreateSourceOnFailure ? "1" : "0",
TEST_RELOAD_ELEVATION_AFTER_QUARANTINE: options.reloadElevationAfterQuarantine ? "1" : "0",
TEST_RENAME_HELPER_HASH_MARKER: path.join(tempRoot, "rename-helper-hash-marker"),
TEST_REPLACE_DAMAGED_APP_DIRECTORY_BEFORE_CUSTODY:
options.replaceDamagedAppDirectoryBeforeCustody ? "1" : "0",
@@ -1048,6 +1211,9 @@ function createInstallRollbackHarness(
options.replaceMigrationSourceSameContentBeforeInitialCustody ? "1" : "0",
TEST_REMOVE_INSTALLED_EXECUTABLE_AFTER_READINESS:
options.removeInstalledExecutableAfterReadiness ? "1" : "0",
TEST_REMOVE_CUA_DRIVER_AFTER_UNSAFE_ENTRY_MOVE: options.removeCuaDriverAfterUnsafeEntryMove
? "1"
: "0",
TEST_RESTART_APP_DURING_BOOTOUT: options.restartAppDuringBootout ? "1" : "0",
TEST_ROLLBACK_NON_NATIVE_SIGNATURE_INVALID: options.rollbackNonNativeSignatureInvalid
? "1"
@@ -1299,6 +1465,47 @@ describe("mac elevation host command contract", () => {
},
);
it.skipIf(process.platform !== "darwin")(
"rejects CUA-bearing elevation artifacts before verify or install",
() => {
const verification = createArtifactVerificationHarness();
const rejectedVerify = runInstaller(
verification.installerPath,
[
"verify",
"--archive",
verification.archivePath,
"--receipt",
verification.receiptPath,
...receiptDigestArgs(verification.receiptPath),
],
{ ...verification.env, TEST_CUA_DRIVER_KIND: "file" },
);
expect(rejectedVerify.status).toBe(1);
expect(rejectedVerify.stderr).toContain("must not contain bundled CUA driver");
const installation = createInstallRollbackHarness();
const rejectedInstall = runInstaller(
installation.installerPath,
[
"install",
"--archive",
installation.archivePath,
"--receipt",
installation.receiptPath,
...receiptDigestArgs(installation.receiptPath),
"--app",
installation.appPath,
"--migrate-launch-agent",
installation.sourcePlist,
],
{ ...installation.env, TEST_CUA_DRIVER_KIND: "symlink" },
);
expect(rejectedInstall.status).toBe(1);
expect(rejectedInstall.stderr).toContain("must not contain bundled CUA driver");
},
);
it.skipIf(process.platform !== "darwin")(
"plans canonical node conversion without reading or copying its token",
() => {
@@ -1643,6 +1850,428 @@ describe("mac elevation host command contract", () => {
},
);
for (const rollbackCuaDriverKind of ["file", "symlink"] as const) {
it.skipIf(process.platform !== "darwin")(
`preserves but never restarts a ${rollbackCuaDriverKind} CUA-bearing elevation rollback`,
() => {
const harness = createInstallRollbackHarness({
existingElevationLoaded: true,
reloadElevationAfterQuarantine: true,
rollbackCuaDriverKind,
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--state-dir",
harness.stateDir,
"--config-path",
harness.configPath,
],
harness.env,
);
expect(result.status).toBe(1);
expect(result.stderr).toContain("could not bootstrap elevation host");
expect(result.stderr).toContain("Preserved previous elevation app with bundled CUA driver");
expect(result.stderr).toContain(
"Quarantined replacement for unsafe previous elevation LaunchAgent",
);
expect(result.stderr).toContain("automatic elevation-host rollback was incomplete");
expect(readFileSync(harness.launchStateFile, "utf8").trim()).toBe("elevation-absent");
const quarantinedApp = preservedCuaAppPath(harness);
expect(quarantinedApp).toBeDefined();
const rollbackDriver = lstatSync(
path.join(quarantinedApp!, "Contents", "Resources", "cua-driver"),
);
expect(rollbackDriver.isSymbolicLink()).toBe(rollbackCuaDriverKind === "symlink");
expect(existsSync(harness.elevationPlist)).toBe(false);
expect(
readdirSync(path.dirname(harness.elevationPlist)).filter((name) =>
name.startsWith("ai.openclaw.mac.elevation-host"),
),
).toEqual([]);
const previousPlist = readdirSync(harness.stateDir).find((name) =>
name.startsWith("elevation-host.previous-plist."),
);
expect(previousPlist).toBeDefined();
expect(readFileSync(path.join(harness.stateDir, previousPlist!), "utf8")).toBe(
harness.elevationPlistContents,
);
const quarantinedPlist = readdirSync(harness.stateDir).find((name) =>
name.startsWith("elevation-host.quarantined-launch-agent."),
);
expect(quarantinedPlist).toBeDefined();
expect(readFileSync(path.join(harness.stateDir, quarantinedPlist!), "utf8")).toContain(
"--elevation-host",
);
},
);
}
it.skipIf(process.platform !== "darwin")(
"removes the discoverable elevation plist when unsafe rollback quarantine fails",
() => {
const harness = createInstallRollbackHarness({
existingElevationLoaded: true,
failElevationQuarantineRename: true,
rollbackCuaDriverKind: "file",
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--state-dir",
harness.stateDir,
"--config-path",
harness.configPath,
],
harness.env,
);
expect(result.status).toBe(1);
expect(result.stderr).toContain(
"Removed unquarantinable replacement for unsafe previous elevation LaunchAgent",
);
expect(readFileSync(harness.launchStateFile, "utf8").trim()).toBe("elevation-absent");
expect(existsSync(harness.elevationPlist)).toBe(false);
const previousPlist = readdirSync(harness.stateDir).find((name) =>
name.startsWith("elevation-host.previous-plist."),
);
expect(previousPlist).toBeDefined();
expect(readFileSync(path.join(harness.stateDir, previousPlist!), "utf8")).toBe(
harness.elevationPlistContents,
);
},
);
it.skipIf(process.platform !== "darwin")(
"quarantines a CUA-bearing elevation app when the recorded rollback destination is blocked",
() => {
const harness = createInstallRollbackHarness({
danglingRollbackDuringMove: true,
existingElevationLoaded: true,
rollbackCuaDriverKind: "file",
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--state-dir",
harness.stateDir,
"--config-path",
harness.configPath,
],
harness.env,
);
expect(result.status).toBe(1);
expect(existsSync(path.join(harness.appPath, "Contents", "Resources", "cua-driver"))).toBe(
false,
);
expect(existsSync(harness.elevationPlist)).toBe(false);
expect(readFileSync(harness.launchStateFile, "utf8").trim()).toBe("elevation-absent");
const quarantinedApp = quarantinedElevationAppPath(harness.stateDir);
expect(quarantinedApp).toBeDefined();
expect(existsSync(path.join(quarantinedApp!, "Contents", "Resources", "cua-driver"))).toBe(
true,
);
},
);
for (const evidence of ["job", "plist", "plist-program", "receipt"] as const) {
it.skipIf(process.platform !== "darwin")(
`quarantines a symlinked CUA app from exact ${evidence} ownership evidence`,
() => {
const harness = createInstallRollbackHarness({
rollbackCuaDriverKind: "file",
unsafeEntryEvidence: evidence,
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--migrate-launch-agent",
harness.sourcePlist,
],
harness.env,
);
expect(result.status).toBe(1);
expect(result.stderr).toContain("Quarantined CUA-bearing elevation app");
expect(lstatSync(harness.appPath, { throwIfNoEntry: false })).toBeUndefined();
expect(readFileSync(harness.launchStateFile, "utf8").trim()).toBe("elevation-absent");
const quarantinedApp = quarantinedElevationAppPath(harness.stateDir);
expect(quarantinedApp).toBeDefined();
expect(lstatSync(quarantinedApp!).isSymbolicLink()).toBe(true);
expect(existsSync(path.join(quarantinedApp!, "Contents", "Resources", "cua-driver"))).toBe(
true,
);
},
);
}
it.skipIf(process.platform !== "darwin")(
"keeps a symlink quarantined when its target changes after the move",
() => {
const harness = createInstallRollbackHarness({
removeCuaDriverAfterUnsafeEntryMove: true,
rollbackCuaDriverKind: "file",
unsafeEntryEvidence: "job",
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--migrate-launch-agent",
harness.sourcePlist,
],
harness.env,
);
expect(result.status).toBe(1);
expect(result.stderr).toContain("Quarantined CUA-bearing elevation app");
const quarantinedApp = quarantinedElevationAppPath(harness.stateDir);
expect(quarantinedApp).toBeDefined();
expect(lstatSync(quarantinedApp!).isSymbolicLink()).toBe(true);
},
);
for (const evidence of ["unrelated-plist", "unrelated-program", "unrelated-receipt"] as const) {
it.skipIf(process.platform !== "darwin")(
`preserves an app when only an ${evidence} remains`,
() => {
const harness = createInstallRollbackHarness({
rollbackCuaDriverKind: "file",
unsafeEntryEvidence: evidence,
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--migrate-launch-agent",
harness.sourcePlist,
],
harness.env,
);
expect(result.status).toBe(1);
expect(result.stderr).not.toContain("Quarantined CUA-bearing elevation app");
expect(lstatSync(harness.appPath).isDirectory()).toBe(true);
expect(quarantinedElevationAppPath(harness.stateDir)).toBeUndefined();
const stalePath =
evidence !== "unrelated-receipt"
? harness.elevationPlist
: path.join(harness.stateDir, "elevation-host-install.json");
expect(existsSync(stalePath)).toBe(true);
},
);
}
for (const setupFailure of ["identity", "mktemp"] as const) {
it.skipIf(process.platform !== "darwin")(
`neutralizes launchd before unsafe app quarantine ${setupFailure} setup fails`,
() => {
const harness = createInstallRollbackHarness({
danglingRollbackDuringMove: true,
existingElevationLoaded: true,
failUnsafeEntryIdentity: setupFailure === "identity",
failUnsafeEntryMktemp: setupFailure === "mktemp",
rollbackCuaDriverKind: "file",
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--state-dir",
harness.stateDir,
"--config-path",
harness.configPath,
],
harness.env,
);
expect(result.status).toBe(1);
expect(result.stderr).toContain("automatic elevation-host rollback was incomplete");
expect(existsSync(harness.elevationPlist)).toBe(false);
expect(readFileSync(harness.launchStateFile, "utf8").trim()).toBe("elevation-absent");
expect(existsSync(path.join(harness.appPath, "Contents", "Resources", "cua-driver"))).toBe(
true,
);
},
);
}
it.skipIf(process.platform !== "darwin")(
"neutralizes unsafe rollback even when its plist evidence becomes corrupt",
() => {
const harness = createInstallRollbackHarness({
corruptRollbackPlistBackupOnSync: true,
existingElevationLoaded: true,
rollbackCuaDriverKind: "file",
});
const result = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--state-dir",
harness.stateDir,
"--config-path",
harness.configPath,
],
harness.env,
);
expect(result.status).toBe(1);
expect(result.stderr).toContain("automatic elevation-host rollback was incomplete");
expect(readFileSync(harness.launchStateFile, "utf8").trim()).toBe("elevation-absent");
expect(existsSync(harness.appPath)).toBe(false);
expect(existsSync(harness.elevationPlist)).toBe(false);
expect(
readdirSync(harness.stateDir).some((name) =>
name.startsWith("elevation-host.quarantined-launch-agent."),
),
).toBe(true);
expect(
existsSync(path.join(preservedCuaAppPath(harness)!, "Contents", "Resources", "cua-driver")),
).toBe(true);
},
);
it.skipIf(process.platform !== "darwin")(
"never restores a CUA-bearing current elevation job after explicit recovery fails",
() => {
const harness = createInstallRollbackHarness({
currentCuaSignatureInvalid: true,
launchdBootstrapFails: false,
migrationRestoreBootstrapFails: true,
});
const installed = runInstaller(
harness.installerPath,
[
"install",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--migrate-launch-agent",
harness.sourcePlist,
],
harness.env,
);
expect(installed.status, installed.stderr).toBe(0);
const currentReceipt = readFileSync(
path.join(harness.stateDir, "elevation-host-install.json"),
"utf8",
);
const resources = path.join(harness.appPath, "Contents", "Resources");
mkdirSync(resources, { recursive: true });
writeExecutable(path.join(resources, "cua-driver"), "#!/bin/sh\nexit 0\n");
const recovered = runInstaller(
harness.installerPath,
[
"recover",
"--archive",
harness.archivePath,
"--receipt",
harness.receiptPath,
...receiptDigestArgs(harness.receiptPath),
"--app",
harness.appPath,
"--state-dir",
harness.stateDir,
],
harness.env,
);
expect(recovered.status).toBe(1);
expect(recovered.stderr).toContain("Preserved current elevation app with bundled CUA driver");
expect(recovered.stderr).toContain(
"recovery failed and the current OpenClaw installation could not be restored completely",
);
expect(readFileSync(harness.launchStateFile, "utf8").trim()).toBe("elevation-absent");
expect(existsSync(harness.elevationPlist)).toBe(false);
expect(existsSync(path.join(harness.appPath, "Contents", "Resources", "cua-driver"))).toBe(
false,
);
const preservedApp = preservedCuaAppPath(harness);
expect(preservedApp).toBeDefined();
expect(existsSync(path.join(preservedApp!, "Contents", "Resources", "cua-driver"))).toBe(
true,
);
expect(readFileSync(path.join(harness.stateDir, "elevation-host-install.json"), "utf8")).toBe(
currentReceipt,
);
const preservedCurrentPlist = readdirSync(harness.stateDir).find((name) =>
name.startsWith("elevation-host.recovery-current-plist."),
);
expect(preservedCurrentPlist).toBeDefined();
expect(readFileSync(path.join(harness.stateDir, preservedCurrentPlist!), "utf8")).toContain(
"--elevation-host",
);
},
);
it.skipIf(process.platform !== "darwin")(
"refuses to record an invalid existing app as rollback state",
() => {
+21
View File
@@ -1680,6 +1680,27 @@ describe("package-mac-app plist stamping", () => {
);
});
it("omits the CUA driver only from elevation-host packages", () => {
const packageScript = readFileSync(scriptPath, "utf8");
const variantBlock = packageScript.slice(
packageScript.indexOf('SIGNING_VARIANT="${OPENCLAW_MAC_SIGNING_VARIANT:-standard}"'),
packageScript.indexOf("# OPENCLAW_SKIP_MLX_TTS"),
);
const cuaBlock = packageScript.slice(
packageScript.indexOf('if [[ "$SIGNING_VARIANT" == "elevation-host" ]]'),
packageScript.indexOf('echo "📦 Copying CLI installer"'),
);
expect(variantBlock).toContain("standard | elevation-host");
expect(variantBlock).toContain("Unknown OPENCLAW_MAC_SIGNING_VARIANT value");
expect(cuaBlock).toContain("Omitting embedded CUA driver from elevation-host package");
expect(cuaBlock).toContain("else");
expect(cuaBlock).toContain("Staging embedded CUA driver");
expect(cuaBlock).toContain(
'"$ROOT_DIR/scripts/stage-cua-driver-macos.sh" "$APP_ROOT/Contents/Resources/cua-driver"',
);
});
it("does not mask required Info.plist stamp failures", () => {
const script = readFileSync(scriptPath, "utf8");
const stampBlock = script.slice(