Every CVE Search/ Get API key

CVE-2026-47121

Sparkle: Binary delta apply intermediate-symlink traversal in malicious .delta

6.1 MediumFix available

Sparkle is a software update framework for macOS. Prior to version 2.9.2, Autoupdate/SUBinaryDeltaApply.m enforces relativePath.pathComponents containsObject:@".." and rejects writes whose immediate parent directory IS itself a symbolic link, but does not detect symlinks deeper in the relative path. Autoupdate/SPUSparkleDeltaArchive.m's extractItem: will create symlinks in the destination tree from archive content (no .. check on the symlink target), and a subsequent Extract item targeting <symlink>/foo/bar then escapes the destination tree via fopen(path, "wb") because the kernel resolves the intermediate symlink during the open call. This is a defense-in-depth issue: exploitation requires a maliciously-crafted .delta that passes EdDSA signature verification, i.e. EdDSA private-key compromise. With the AppInstaller running as root for system-domain installs, it gives the holder of a stolen signing key arbitrary file write at root level via the delta-apply path, which is a strictly broader primitive than the "drop-in replacement bundle" install they would otherwise have. Version 2.9.2 contains a patch for the issue.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
None
Integrity
High
Availability
None

Exploit likelihood

0.34%Up 0.3 points since Jun 12, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.1

Sparkle: Binary delta apply intermediate-symlink traversal in malicious .delta

Sparkle is a software update framework for macOS. Prior to version 2.9.2, Autoupdate/SUBinaryDeltaApply.m enforces relativePath.pathComponents containsObject:@".." and rejects writes whose immediate parent directory IS itself a symbolic link, but does not detect symlinks deeper in the relative path. Autoupdate/SPUSparkleDeltaArchive.m's extractItem: will create symlinks in the destination tree from archive content (no .. check on the symlink target), and a subsequent Extract item targeting <symlink>/foo/bar then escapes the destination tree via fopen(path, "wb") because the kernel resolves the intermediate symlink during the open call. This is a defense-in-depth issue: exploitation requires a maliciously-crafted .delta that passes EdDSA signature verification, i.e. EdDSA private-key compromise. With the AppInstaller running as root for system-domain installs, it gives the holder of a stolen signing key arbitrary file write at root level via the delta-apply path, which is a strictly broader primitive than the "drop-in replacement bundle" install they would otherwise have. Version 2.9.2 contains a patch for the issue.

Record
CVE-2026-47121
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
Weakness
CWE-22, CWE-59
Updated
2026-11-02
EUVDEUVD-2026-46259 6.1

Sparkle is a software update framework for macOS. Prior to version 2.9.2, Autoupdate/SUBinaryDeltaApply.m enforces relativePath.pathComponents containsObject:@".." and rejects writes whose immediate parent directory IS itself a symbolic link, but does not detect symlinks deeper in the relative path. Autoupdate/SPUSparkleDeltaArchive.m's extractItem: will create symlinks in the destination tree from archive content (no .. check on the symlink target), and a subsequent Extract item targeting <symlink>/foo/bar then escapes the destination tree via fopen(path, "wb") because the kernel resolves the intermediate symlink during the open call. This is a defense-in-depth issue: exploitation requires a maliciously-crafted .delta that passes EdDSA signature verification, i.e. EdDSA private-key compromise. With the AppInstaller running as root for system-domain installs, it gives the holder of a stolen signing key arbitrary file write at root level via the delta-apply path, which is a strictly broader primitive than the "drop-in replacement bundle" install they would otherwise have. Version 2.9.2 contains a patch for the issue.

Record
EUVD-2026-46259
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-hg88-v3cw-3qrh 6.1

Sparkle: Binary delta apply intermediate-symlink traversal in malicious .delta

Summary

Binary delta apply intermediate-symlink traversal in malicious .delta

Autoupdate/SUBinaryDeltaApply.m enforces relativePath.pathComponents containsObject:@".." and rejects writes whose immediate parent directory IS itself a symbolic link, but does not detect symlinks deeper in the relative path. Autoupdate/SPUSparkleDeltaArchive.m's extractItem: will create symlinks in the destination tree from archive content (no .. check on the symlink target), and a subsequent Extract item targeting <symlink>/foo/bar then escapes the destination tree via fopen(path, "wb") because the kernel resolves the intermediate symlink during the open call.

This is a defense-in-depth issue: exploitation requires a maliciously-crafted .delta that passes EdDSA signature verification, i.e. EdDSA private-key compromise. With the AppInstaller running as root for system-domain installs, it gives the holder of a stolen signing key arbitrary file write at root level via the delta-apply path, which is a strictly broader primitive than the "drop-in replacement bundle" install they would otherwise have.

Affected versions: 1.x (master branch), 2.x branch including 2.9.1.

Details

Symlink writeable from archive

Autoupdate/SPUSparkleDeltaArchive.m:557-678's extractItem: handles symlinks if the archive item carries S_ISLNK(mode):

} else {
    // Link files

    if (PARTIAL_IO_CHUNK_SIZE < decodedLength) { ...too long... }
    if (decodedLength > PATH_MAX) { ...too long... }

    char buffer[PATH_MAX + 1] = {0};
    if (![self _readBuffer:buffer length:(int32_t)decodedLength]) { ... }

    NSString *destinationPath = [fileManager stringWithFileSystemRepresentation:buffer length:decodedLength];

    [fileManager removeItemAtPath:itemFilePath error:NULL];

    NSError *createLinkError = nil;
    if (![fileManager createSymbolicLinkAtPath:itemFilePath withDestinationPath:destinationPath error:&createLinkError]) {
        _error = createLinkError;
        return NO;
    }
    ...
    lchmod(itemFilePathString, mode);
}

The link's destinationPath is taken verbatim from the archive content with only a length cap; absolute paths and .. are accepted. After this item is processed, the destination tree contains a symlink that points outside it.

Parent-symlink check is shallow

Autoupdate/SUBinaryDeltaApply.m:177-207:

[archive enumerateItems:^(SPUDeltaArchiveItem *item, BOOL *stop) {
    NSString *relativePath = item.relativeFilePath;

    if ([relativePath.pathComponents containsObject:@".."]) {
        ...reject...
    }

    NSString *sourceFilePath = [source stringByAppendingPathComponent:relativePath];
    NSString *destinationFilePath = [destination stringByAppendingPathComponent:relativePath];
    {
        NSString *destinationParentDirectory = destinationFilePath.stringByDeletingLastPathComponent;
        NSDictionary<NSFileAttributeKey, id> *destinationParentDirectoryAttributes = [fileManager attributesOfItemAtPath:destinationParentDirectory error:NULL];

        // It is OK for the directory parent to not exist if it has already been removed
        if (destinationParentDirectoryAttributes != nil) {
            NSString *fileType = destinationParentDirectoryAttributes[NSFileType];
            if ([fileType isEqualToString:NSFileTypeSymbolicLink]) {
                ...reject...
            }
        }
    }
    ...
}];

Two gaps:

  1. The check inspects only destinationParentDirectory (one level up), not all intermediate components. For a relative path a/b/c.txt, the kernel resolves through any symlink at component a. attributesOfItemAtPath: with the resolved path returns attributes of the resolved-through directory, which is NSFileTypeDirectory (not NSFileTypeSymbolicLink), so the check passes.

  2. The check is skipped entirely if destinationParentDirectoryAttributes == nil (line 195). When the symlink target is to a directory that does not contain the named subpath,…

Record
GHSA-hg88-v3cw-3qrh
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
Weakness
CWE-22, CWE-59
Updated
2026-11-02
NVD 6.1

Sparkle is a software update framework for macOS. Prior to version 2.9.2, Autoupdate/SUBinaryDeltaApply.m enforces relativePath.pathComponents containsObject:@".." and rejects writes whose immediate parent directory IS itself a symbolic link, but does not detect symlinks deeper in the relative path. Autoupdate/SPUSparkleDeltaArchive.m's extractItem: will create symlinks in the destination tree from archive content (no .. check on the symlink target), and a subsequent Extract item targeting <symlink>/foo/bar then escapes the destination tree via fopen(path, "wb") because the kernel resolves the intermediate symlink during the open call. This is a defense-in-depth issue: exploitation requires a maliciously-crafted .delta that passes EdDSA signature verification, i.e. EdDSA private-key compromise. With the AppInstaller running as root for system-domain installs, it gives the holder of a stolen signing key arbitrary file write at root level via the delta-apply path, which is a strictly broader primitive than the "drop-in replacement bundle" install they would otherwise have. Version 2.9.2 contains a patch for the issue.

Record
CVE-2026-47121
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
Weakness
CWE-22, CWE-59
Updated
2026-11-02
OSV 6.1

Sparkle: Binary delta apply intermediate-symlink traversal in malicious .delta

Sparkle is a software update framework for macOS. Prior to version 2.9.2, Autoupdate/SUBinaryDeltaApply.m enforces relativePath.pathComponents containsObject:@".." and rejects writes whose immediate parent directory IS itself a symbolic link, but does not detect symlinks deeper in the relative path. Autoupdate/SPUSparkleDeltaArchive.m's extractItem: will create symlinks in the destination tree from archive content (no .. check on the symlink target), and a subsequent Extract item targeting <symlink>/foo/bar then escapes the destination tree via fopen(path, "wb") because the kernel resolves the intermediate symlink during the open call. This is a defense-in-depth issue: exploitation requires a maliciously-crafted .delta that passes EdDSA signature verification, i.e. EdDSA private-key compromise. With the AppInstaller running as root for system-domain installs, it gives the holder of a stolen signing key arbitrary file write at root level via the delta-apply path, which is a strictly broader primitive than the "drop-in replacement bundle" install they would otherwise have. Version 2.9.2 contains a patch for the issue.

Record
CVE-2026-47121
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
Weakness
CWE-22, CWE-59
Updated
2026-11-02
OSVGHSA-hg88-v3cw-3qrh 6.1

Sparkle: Binary delta apply intermediate-symlink traversal in malicious .delta

Summary

Binary delta apply intermediate-symlink traversal in malicious .delta

Autoupdate/SUBinaryDeltaApply.m enforces relativePath.pathComponents containsObject:@".." and rejects writes whose immediate parent directory IS itself a symbolic link, but does not detect symlinks deeper in the relative path. Autoupdate/SPUSparkleDeltaArchive.m's extractItem: will create symlinks in the destination tree from archive content (no .. check on the symlink target), and a subsequent Extract item targeting <symlink>/foo/bar then escapes the destination tree via fopen(path, "wb") because the kernel resolves the intermediate symlink during the open call.

This is a defense-in-depth issue: exploitation requires a maliciously-crafted .delta that passes EdDSA signature verification, i.e. EdDSA private-key compromise. With the AppInstaller running as root for system-domain installs, it gives the holder of a stolen signing key arbitrary file write at root level via the delta-apply path, which is a strictly broader primitive than the "drop-in replacement bundle" install they would otherwise have.

Affected versions: 1.x (master branch), 2.x branch including 2.9.1.

Details

Symlink writeable from archive

Autoupdate/SPUSparkleDeltaArchive.m:557-678's extractItem: handles symlinks if the archive item carries S_ISLNK(mode):

} else {
    // Link files

    if (PARTIAL_IO_CHUNK_SIZE < decodedLength) { ...too long... }
    if (decodedLength > PATH_MAX) { ...too long... }

    char buffer[PATH_MAX + 1] = {0};
    if (![self _readBuffer:buffer length:(int32_t)decodedLength]) { ... }

    NSString *destinationPath = [fileManager stringWithFileSystemRepresentation:buffer length:decodedLength];

    [fileManager removeItemAtPath:itemFilePath error:NULL];

    NSError *createLinkError = nil;
    if (![fileManager createSymbolicLinkAtPath:itemFilePath withDestinationPath:destinationPath error:&createLinkError]) {
        _error = createLinkError;
        return NO;
    }
    ...
    lchmod(itemFilePathString, mode);
}

The link's destinationPath is taken verbatim from the archive content with only a length cap; absolute paths and .. are accepted. After this item is processed, the destination tree contains a symlink that points outside it.

Parent-symlink check is shallow

Autoupdate/SUBinaryDeltaApply.m:177-207:

[archive enumerateItems:^(SPUDeltaArchiveItem *item, BOOL *stop) {
    NSString *relativePath = item.relativeFilePath;

    if ([relativePath.pathComponents containsObject:@".."]) {
        ...reject...
    }

    NSString *sourceFilePath = [source stringByAppendingPathComponent:relativePath];
    NSString *destinationFilePath = [destination stringByAppendingPathComponent:relativePath];
    {
        NSString *destinationParentDirectory = destinationFilePath.stringByDeletingLastPathComponent;
        NSDictionary<NSFileAttributeKey, id> *destinationParentDirectoryAttributes = [fileManager attributesOfItemAtPath:destinationParentDirectory error:NULL];

        // It is OK for the directory parent to not exist if it has already been removed
        if (destinationParentDirectoryAttributes != nil) {
            NSString *fileType = destinationParentDirectoryAttributes[NSFileType];
            if ([fileType isEqualToString:NSFileTypeSymbolicLink]) {
                ...reject...
            }
        }
    }
    ...
}];

Two gaps:

  1. The check inspects only destinationParentDirectory (one level up), not all intermediate components. For a relative path a/b/c.txt, the kernel resolves through any symlink at component a. attributesOfItemAtPath: with the resolved path returns attributes of the resolved-through directory, which is NSFileTypeDirectory (not NSFileTypeSymbolicLink), so the check passes.

  2. The check is skipped entirely if destinationParentDirectoryAttributes == nil (line 195). When the symlink target is to a directory that does not contain the named subpath,…

Record
GHSA-hg88-v3cw-3qrh
CVSS 3.1
6.1 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
Weakness
CWE-22, CWE-59
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
github.com/sparkle-project/Sparkle
SwiftURL
through 2.9.1
2.9.2
github.com/sparkle-project/Sparkle
SwiftURL
before 2.9.2
2.9.2

History

History
RecordedChangedSources
aliases, productsEUVD
first indexedCVE List, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-47121 is a medium vulnerability (CVSS 6.1) in Sparkle Project Sparkle, published May 29, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. Fixed in github.com/sparkle-project/Sparkle 2.9.2.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open