Skip to content

Refactored XPC for FinderSync Extension - #10492

Open
i2h3 wants to merge 3 commits into
masterfrom
i2h3/fix/10478
Open

Refactored XPC for FinderSync Extension#10492
i2h3 wants to merge 3 commits into
masterfrom
i2h3/fix/10478

Conversation

@i2h3

@i2h3 i2h3 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Checklist

AI (if applicable)

@i2h3 i2h3 added this to the 34.0.1 milestone Jul 30, 2026
@i2h3 i2h3 self-assigned this Jul 30, 2026
@i2h3 i2h3 added bug os: 🍎 macOS Apple macOS, formerly also known as OS X feature: 🐚 shell integration labels Jul 30, 2026
@github-project-automation github-project-automation Bot moved this to 🧭 Planning evaluation (don't pick) in 💻 Desktop Clients team Jul 30, 2026
@i2h3
i2h3 requested a review from Copilot July 30, 2026 12:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@i2h3 i2h3 moved this from 🧭 Planning evaluation (don't pick) to 🏗️ In progress in 💻 Desktop Clients team Jul 30, 2026
@i2h3
i2h3 force-pushed the i2h3/fix/10478 branch from 61e1ca4 to f58bce5 Compare July 30, 2026 15:18
@i2h3
i2h3 marked this pull request as ready for review July 30, 2026 15:19
@i2h3

i2h3 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

/backport to stable-34.0

Comment thread AGENTS.md Outdated
Comment thread admin/osx/CMakeLists.txt
Comment thread admin/osx/mac-crafter/Sources/Utils/Signer.swift
Comment thread doc/macOS-FinderSync-extension.md Outdated
Comment thread doc/macOS-FinderSync-extension.md
Comment thread doc/macOS-FinderSync-extension.md
Comment thread shell_integration/MacOSX/CMakeLists.txt Outdated
@i2h3
i2h3 force-pushed the i2h3/fix/10478 branch from 3d676b7 to 744d677 Compare July 31, 2026 08:47
@i2h3
i2h3 requested a review from claucambra July 31, 2026 10:27
claucambra

This comment was marked as outdated.

@claucambra claucambra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes look fine.

I still think the quantity and length of comments in these changes is excessive. I understand providing context around certain additions motivated by solved problems is valuable. We should certainly seek to preserve what we learn during the development process. I just don't think doing so in code files is the best approach. Comments like the ones in FinderSyncBrokerProtocol.h are for me bordering on a code smell; they are lengthy, reference issues and problems that are not familiar to the reader, they increase cognitive load, they distract from the actual implementation of the method itself... I think a lot of these comments are better served living in a dedicated document for e.g. the broker extension. That way we can follow progressive disclosure and offer information where/when it is relevant for both humans and the agents.

Given the urgency of the issue, and since the functional changes in this PR are fine, we can go ahead and merge

Comment on lines +17 to +41
* The broker exists because neither the app nor the extension is allowed to publish
* a named XPC endpoint. Only a launchd job may do that: the Mach service name has to
* be declared in a launchd.plist, and `xpc_connection_create(3)` states that "new
* service names may NOT be dynamically registered" and that the allowances XPC makes
* for this "in debug scenarios" will "absolutely NOT be made in the production
* scenario". That is why a plain `-[NSXPCListener initWithMachServiceName:]` in the
* app works under Xcode and fails once the app is launched from Finder — the failure
* mode that left FinderSync completely inert in 34.0.0.
*
* A Service Management login item *is* a launchd job, so it can vend a name that both
* sandboxed peers are permitted to look up: the broker's Mach service name equals its
* own bundle identifier, which is prefixed by the shared App Group identifier, and the
* App Sandbox grants `mach-lookup` for any name under an App Group the process claims.
* No `temporary-exception` entitlement is involved on any side.
*
* The broker is only a rendezvous point. It stores the endpoint of the app's anonymous
* listener and hands it to the extension; all FinderSync traffic then flows directly
* between app and extension over that endpoint, so no message is ever relayed here.
*
* @note `NSXPCListenerEndpoint` needs no `-setClasses:forSelector:argumentIndex:ofReply:`
* allow-list. That API constrains the contents of *collection* arguments; a directly
* typed parameter takes its class from the method signature, and endpoints conform to
* `NSSecureCoding`. Passing an endpoint as `id`, or nested inside a dictionary, would
* require an allow-list — so do not do that.
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good example of context that hinders rather than informs. It's a big information dump. This would be better served in a detailed doc explaining the issue, separated from the code

Comment on lines +90 to +94
* `-[NSXPCListener resume]` returns void and reports activation failure only to the XPC
* subsystem's own log, and `initWithMachServiceName:` never returns nil, so neither
* proves anything about the channel. Only a completed round-trip does: a reply here
* establishes that the Mach name really is in the bootstrap namespace and that the
* broker's listener delegate is accepting connections.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I think this exemplifies too much information being offered up front

Comment on lines +155 to +160
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]
?: [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"] ?: @"Nextcloud";
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
NSMenuItem *item = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ — not connected", appName]
action:nil
keyEquivalent:@""];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should constify these pointers and fix the alignment of the selector parameters

Comment on lines +232 to +238
// Carry the command on the item itself rather than an index into _menuItems. The
// menu outlives the array it was built from: a later GET_MENU_ITEMS round trip can
// reset and repopulate _menuItems before the user clicks, after which an index
// either selects the wrong command or is out of bounds and throws — crashing the
// extension. identifier is a plain string property from
// NSUserInterfaceItemIdentification, so unlike representedObject it survives
// whatever copying Finder does to the menu.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is far too wordy. It increases cognitive load. Seems to me we can explain this far more simply with

// Store the command on the menu item instead of an index into _menuItems. Finder may
// rebuild _menuItems before the user clicks, making the index invalid or pointing to
// the wrong command. identifier is copied with the menu item, so it remains valid.

Comment on lines +125 to +149
// No -setCodeSigningRequirement: on our connection to the broker. That is deliberate, and it
// is the one direction of the three where peer validation cannot be done.
//
// It was tried. A Finder Sync extension evaluating the broker's signature fails the check even
// when the broker is correctly signed and satisfies the very same requirement statically:
//
// codesign --verify -R <req> …FinderSyncBroker.app -> satisfies
// runtime, from this process:
// "Received message forbidden due to code signing requirement:
// xpc_support_check_token: anchor apple generic and
// certificate leaf[subject.OU] = "…" status: -67030" (errSecCSReqFailed)
//
// The client, also sandboxed, validates the same broker over the same protocol without any
// trouble, so what differs is this process: app extensions run in a custom sandbox that, as
// Apple DTS puts it, "doesn't always align with the standard App Sandbox used by apps"
// (developer.apple.com/forums/thread/802817). Setting a requirement that cannot succeed is
// worse than setting none — the connection is invalidated on the first reply, which is exactly
// how this manifested: the broker handed over the endpoint and we then rejected it, forever.
//
// The security property is still covered from both other directions:
// * the broker requires *us* to satisfy its requirement before it accepts our connection,
// and it does the same for the client;
// * the client validates the broker before publishing an endpoint to it.
// And the broker's Mach service name lives inside the shared App Group, so registering it at
// all means being a login item whose bundle identifier is prefixed with our team identifier.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This extended comment doesn't help explain the code below. It justifies a broader technical decision based on a past decision not made by the reader. This belongs in a separate technical doc (which can be referenced here). If I am a reader trying to understand what this code does, I don't think it's valuable to know what didn't work. I would want to know what the code below does and have the option of reading more somewhere if I need to, rather than having those impede my understanding of this class implementation

// - the install destination in shell_integration/MacOSX/CMakeLists.txt, because a login
// item's wrapper filename must equal its bundle identifier
// - the extension's own derivation from NCApplicationGroupIdentifier
return appGroupIdentifier() + QStringLiteral(".FinderSyncBroker");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice example of a comment that is useful as it explains something that is not explicit to this code but does directly affect this specific code

Signed-off-by: Iva Horn <iva.horn@nextcloud.com>
make Codesign.swift discover branded login-item names dynamically and derive the broker entitlement manifest from the existing FinderSync entitlement path. This avoids changing the CLI or Brander command.

Signed-off-by: Rello <Rello@users.noreply.github.com>
@Rello
Rello self-requested a review July 31, 2026 21:32
…ed from clang-tidy

Assisted-by: Codex:GPT-5
Signed-off-by: Rello <github@scherello.de>
@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10492.zip

Digest: sha256:7d2e8e3303eed5a429bd0df4fc4685ca1fed2ee90ffe2212835b94228fca4ab1

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: 🏗️ In progress

Development

Successfully merging this pull request may close these issues.

The FinderSync extension does not work Fix Mach exception entitlements (Mac App Store)

4 participants