Replies: 3 comments 5 replies
|
I am skeptical about this report for a number of reasons. Sparkle is widely used in "non‑sandboxed, Developer‑ID, Hardened‑Runtime apps" in Tahoe. We have no other reports of this, and this related code in Sparkle has not changed in a long time, so the reproducible steps (re: section 2) are not convincing or lacking in detail. I suspect this is a specific one-off/edge case if you are running into this kind of error. Your report implies Autoupdate succeeds in creating Installation intermediate directory but fails creating a subdirectory in there using Further, directories in |
|
I just hit this error today while doing an update. The update didn't work in that run but it did work in subsequent runs. I also could see a macOS notification saying ""App" was prevented from modifying apps on your Mac." I'm investigating and trying to reproduce the issue. |
|
#2882 has a workaround for resolving this issue. @fbarbat confirmed that their app bundle identifier ends with If your bundle identifier ends with |
Uh oh!
There was an error while loading. Please reload this page.
Summary
On macOS 26 (Tahoe), Sparkle's
Autoupdatehelper fails to install downloadedupdates for non‑sandboxed, Developer‑ID signed host apps. The helper aborts
with:
Reproduced on Sparkle 2.9.2 (current release) and on 2.9.1. The
underlying syscall is
mkdtemp(3)returningEPERMeven though theparent directory exists, is owned by the user, and is
mode 0700. The causeappears to be Tahoe's expanded app‑bound data protection (the
com.apple.provenancexattr) on~/Library/Caches/<bundle-id>/, which gateswrites by a process whose code identity differs from the directory's
"owning" app — and
Autoupdatehas a differentcdhashthan the host.A workaround that stages the installer under the per‑user Darwin temp dir
(
$TMPDIR) instead of~/Library/Caches/<bundle-id>/fixes it; verifiedon‑device. Filing now to confirm reproduction and discuss the fix shape
before opening a PR.
1. Environment
arm64(Apple Silicon)2.9.2(xcframeworkbinaryTarget)LSUIElement = trueDeveloper ID Application: <redacted> (TEAMID)Autoupdateextracts → relaunchSandboxed apps were not tested; the report scope is non‑sandboxed
Developer‑ID hosts.
2. Reproduction
embeds Sparkle 2.9.2 via SPM (xcframework).
Code=10 SPUInstallerError. The user sees the standard error alert; the app doesnot relaunch.
This is reproducible on first‑install, and recurs after clearing
~/Library/Caches/<bundle-id>/between attempts.3. Symptom — observed logs
Clean, uninstrumented build (stock Sparkle 2.9.2):
The same error is logged twice: once by the
Autoupdatehelper that fails,once relayed back to the host process.
The error originates in
Autoupdate/AppInstaller.mwhen+[SPULocalCacheDirectory createUniqueDirectoryInDirectory:]returnsnil.Underlying syscall
With a locally instrumented Sparkle build (added
SULogaroundmkdir(2)/mkdtemp(3)insideSPULocalCacheDirectory), the failing call is:Key detail: the parent directory
…/org.sparkle-project.Sparkle/Installationexists, is owned by the same uid (501), and has POSIX
mode 0700.POSIX permits the write.
EPERMis being returned by something other thanPOSIX — see §4.
4. What we measured
com.apple.provenancexattr is present on every level of~/Library/Caches/<bundle-id>/…(host‑app container,org.sparkle-project.Sparkle/subdir,Installation/subdir).xattr -l <path>showscom.apple.provenanceat each level.Installation/is created by a different process (Autoupdate) than its parents (host app).xattr -px com.apple.provenance <path>returns the same hex at all three levels.xattr -d com.apple.provenance <dir>is silently rejected.cdhashes), even after the helper is re‑signed with a host‑prefixed identifier (com.<bundle-id>.Sparkle.Autoupdate).codesign -dvvvon both binaries.uid 501,mode 0700); the write is denied despite POSIX allowing it.mkdtemp(3)returnserrno=1 (EPERM).Launcher/subtree succeeded in the same process where theAutoupdate‑createdInstallation/subtree failed; minutes later, after the cache was cleared and recreated, the sameInstallation/path succeeded.5. Why this hits Sparkle specifically
Sparkle ships two separately code‑signed executables that share a uid
but differ in
cdhash:Autoupdate/ installer helper — a distinct Mach‑O with its ownsignature (and, in our case, a host‑prefixed identifier
com.<bundle-id>.Sparkle.Autoupdate).Stock Sparkle stages everything under one shared tree:
Sequence:
…/org.sparkle-project.Sparkle/(andPersistentDownloads/,Launcher/). The kernel stamps that subtree withthe host app's provenance.
Autoupdate(differentcdhash) then tries to create…/org.sparkle-project.Sparkle/Installation. Because the parent isprovenance‑bound to the host app,
AutoupdategetsEPERM.The failure is therefore per‑parent‑directory: a differently‑signed
helper cannot create a child inside any directory created by the host
app's identity, regardless of POSIX permissions.
6. Why
$TMPDIRworks (workaround)confstr(_CS_DARWIN_USER_TEMP_DIR, …)returns the per‑user Darwin tempdirectory (
$TMPDIR), e.g.:This directory is:
launchd(not by any app) → it is notstamped with any app's provenance.
mode 0700, owned by the user → user‑private.cdhash→ boththe host app and
Autoupdatecan create their own subtrees in it.After patching
Autoupdate/AppInstaller.m(via a wrapper inSPULocalCacheDirectory) to put the installer staging root under$TMPDIR/<bundle-id>.org.sparkle-project.Sparkle.Installation/…instead ofunder
~/Library/Caches/<bundle-id>/…/Installation, the install stepcompletes and the relaunch succeeds. Verified on macOS 26.4.1.
/private/tmpis also exempt (it'smode 1777, system‑owned), so itworks as a workaround too — but it's world‑writable, so it is not an
appropriate staging location for a signed bundle.
$TMPDIRis thesecurity‑correct choice.
7. What we tried before patching Sparkle
These did not fix it on macOS 26:
Autoupdatewith a host‑prefixed identifier(
com.<bundle-id>.Sparkle.Autoupdate). Thecdhashof the host and thehelper still differ, and the EPERM still occurs.
Downloader.xpc/Installer.xpcfrom theembedded
Sparkle.frameworkfor our non‑sandboxed host (perSparkle's sandboxing docs).
This is independently required for non‑sandboxed hosts but does not
address the
EPERM.~/Library/Caches/<bundle-id>/org.sparkle-project.Sparkle/Installationfrom the host app at launch (so it exists with the host's provenance
before
Autoupdateruns).Autoupdatestill getsEPERMwhen it triesto
mkdtempinside that pre‑created directory.~/Library/Caches/<bundle-id>/between attempts. The failurerecurs on the next attempt because the host app recreates the parent
subtree first.
8. What we cannot prove from outside
provenance‑stamped directory. The fact that the xattr value is identical
at every level (rather than per‑creator) suggests the value is associated
with the app data container (bundle id), not stamped per creating
process. The precise authorization predicate (team id, designated
requirement, MACL, sandbox container ID, or a combination) is not publicly
documented.
no Apple statement either way; we are filing in parallel via Feedback
Assistant.
There is no Apple developer documentation of
com.apple.provenancesemantics that we are aware of. The most thorough public writing is Howard
Oakley's Eclectic Light Co. series (2023–2025), which is explicitly
reverse‑engineered observation, not Apple specification.
9. Ask
Sparkle host reproduce the install failure with stock 2.9.2? (We've
reproduced on one machine; broader confirmation would help.)
installer's staging root (
rootCacheInstallationPathinAutoupdate/AppInstaller.m) to a top‑level entry under$TMPDIR, with agraceful fallback to the legacy
~/Library/Caches/…/Installationpathfor pre‑Tahoe / unusual environments? The host‑app paths
(
PersistentDownloads/,Launcher/) would be left untouched so thedownload‑cache behavior across launches does not change.
+[SPULocalCacheDirectory createUniqueDirectoryInDirectory:](retryunder
$TMPDIRonEPERM) be preferred so the canonical~/Library/Cachespath is always attempted first?Happy to implement whichever shape is preferred and open a PR against the
2.xbranch.10. Repro artifacts available on request
[diag]prefixed linesare from local
SULoginstrumentation added toSPULocalCacheDirectory,not stock Sparkle).
xattr -l/xattr -px com.apple.provenancedumps for the threedirectory levels.
codesign -dvvvfor host andAutoupdatebinaries (withcdhash).All reactions