From 683f38acd74efe80112e37185927ae1f2c7dbcca Mon Sep 17 00:00:00 2001 From: Peter van Tol Date: Thu, 1 Oct 2026 18:02:11 +0200 Subject: [PATCH] Read sealed app executable identity for bundled CLI --- macos/ForgePlatformInstaller/README.md | 4 ++++ ...MacOSInstallerExecutableArchitecture.swift | 11 +++++++--- ...InstallerExecutableArchitectureTests.swift | 20 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/macos/ForgePlatformInstaller/README.md b/macos/ForgePlatformInstaller/README.md index 499fcd49..3b6f5f9a 100644 --- a/macos/ForgePlatformInstaller/README.md +++ b/macos/ForgePlatformInstaller/README.md @@ -228,6 +228,10 @@ startup. It asks for explicit confirmation without `--yes`, rechecks installer currency immediately before `SMAppService` registration, and reports native `ENABLED` or `REQUIRES_APPROVAL` separately. Registration never implies that the product-operation backend or reboot persistence is ready. +The CLI is a second executable inside the signed app. Current-bundle +architecture admission reads the sealed `CFBundleExecutable` and checks the +fixed GUI executable rather than using Foundation's process-relative +`executableURL`, which can point at the running CLI. An `ENABLED` system job is accepted only after a fixed-label native readback confirms the registered parent bundle, Team, helper program and exact current installer version. An older registered helper fails closed; registration does diff --git a/macos/ForgePlatformInstaller/Sources/ForgePlatformInstallerCore/MacOSInstallerExecutableArchitecture.swift b/macos/ForgePlatformInstaller/Sources/ForgePlatformInstallerCore/MacOSInstallerExecutableArchitecture.swift index e1218658..fd2e7ded 100644 --- a/macos/ForgePlatformInstaller/Sources/ForgePlatformInstallerCore/MacOSInstallerExecutableArchitecture.swift +++ b/macos/ForgePlatformInstaller/Sources/ForgePlatformInstallerCore/MacOSInstallerExecutableArchitecture.swift @@ -13,11 +13,16 @@ enum MacOSInstallerExecutableArchitecture { static func requireThinARM64Executable(in bundleURL: URL) throws { guard bundleURL.isFileURL, let bundle = Bundle(url: bundleURL), - let executableURL = bundle.executableURL, - executableURL.deletingLastPathComponent().lastPathComponent == "MacOS", - executableURL.lastPathComponent == "ForgePlatformInstaller" else { + bundle.infoDictionary?["CFBundleExecutable"] as? String == + "ForgePlatformInstaller" else { throw MacOSInstallerExecutableArchitectureError.invalidBundleExecutable } + // Foundation's `executableURL` points at the *running* CLI when it is + // the second executable in this app. The sealed Info.plist instead + // identifies the one fixed GUI executable covered by this contract. + let executableURL = bundleURL.appendingPathComponent( + "Contents/MacOS/ForgePlatformInstaller", isDirectory: false + ) try requireThinARM64Executable(at: executableURL) } diff --git a/macos/ForgePlatformInstaller/Tests/ForgePlatformInstallerCoreTests/MacOSInstallerExecutableArchitectureTests.swift b/macos/ForgePlatformInstaller/Tests/ForgePlatformInstallerCoreTests/MacOSInstallerExecutableArchitectureTests.swift index b3336321..336ddfaf 100644 --- a/macos/ForgePlatformInstaller/Tests/ForgePlatformInstallerCoreTests/MacOSInstallerExecutableArchitectureTests.swift +++ b/macos/ForgePlatformInstaller/Tests/ForgePlatformInstallerCoreTests/MacOSInstallerExecutableArchitectureTests.swift @@ -42,6 +42,26 @@ final class MacOSInstallerExecutableArchitectureTests: XCTestCase { try MacOSInstallerExecutableArchitecture.requireThinARM64Executable(in: bundle) ) + let validBundle = root.appendingPathComponent("ValidInstaller.app", isDirectory: true) + try FileManager.default.copyItem(at: bundle, to: validBundle) + try thinARM64Header().write(to: validBundle.appendingPathComponent( + "Contents/MacOS/ForgePlatformInstaller" + )) + XCTAssertNoThrow( + try MacOSInstallerExecutableArchitecture.requireThinARM64Executable(in: validBundle) + ) + + let wrongInfoBundle = root.appendingPathComponent("WrongInfo.app", isDirectory: true) + try FileManager.default.copyItem(at: validBundle, to: wrongInfoBundle) + var wrongInfo = info + wrongInfo["CFBundleExecutable"] = "forge-platform-installer" + try PropertyListSerialization.data( + fromPropertyList: wrongInfo, format: .xml, options: 0 + ).write(to: wrongInfoBundle.appendingPathComponent("Contents/Info.plist")) + XCTAssertThrowsError( + try MacOSInstallerExecutableArchitecture.requireThinARM64Executable(in: wrongInfoBundle) + ) + // Use a separate inode for the positive path so this assertion does // not depend on Foundation's Bundle or file-resource caches after the // rejected bundle inspection above.