CVE-2026-47121
Swift vulnerability analysis and mitigation

Overview

CVE-2026-47121 is a binary delta apply intermediate-symlink traversal vulnerability in the Sparkle software update framework for macOS. It affects all Sparkle versions up to and including 2.9.1 (both the 1.x master branch and the 2.x branch). The vulnerability was published to the GitHub Advisory Database on May 29, 2026, and assigned CVE ID CVE-2026-47121. It carries a CVSS v3.1 base score of 6.1 (Moderate) (Github Advisory, Sparkle Advisory).

Technical details

The root cause is a combination of CWE-22 (Path Traversal) and CWE-59 (Improper Link Resolution Before File Access / Link Following) in two source files. In Autoupdate/SPUSparkleDeltaArchive.m, the extractItem: method creates symlinks from archive content verbatim — accepting absolute paths and .. targets — without validating the symlink destination. In Autoupdate/SUBinaryDeltaApply.m, the parent-directory symlink check only inspects the immediate parent of the destination path (one level up) rather than walking all intermediate path components; additionally, the check is skipped entirely when attributesOfItemAtPath: returns nil (i.e., when the parent does not yet exist). An attacker crafts a malicious .delta archive with two items: the first creates a symlink inside the destination tree pointing to an arbitrary directory (e.g., /Library/LaunchDaemons), and the second extracts a regular file through that symlink path. Because fopen(path, "wb") follows intermediate symlinks at the kernel level, the file is written to the resolved external path. Exploitation requires the malicious .delta to pass EdDSA signature verification, meaning the attacker must possess the developer's private signing key (Sparkle Advisory, Github Advisory).

Impact

The primary impact is arbitrary file write at the privilege level of the AppInstaller process. For system-domain installs (where AppInstaller runs as root), this enables writing files to any location on the filesystem — including /Library/LaunchDaemons, /etc/, and /usr/local/ — allowing an attacker to establish persistent root-level code execution (e.g., by dropping a LaunchDaemon plist). For user-domain installs, the same technique can write to user-writable directories such as ~/Library/LaunchAgents/, enabling user-level persistence. This is a defense-in-depth gap: it grants a strictly broader write primitive than the "replace the app bundle" capability that a stolen signing key already provides, with no confidentiality or availability impact beyond the integrity violation (Sparkle Advisory).

Exploitability

A proof-of-concept (PoC) is publicly documented in the GitHub Security Advisory, detailing the exact archive structure and item sequence required to trigger the traversal. The NVD SSVC assessment classifies exploitation as "poc" and "not automatable," reflecting the high-complexity prerequisite of EdDSA private-key compromise. The EPSS score is approximately 0.029% (0.000290), indicating a low near-term exploitation probability. There is no evidence of in-the-wild exploitation, no known threat actor attribution, and the vulnerability is not listed in the CISA KEV catalog (Github Advisory, Sparkle Advisory).

Exploitation steps

  1. Obtain a valid EdDSA signing key: Compromise or otherwise obtain the developer's EdDSA private signing key used to sign Sparkle delta updates for the target application. Without this key, the malicious .delta will fail signature verification and be rejected.
  2. Craft the malicious .delta archive: Using the SPUSparkleDeltaArchive writer or by hand-assembling the archive format, construct a delta with two items in order:
    • Item 1 (symlink): relativeFilePath = "Contents/Resources/escape", commands = SPUDeltaItemCommandExtract (0x02), mode = S_IFLNK | 0o755 (0xA1ED), payload = "/Library/LaunchDaemons" (or another target directory).
    • Item 2 (regular file): relativeFilePath = "Contents/Resources/escape/com.attacker.persistence.plist", commands = SPUDeltaItemCommandExtract (0x02), mode = S_IFREG | 0o644 (0x81A4), payload = <attacker-chosen LaunchDaemon plist bytes>.
  3. Sign and publish the delta: Sign the archive with the stolen EdDSA key, set the matching sparkle:edSignature attribute, and host it on an attacker-controlled update feed that the target Sparkle host is pointed at.
  4. Trigger the update: Wait for or induce the target application to check for updates and initiate a system-domain install. applyBinaryDelta enumerates items; Item 1 passes the .. check (no .. in path components) and the shallow parent-symlink check, then extractItem: creates Contents/Resources/escape -> /Library/LaunchDaemons.
  5. Achieve arbitrary file write: Item 2's parent path Contents/Resources/escape resolves through the newly created symlink to /Library/LaunchDaemons; attributesOfItemAtPath: returns NSFileTypeDirectory (not symlink), so the check passes. fopen("Contents/Resources/escape/com.attacker.persistence.plist", "wb") causes the kernel to resolve the symlink and write /Library/LaunchDaemons/com.attacker.persistence.plist.
  6. Establish persistence: The dropped LaunchDaemon plist is outside finalDestination, so the post-apply hash check passes (or the file persists even if the hash fails, since cleanup only removes finalDestination). On next reboot, macOS launches the attacker's daemon as root (Sparkle Advisory).

Indicators of compromise

  • File System: Unexpected .plist files in /Library/LaunchDaemons/ or ~/Library/LaunchAgents/ not associated with any known installed application; new symlinks inside application bundle directories (e.g., Contents/Resources/escape) pointing to system directories such as /Library/LaunchDaemons, /etc/, or /usr/local/.
  • File System: Unexpected files in /usr/local/, /etc/ subdirectories, or other system-owned paths with timestamps coinciding with a Sparkle update event.
  • Logs: macOS system logs (/var/log/system.log or unified logging via log show) showing launchd loading a previously unknown daemon from /Library/LaunchDaemons/ after a reboot; Sparkle update logs recording a delta apply operation immediately before the anomalous file creation.
  • Process: Unexpected processes launched as root by launchd referencing a plist not present before the last Sparkle update cycle; child processes of the Sparkle AppInstaller writing files outside the target application bundle directory.
  • Network: Sparkle appcast feed requests resolving to an unexpected or attacker-controlled host; delta (.delta) file downloads from domains not matching the legitimate software vendor (Sparkle Advisory).

Mitigation and workarounds

Upgrade to Sparkle version 2.9.2, which contains the patch. The fix modifies SUBinaryDeltaApply.m to walk every intermediate path component of relativePath using lstat() and reject the operation if any component is a symbolic link, rather than only checking the immediate parent directory. No configuration-based workaround is available; the only remediation is upgrading to 2.9.2. Developers embedding Sparkle in their macOS applications should rebuild and redistribute their applications with the patched framework version (Github Advisory, Patch Commit).

Additional resources


SourceThis report was generated using AI

Related Swift vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

CVE-2026-28975MEDIUM6.9
  • Swift logoSwift
  • SwiftNIOExtras
NoYesJun 12, 2026
CVE-2026-47121MEDIUM6.1
  • Swift logoSwift
  • github.com/sparkle-project/Sparkle
NoYesJul 21, 2026
CVE-2026-64785MEDIUM5.3
  • Swift logoSwift
  • swift-nio-http2
NoYesJul 23, 2026
CVE-2026-28898MEDIUM5.3
  • Swift logoSwift
  • SwiftNIOHPACK
NoYesJun 25, 2026
CVE-2026-47122MEDIUM4.2
  • Swift logoSwift
  • sparkle
NoNoJul 21, 2026

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management