mxcl/AppUpdater is an open-source project on GitHub with 284 stars, written primarily in Swift. Automatically update open source macOS apps from GitHub releases. Full support for attestations.
Snapshot summary built from the project's own GitHub metadata — there's no written TopGit review yet. The page will update automatically when a full review is published.
WHY NO REVIEW YET
TopGit writes full reviews for the most-starred, most-requested repositories. This page is a snapshot until then — see the READ ME tab for the original README in full.
A small, self-update library for Developer ID signed macOS apps. AppUpdater checks
GitHub Releases, validates a DMG, replaces the running app, and relaunches it.
AppUpdater supports macOS 12 and later. Version 4 separates update discovery
from downloading and preparing an installation.
When Xcode owns your app target it embeds AppUpdater's resources automatically.
If you assemble the .app yourself, build with Xcode's build system and copy
the resource bundle into the app:
[!INFO]
AppUpdater's Sigstore trust bootstrap is declared as SwiftPM resources.
SwiftPM emits declared resources as a separate bundle, and generates
Bundle.module to load them. AppUpdater 4.1 therefore has no supported
bundle-free packaging mode: omitting or renaming
AppUpdater_AppUpdater.bundle prevents attestation from loading its trust
roots. Folding the data into the executable would require a different
AppUpdater implementation that generates Swift source instead of packaging
the JSON resources; it isn't a choice the consuming app can make.
Release layout
AppUpdater only accepts DMG assets. Name each asset
<repository>-<semantic-version>.dmg, for example MyApp-2.1.0.dmg.
The mounted DMG must contain one top-level app. Its filename must match the
installed app, including case. A release for MyApp.app therefore contains:
MyApp-2.1.0.dmg
└── MyApp.app
AppUpdater ignores ZIP files, tarballs, packages, and DMGs with missing,
multiple, or misnamed apps.
Usage
import AppKit
import AppUpdater
@NSApplicationMain
final class AppDelegate: NSObject, NSApplicationDelegate {
let updater = AppUpdater(
owner: "your-github-username",
repo: "your-github-repo-name"
)
@IBAction func checkForUpdates(_ sender: Any?) {
Task { @MainActor in
do {
guard let update = try await updater.check() else { return }
print("Version \(update.version) is available")
// This downloads and validates the update. The app can keep
// operating while it runs. Finder may ask for authorization
// when the app lives in a protected folder.
let prepared = try await update.prepareInstallation()
// Save documents, stop background work, close helper processes,
// and finish every read from Bundle.main here.
try await quiesceForUpdate()
// Do not read code or resources from the old bundle after this call.
try await prepared.installAndRelaunch()
} catch {
// Present or log the error. A failed launch restores the old app.
}
}
}
}
check() fetches bounded GitHub release metadata and returns a lightweight,
one-shot Update with version and assetName. It does not download the DMG.
prepareInstallation() downloads the DMG, mounts it read-only and
non-browsable, enforces the configured resource limits, and validates the app.
It then copies the DMG beside the installed app, mounts that copy read-only,
and repeats the resource and signature checks. The returned PreparedUpdate
is also one-shot. Call discard() on either object if you decide not to
continue.
Call installAndRelaunch() only after the host has saved its state, stopped
background work, and ceased loading bundle code or resources. The running
instance moves itself to a backup, copies the validated candidate into its old
path, and validates that the installed copy launches. It stops the temporary
validation instance before committing the transaction and restores the backup
if copying, final validation, launch, or probe termination fails. On success,
the old instance exits and an armed fallback launches the installed app.
[!IMPORTANT]
Quiescing must not cause the old instance to exit. In particular, hosts that
return true from applicationShouldTerminateAfterLastWindowClosed(_:)
must suppress that behavior during installation. If the host exits while
AppUpdater is copying the replacement, the transaction cannot finish or
relaunch the app. Let installAndRelaunch() terminate the old instance after
replacement and validation complete.
Configuration
The defaults cap downloads at 2 GiB, mounted regular-file content at 4 GiB, and
mounted filesystem entries at 100,000. Network, mount, and enumeration work use
a 10-minute timeout.
This is opt-in. The default attestationPolicy == nil retains AppUpdater 4.0
behavior. When enabled, the GitHub release must expose its exact 40-character
target commit and a sha256: digest for the DMG. The configured workflow must
publish SLSA provenance v1 with GitHub's workflow build type, use a GitHub-hosted
runner, and attest the DMG filename, digest, repository, workflow, ref, and
resolved source commit.
Only public GitHub Actions bundles using Sigstore's public-good Fulcio, Rekor,
and CT logs are supported. GitHub release attestations, private-repository RFC
3161 bundles, SBOM predicates, generic Sigstore identities, other build types,
and other trust domains are rejected.
Security model
AppUpdater aims to prevent privilege amplification. A same-user attacker must
not be able to replace a downloaded candidate and then borrow Finder's
authorization to modify an app that the user cannot otherwise replace.
The result of check() is advisory GitHub metadata, not an authenticated app.
Only prepareInstallation() downloads and authenticates the candidate. Do not
grant privileges or stop security services based only on an available update.
For every candidate, AppUpdater requires a valid Developer ID Application
signature. The installed and candidate apps must have the same Team ID, signing
identifier, and bundle identifier. Validation covers all architectures, nested
code, strict sealed resources, app-like bundle structure, and restricted
symlinks. AppUpdater rejects ad-hoc, development, self-signed, and broad custom
requirements such as designated => true.
With an attestation policy, provenance verification is an additional mandatory
gate; Developer ID validation is never used as a fallback. AppUpdater streams
and hashes the downloaded DMG, matches GitHub's release digest, and verifies the
Sigstore v0.3 DSSE bundle before mounting. Verification covers the Fulcio chain
and signing time, code-signing EKU, GitHub OIDC certificate claims, SCT, DSSE
signature, Rekor SET, RFC 6962 inclusion proof, signed checkpoint, and the
supported in-toto/SLSA assertions. After Finder promotion, AppUpdater hashes
the protected copy again and refuses to mount it unless the verified digest is
unchanged.
Sigstore trust is bootstrapped from the root shipped with AppUpdater and
refreshed from Sigstore's public TUF repository. Refresh performs sequential
root rotation and threshold verification, then verifies timestamp, snapshot,
targets, hashes, lengths, expiry, and rollback state. Cache writes are atomic;
an offline cache is used only when its complete signed metadata chain remains
valid, otherwise the shipped trusted-root snapshot is used. Malformed or
cryptographically invalid online metadata is never treated as an offline
failure.
Missing, malformed, expired, unsupported, oversized, or unverifiable
attestation material fails prepareInstallation() with
AppUpdaterError.attestationVerificationFailed. No DMG is mounted and no
installation change is attempted.
AppUpdater downloads into a private directory, mounts the DMG read-only, and
keeps the validated mount alive. For a protected installation, Finder copies
the DMG itself into a randomized hidden sibling of the installed app. AppUpdater
rejects the promoted copy unless it is a regular file directly under that
parent, has no symlink path components, and the current user can neither write
nor replace it. AppUpdater then mounts and validates the promoted copy again.
It does not touch the installed app if promotion fails these checks.
An app installed in a user-writable bundle or parent directory is already under
that user's control. AppUpdater still uses a private same-parent DMG copy and
performs the same validation and rollback transaction, but it cannot protect
that path from another process running as the same user.
Neither mode binds a GitHub release version to the version inside the app or
performs a Gatekeeper/notarization assessment. Without an attestation policy,
AppUpdater provides no release provenance or rollback protection and a
compromised Developer ID key can produce an update that passes identity checks.
With a policy, a candidate must also originate from the configured GitHub
workflow/ref and resolve to the release's exact source commit.
No homepage URL was recorded for mxcl/AppUpdater in TopGit's last sync. The README tab above frequently contains screenshots and demo links, or check the repository description on GitHub.
How active is development on mxcl/AppUpdater?
The most recent commit recorded on mxcl/AppUpdater was 2 days ago, based on the GitHub push timestamp. The repository has 8 forks — one of the better signals of community interest.
How many stars does mxcl/AppUpdater have?
mxcl/AppUpdater has 284 GitHub stars — refresh the page for the live number, or check github.com/mxcl/AppUpdater. TopGit mirrors GitHub's count but does not claim minute-by-minute accuracy.
What language is mxcl/AppUpdater written in?
mxcl/AppUpdater is written primarily in Swift. GitHub's language field is based on the largest share of bytes in the default branch.
What license does mxcl/AppUpdater use?
mxcl/AppUpdater is released under the Unlicense license. Always verify the LICENSE file directly on GitHub for the authoritative terms — license strings can be edited out of sync with a project's actual stance.
What topics is mxcl/AppUpdater associated with?
GitHub's repository topics for mxcl/AppUpdater: "swift-package-manager". TopGit's editorial category is open-source.
Where do I read more about mxcl/AppUpdater?
This TopGit page is a snapshot — the READ ME tab shows the project's own README content (links stripped, images preserved). The GitHub repository at github.com/mxcl/AppUpdater is the definitive source.
Read full README in the tab above.
Still deciding about AppUpdater?
One click hands the question to an AI along with this page — see what it says about AppUpdater.