Skip to content

cask/upgrade: preserve an unquarantined app's state across upgrades - #23556

Open
aholland wants to merge 1 commit into
Homebrew:mainfrom
aholland:quarantine-preserve-posture
Open

cask/upgrade: preserve an unquarantined app's state across upgrades#23556
aholland wants to merge 1 commit into
Homebrew:mainfrom
aholland:quarantine-preserve-posture

Conversation

@aholland

@aholland aholland commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Following on from #23545 and #23546. This one concerns self-updating casks. When such an app updates itself it replaces a bundle the user had already approved, since nothing unapproved gets far enough to run its own updater, and the version it puts in place carries no quarantine attribute at all. Such a version launches without any Gatekeeper prompt. Upgrading it through Homebrew brings the prompt back because the new version, having no approval to inherit, is quarantined afresh.

Walking through the process: The user approves the app once, and Homebrew carries that approval onto each new version it installs. Then at some point the app's own updater replaces the bundle, potentially multiple times in a series of auto-updates, leaving no quarantine attribute at all — self-updaters do not quarantine what they install. The app keeps launching without a prompt throughout. But the very first Homebrew upgrade subsequent to a self-update quarantines the new version afresh (it has nothing to inherit from) and the app the user has been opening freely for weeks through a series of upgrades may suddenly ask whether they are sure they want to open it.

Quite simply: approved stays approved (#23060, which preserves a cask's quarantine approval across upgrades), unapproved stays unapproved, and now unquarantined-due-to-self-update graduates to approved — approved rather than left untagged, so the download's audit trail is still recorded. Approval is granted only after the existing signing-identity check passes, so this approves nothing that #23060 would not have approved had the attribute survived.

To reproduce, with any installed cask:

app="/Applications/AppCleaner.app"
find "$app" -print0 | xargs -0 xattr -d com.apple.quarantine   # what a self-updater leaves behind
brew upgrade --cask appcleaner
xattr -p com.apple.quarantine "$app"                           # 0381… — quarantined, unapproved
Details

Demonstrated end to end. Stripping AppCleaner's attributes and upgrading gave 0381 on stock Homebrew, and 03c1 with this change, from the identical starting state. Both runs used a locally bumped cask version so the upgrade path ran for real rather than through the test harness.

Why not simply leave the new version unquarantined? Because the quarantine attribute also carries the download's provenance, which #23060 (preserving quarantine approval across upgrades) deliberately kept when it replaced release-by-deletion with approval inheritance. Matching that, this keeps the attribute and sets the approved bit.

Why this is not a new trust decision. An untagged app already runs without any Gatekeeper evaluation, so nothing is being unlocked that was locked before; the change only stops an upgrade from making the user's position worse. Anyone able to strip the attribute could equally set the approved bit directly, so no capability is added.

Reporting. The decision is logged with odebug rather than at verbose level: nothing is asked of the user, and on a machine with several self-updating casks a verbose line would appear on most upgrades.

  • Have you followed our Contributing guidelines?
  • Have you checked for other open Pull Requests for the same change?
  • Have you explained what your changes do? Performance claims (e.g. "this is faster") must include Hyperfine benchmarks.
  • Have you explained why you'd like these changes included, not just what they do?
  • For bug fixes, have you given step-by-step brew commands to reproduce the bug?
  • Have you written new tests (excluding integration tests)? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) locally?

  • I did not use AI/LLM to create this PR, or I disclosed the tool/model below and reviewed its output; I did not attribute commits to AI and will answer maintainer questions and review comments myself without AI/LLM.

Used Claude Code (Opus 5) to investigate and draft; I directed the investigation, and reviewed the diff and every line of this PR text.


An app with no quarantine attribute launches without a Gatekeeper prompt,
so an upgrade should not reintroduce one. Treat that state like an
approved one when deciding whether to carry approval forward, subject to
the same signing identity check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant