From e87a535d57fc09e6a8a8749e6ba93d4ce04b61fa Mon Sep 17 00:00:00 2001 From: creatixchu Date: Thu, 29 Oct 2026 17:37:31 +0800 Subject: [PATCH] fix(cua): keep standalone application URLs stable across reuse --- .../Sources/CuaNative/StandaloneApplication.swift | 8 ++++---- .../tests/swift/StandaloneApplicationTests.swift | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/experimental/cua-macos/native/Sources/CuaNative/StandaloneApplication.swift b/packages/experimental/cua-macos/native/Sources/CuaNative/StandaloneApplication.swift index ea0b357454..15bd509639 100644 --- a/packages/experimental/cua-macos/native/Sources/CuaNative/StandaloneApplication.swift +++ b/packages/experimental/cua-macos/native/Sources/CuaNative/StandaloneApplication.swift @@ -28,8 +28,8 @@ enum StandaloneApplication { let files = FileManager.default try files.createDirectory(at: root, withIntermediateDirectories: true, attributes: [.posixPermissions: 0o700]) let root = root.resolvingSymlinksInPath() - let generation = root.appendingPathComponent(runtimeKey) - let application = generation.appendingPathComponent(source.lastPathComponent) + let generation = root.appendingPathComponent(runtimeKey, isDirectory: true) + let application = generation.appendingPathComponent(source.lastPathComponent, isDirectory: true) guard !hasApplicationAncestor(application) else { throw NativeFailure(code: "NATIVE_APPLICATION_REQUIRED", message: "Computer Use application storage must be outside another .app: \(root.path)") } @@ -50,10 +50,10 @@ enum StandaloneApplication { } return application } - let staging = root.appendingPathComponent(".staging-\(UUID().uuidString)") + let staging = root.appendingPathComponent(".staging-\(UUID().uuidString)", isDirectory: true) try files.createDirectory(at: staging, withIntermediateDirectories: false, attributes: [.posixPermissions: 0o700]) do { - let stagedApplication = staging.appendingPathComponent(source.lastPathComponent) + let stagedApplication = staging.appendingPathComponent(source.lastPathComponent, isDirectory: true) try files.copyItem(at: source, to: stagedApplication) try validate(stagedApplication, runtimeKey: runtimeKey) try files.moveItem(at: staging, to: generation) diff --git a/packages/experimental/cua-macos/native/tests/swift/StandaloneApplicationTests.swift b/packages/experimental/cua-macos/native/tests/swift/StandaloneApplicationTests.swift index 5db54bcd3c..a73dda8516 100644 --- a/packages/experimental/cua-macos/native/tests/swift/StandaloneApplicationTests.swift +++ b/packages/experimental/cua-macos/native/tests/swift/StandaloneApplicationTests.swift @@ -111,6 +111,17 @@ func aChangedCachedApplicationIsRejected(file: String) throws { } } +@Test func aCachedSymlinkCannotRedirectTheHelperIntoAnEnclosingApplication() throws { + let f = try ApplicationFixture(); defer { f.close() } + let key = try f.key + try FileManager.default.createDirectory(at: f.cache, withIntermediateDirectories: true) + try FileManager.default.createSymbolicLink(at: f.cache.appendingPathComponent(key), + withDestinationURL: f.application.deletingLastPathComponent()) + #expect(throws: (any Error).self) { + try StandaloneApplication.install(f.application, runtimeKey: key, root: f.cache, deadline: f.deadline) + } +} + @Test func aCompetingInstallerDoesNotUseAPartialGeneration() throws { let f = try ApplicationFixture(); defer { f.close() } let key = try f.key