VYPR
Medium severity4.2GHSA Advisory· Published May 29, 2026· Updated May 29, 2026

Sparkle's AppInstaller post-stage-1 XPC listener accepts unvalidated connections, allowing spoofed appcast item data injection

CVE-2026-47122

Description

Summary

AppInstaller post-stage-1 XPC listener accepts unvalidated connections, allowing spoofed appcast item data injection.

Details

Autoupdate/AppInstaller.m's shouldAcceptNewConnection: only enforces SUCodeSigningVerifier validateConnection: before stage 1 completes. After _performedStage1Installation = YES, new connections to the registered Mach service -spki are accepted from any local process without team-ID or code-signing checks.

The following chain of events enables an attacker to inject a spoofed SPUSentUpdateAppcastItemData payload:

  1. Installer finishes unarchiving the update successfully (_willCompleteInstallation is set).
  2. The app responsible for updating the bundle crashes or is forcefully quit before it has a chance to send SPUSentUpdateAppcastItemData to the installer. There is no user interaction between the prior step and this one, so the timing window is tight.
  3. After stage 1 of the installer is performed (_performedStage1Installation = YES), but before final installation completes (since all services are cleaned up by then), an attacker process connects to the -spki Mach service - no code-signing validation is enforced - and sends a spoofed SPUSentUpdateAppcastItemData message containing an attacker-crafted SUAppcastItem.
  4. A Sparkle-aware app that checks for updates on the bundle being updated launches before installation completes. The progress agent re-broadcasts the spoofed SUAppcastItem on its -spks status service, and the launching app displays attacker-controlled release notes (name, version, critical flag).

Note: Sparkle can be used to update other app bundles, so the "app doing the updating" and the "app being updated" are not necessarily the same bundle.

In the system-domain case (SPUUsesSystemDomainForBundlePath = true), the AppInstaller runs as root via SMJobSubmit to kSMDomainSystemLaunchd, and the Mach service is reachable by any local user process.

Affected versions: 2.x branch including 2.9.1.

Impact

A local user-level process can inject a forged SUAppcastItem (arbitrary name, version, critical flag) into the progress agent's status broadcast. Other Sparkle-aware clients on the system will display attacker-controlled release notes as authoritative installation state.

The integrity of the installed code is not affected - the bundle moved into place is the legitimate, signature-validated update from stage 1. The impact is limited to UI spoofing of installation metadata.

Remediation

Enforce SUCodeSigningVerifier validateConnection: on all new connections regardless of installation stage, or disallow SPUSentUpdateAppcastItemData after the active connection invalidates.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

AppInstaller XPC listener in Sparkle 2.x accepts unvalidated connections after stage 1, allowing local attackers to spoof appcast item data.

Vulnerability

The AppInstaller XPC listener in Autoupdate/AppInstaller.m only enforces SUCodeSigningVerifier validateConnection: before stage 1 completes. After _performedStage1Installation = YES, new connections to the registered Mach service -spki are accepted from any local process without team-ID or code-signing checks. This affects all versions in the 2.x branch including 2.9.1 [1][2].

Exploitation

A local attacker can exploit the vulnerability by timing a connection after the installer finishes unarchiving the update and the updating app crashes or is forcefully quit before sending SPUSentUpdateAppcastItemData. The attacker then connects to the -spki service and sends a spoofed SPUSentUpdateAppcastItemData message containing a crafted SUAppcastItem. If a Sparkle-aware app launches before installation completes, the progress agent re-broadcasts the spoofed item on its -spks service, displaying attacker-controlled release notes (name, version, critical flag). In the system-domain case (SPUUsesSystemDomainForBundlePath = true), the service runs as root and is reachable by any local user [1][2].

Impact

Successful injection allows an attacker to forge the update information displayed to the user, misleading them with false release notes, version numbers, or critical status. This undermines the trust in the update mechanism but does not directly lead to code execution or privilege escalation beyond the local process context [1][2].

Mitigation

No fixed version has been disclosed in the available references. Users should monitor the Sparkle project for a patch. Restricting local access to the affected Mach services may reduce the attack surface, but no official workaround is provided [1][2].

AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

3

Patches

3
6e370a5ab829

Enforce connection to installer to be validated before receiving appcast item data (#2876)

https://github.com/sparkle-project/SparkleZorgMay 16, 2026Fixed in 2.9.2via llm-release-walk
1 file changed · +7 2
  • Autoupdate/AppInstaller.m+7 2 modified
    @@ -97,6 +97,7 @@ @implementation AppInstaller
         
         // Setting _performedStage1Installation on main thread must be synchronzied with reading it from new connection handler
         BOOL _performedStage1Installation;
    +    BOOL _receivedAppcastItemData;
         
         BOOL _performedStage2Installation;
         BOOL _performedStage3Installation;
    @@ -142,9 +143,9 @@ - (BOOL)listener:(NSXPCListener *)__unused listener shouldAcceptNewConnection:(N
             BOOL connectionCodeSigningValidationSkipped = NO;
         #endif
             
    -        // It's safe to allow any connections once stage 1 installation is complete
    +        // It's safe to allow any connections once stage 1 installation is complete and appcast data has been received.
             // This is to allow general updaters to resume the installation.
    -        if (!_performedStage1Installation) {
    +        if (!_performedStage1Installation || !_receivedAppcastItemData) {
                 BOOL passesValidation;
                 NSError *validationError = nil;
                 SUValidateConnectionStatus status = [SUCodeSigningVerifier validateConnection:newConnection error:&validationError];
    @@ -597,6 +598,10 @@ - (void)handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data
                 [self extractAndInstallUpdate];
             });
         } else if (identifier == SPUSentUpdateAppcastItemData) {
    +        os_unfair_lock_lock(&_newConnectionLock);
    +        _receivedAppcastItemData = YES;
    +        os_unfair_lock_unlock(&_newConnectionLock);
    +        
             SUAppcastItem *updateItem = (data != nil) ? (SUAppcastItem *)SPUUnarchiveRootObjectSecurely(data, [SUAppcastItem class]) : nil;
             if (updateItem != nil) {
                 SPUInstallationInfo *installationInfo = [[SPUInstallationInfo alloc] initWithAppcastItem:updateItem];
    
113279a21f75

Improve synchronization of _receivedAppcastItemData (#2877)

https://github.com/sparkle-project/SparkleZorgMay 17, 2026Fixed in 2.9.2via llm-release-walk
2 files changed · +12 10
  • Autoupdate/AppInstaller.m+11 9 modified
    @@ -599,18 +599,20 @@ - (void)handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data
             });
         } else if (identifier == SPUSentUpdateAppcastItemData) {
             os_unfair_lock_lock(&_newConnectionLock);
    -        _receivedAppcastItemData = YES;
    -        os_unfair_lock_unlock(&_newConnectionLock);
    -        
    -        SUAppcastItem *updateItem = (data != nil) ? (SUAppcastItem *)SPUUnarchiveRootObjectSecurely(data, [SUAppcastItem class]) : nil;
    -        if (updateItem != nil) {
    -            SPUInstallationInfo *installationInfo = [[SPUInstallationInfo alloc] initWithAppcastItem:updateItem];
    +        if (!_receivedAppcastItemData) {
    +            _receivedAppcastItemData = YES;
                 
    -            NSData *archivedData = SPUArchiveRootObjectSecurely(installationInfo);
    -            if (archivedData != nil) {
    -                [_agentConnection.agent registerInstallationInfoData:archivedData];
    +            SUAppcastItem *updateItem = (data != nil) ? (SUAppcastItem *)SPUUnarchiveRootObjectSecurely(data, [SUAppcastItem class]) : nil;
    +            if (updateItem != nil) {
    +                SPUInstallationInfo *installationInfo = [[SPUInstallationInfo alloc] initWithAppcastItem:updateItem];
    +                
    +                NSData *archivedData = SPUArchiveRootObjectSecurely(installationInfo);
    +                if (archivedData != nil) {
    +                    [_agentConnection.agent registerInstallationInfoData:archivedData];
    +                }
                 }
             }
    +        os_unfair_lock_unlock(&_newConnectionLock);
         } else if (identifier == SPUResumeInstallationToStage2 && data.length == sizeof(uint8_t) * 2) {
             // Because anyone can ask us to resume the installation, it may be wise to think about backwards compatibility here if IPC changes
             uint8_t relaunch = *((const uint8_t *)data.bytes);
    
  • CHANGELOG+1 1 modified
    @@ -4,7 +4,7 @@
     * Polish and update Spanish translations to be gender neutral (#2874, #2875) (Emilio P Egido)
     * Guard against NULL CFRelease() on failure condition in fallback path (#2867) (Zorg)
     * Guard against symlinks when applying delta update files (fe7b718) (Zorg, fg0x0)
    -* Enforce connection to installer to be validated before receiving appcast item data (#2876) (Zorg, fg0x0)
    +* Enforce connection to installer to be validated before receiving appcast item data (#2876, #2877) (Zorg, fg0x0)
     
     # 2.9.1
     
    
1520cda1bda6

Guard against NULL CFRelease() on failure condition in fallback path (#2867)

https://github.com/sparkle-project/SparkleZorgApr 26, 2026Fixed in 2.9.2via llm-release-walk
1 file changed · +3 1
  • Autoupdate/SUCodeSigningVerifier.m+3 1 modified
    @@ -471,7 +471,9 @@ + (SUValidateConnectionStatus)validateConnection:(NSXPCConnection *)connection e
             SecCodeRef code = NULL;
             OSStatus result = SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef _Nullable)(attributes), kSecCSDefaultFlags, &code);
             if (result != errSecSuccess) {
    -            CFRelease(code);
    +            if (code != NULL) {
    +                CFRelease(code);
    +            }
                 
                 if (error != NULL) {
                     *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUInsufficientSigningError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"The client connection could not be validated because SecCodeCopyGuestWithAttributes() failed with error %d", result] }];
    

Vulnerability mechanics

Root cause

"Missing code-signing validation on XPC connections accepted after stage 1 installation completes, allowing unvalidated local processes to inject spoofed appcast item data."

Attack vector

A local attacker connects to the `<bundleId>-spki` Mach service after stage 1 installation completes but before the installer's connection is invalidated. Because `shouldAcceptNewConnection:` skips `SUCodeSigningVerifier validateConnection:` once `_performedStage1Installation` is `YES` [patch_id=3106496], the attacker can send a spoofed `SPUSentUpdateAppcastItemData` message containing a crafted `SUAppcastItem`. The progress agent re-broadcasts the forged item on its status service, causing other Sparkle-aware clients to display attacker-controlled release notes (name, version, critical flag). In the system-domain case the Mach service runs as root and is reachable by any local user process.

Affected code

`Autoupdate/AppInstaller.m`'s `shouldAcceptNewConnection:` and `handleMessageWithIdentifier:` methods. The vulnerability is in the XPC listener logic that skips code-signing validation after `_performedStage1Installation` is set, and in the lack of a `_receivedAppcastItemData` guard to prevent spoofed `SPUSentUpdateAppcastItemData` messages from unvalidated connections.

What the fix does

Patch [patch_id=3106496] introduces a `_receivedAppcastItemData` boolean that is set to `YES` only when the installer processes a legitimate `SPUSentUpdateAppcastItemData` message. The `shouldAcceptNewConnection:` method now requires both `_performedStage1Installation` AND `_receivedAppcastItemData` to be true before skipping code-signing validation, preventing unvalidated connections from injecting spoofed appcast data. Patch [patch_id=3106494] wraps the flag check and the data processing inside the same `os_unfair_lock` critical section, eliminating a race condition where a second concurrent message could bypass the guard.

Preconditions

  • authThe attacker must be a local process on the same macOS system.
  • configStage 1 installation must have completed (_performedStage1Installation = YES) but the legitimate appcast item data must not yet have been received (_receivedAppcastItemData = NO).
  • networkThe attacker must connect to the -spki Mach service before the installer's connection is invalidated.
  • inputThe attacker sends a crafted SPUSentUpdateAppcastItemData message containing a spoofed SUAppcastItem.

Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.