From b84b5c8487c7538864ccf31562748a307119e3bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Modro=C3=B1o=20Vara?= Date: Wed, 3 Jun 2026 16:23:21 +0200 Subject: [PATCH] Open the File Provider folder in Finder via NSWorkspace The "Open in Finder" button shelled out to /usr/bin/open, which does nothing from a sandboxed app: the spawned tool inherits the sandbox and its LaunchServices request to open a path outside the container is silently denied (no kernel sandbox violation, no effect). Open the user-visible File Provider URL with NSWorkspace from the app process instead, holding security-scoped access (the URL is security-scoped), and fall back to revealing the item if opening the folder fails. Logs each outcome so the path is diagnosable. --- Sources/App/ViewModels/AppState.swift | 32 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Sources/App/ViewModels/AppState.swift b/Sources/App/ViewModels/AppState.swift index a1c3db7..b986545 100644 --- a/Sources/App/ViewModels/AppState.swift +++ b/Sources/App/ViewModels/AppState.swift @@ -1256,10 +1256,13 @@ final class AppState: ObservableObject { } func openFileProviderInFinder(selecting course: MoodleCourse? = nil) async { - guard let site = currentSite else { return } + guard let site = currentSite else { + logger.warning("Open in Finder: no current site") + return + } guard let rootURL = await fileProviderRootURL(for: site) else { - logger.warning("Cannot open in Finder: File Provider root URL not available") + logger.warning("Open in Finder: File Provider root URL not available") return } @@ -1271,13 +1274,24 @@ final class AppState: ObservableObject { targetURL = rootURL } - // NSWorkspace.shared.open() can be blocked by the sandbox for File - // Provider CloudStorage URLs after binary changes (e.g. Sparkle updates). - // Shell out to /usr/bin/open which is not subject to the app sandbox. - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/open") - process.arguments = [targetURL.path] - try? process.run() + // The user-visible File Provider URL is security-scoped, so hold access + // while opening it. Open it with NSWorkspace from this process: shelling + // out to /usr/bin/open does not work, because the spawned tool inherits + // the app sandbox and its LaunchServices request to open a path outside + // the container is silently denied (no kernel sandbox violation, no + // effect). + let didAccess = targetURL.startAccessingSecurityScopedResource() + defer { if didAccess { targetURL.stopAccessingSecurityScopedResource() } } + + do { + _ = try await NSWorkspace.shared.open(targetURL, configuration: NSWorkspace.OpenConfiguration()) + logger.info("Opened \(targetURL.lastPathComponent, privacy: .public) in Finder") + } catch { + // Revealing (selecting the item in its parent) can still succeed when + // opening the folder itself does not. + logger.warning("NSWorkspace.open failed (\(error.localizedDescription, privacy: .public)); revealing in Finder instead") + NSWorkspace.shared.activateFileViewerSelecting([targetURL]) + } } func resetProvider() async {