1.4.0 is out — XPackager now checks for its own updates

Check for Updates… in the application menu compares your version with the latest
release, shows what is new, downloads the package and hands it to Apple’s Installer. It
also checks silently at startup; a checkbox turns that off.

One detail worth passing on, because it caught me out: a file downloaded by an
application does not get the quarantine flag.
Only browsers and similar set it. So
Gatekeeper never examines that file when you open it — the usual safety net simply is not
there. XPackager therefore checks the package itself before opening it: notarized
according to spctl, and signed by the expected Developer ID team according to
pkgutil. If any of that fails, it does not open it and shows you what it found.

Note for anyone already running 1.3.0 or earlier: that build has no update menu, so this
one upgrade is manual. After 1.4.0 they announce themselves.

What’s next for XPackager

1.4.0 is out, so I went back through the manual pages of pkgbuild and productbuild, and
Apple’s distribution schema reference, to see what I had left on the table. What’s on the
bench:

  • Smaller packages. pkgbuild can use a newer compression as soon as you declare a
    minimum macOS version — which XPackager already asks for. Measured on a 37 MB payload:
    12.9 MB → 8.8 MB, a third less, for two extra seconds of build time.
  • Applications to quit before installing. Declaring which ones the Installer must close
    first — the obvious case being updating an application while it is running.
  • How bundles get replaced. Replacing one as a whole instead of merging into it, so
    files that disappeared from the new version don’t linger on disk. And refusing to
    overwrite a newer version already installed.
  • Apple’s notarization log, inside the application. When a submission is rejected,
    notarytool holds a JSON that says precisely why. Today you go to the Terminal to read
    it — at the worst possible moment.
  • A generated uninstaller. macOS doesn’t give you one; the receipt, however, knows every
    file the package laid down.
  • Reading .pkgproj files, for anyone arriving from Packages.

No dates. I’d rather know which of these you would actually use before I fix the order — and
if what you need isn’t on the list, that’s the most useful reply of all.

I’d be tempted to think this flag must be set by any software that downloads apps intentionally. In not doing so, the embedded checks of Mac OS aren’t performed. You’re making your own checks instead, but (1) that’s duplicating what already exists (i.e. Mac OS’ framework) and (2) Mac OS certainly does more than one might expect.
Not wanting to tell you what you should do, though. It’s just how I see things.

I think the issue there is that it would break apps that have a built in update system.

If the quarantine attribute was set, then the updated app would launch in quarantine and it would be up to the user to remove the flag manually, otherwise the app wouldn’t work as expected.

@Arnaud , you’re right — and I was wrong about what it costs to do it properly. So I went and measured it, on macOS 27, with my own package.

Without the flag, nothing runs. An unsigned package carrying no quarantine attribute opens straight into the Installer wizard. No assessment, no prompt, nothing in the log.

With the flag, macOS does exactly what you would want. The same unsigned package, with com.apple.quarantine set by hand: the open is refused outright, and the dialog says Apple could not confirm the package is free of malware. Its buttons are Move to Trash and Done — and on one of my attempts the package was simply gone afterwards.

And on a legitimate package it costs nothing at all. My signed, notarized, stapled .pkg with the flag set opens the Installer with no dialog whatsoever. The log shows the quarantine resolver running and XProtectIsNotarized = 1.

That last line is where you were more right than I had realised. The quarantine path runs XProtect’s malware analysis*; my own check does not. For the signature half, my check isn’t a reimplementation — spctl --assess -t install is the same SecAssessment evaluation Gatekeeper performs, called explicitly instead of being triggered by the flag. But the signature half is all it is. So the two aren’t redundant: mine asks “is this signed and notarized, and by whom it claims”, and the system adds “and does XProtect recognise anything inside”.

So XPackager will set the flag on what it downloads, and keep its own check. The order matters: verify first, so the application can refuse with a reason in the user’s own language and never hand a bad file to the Installer at all — Apple’s dialog tells the user nothing about why, and one of its two buttons bins the download. Then hand it over with the flag on, and let XProtect and Gatekeeper have their say.

@Jared — your objection is real, and it belongs to a different route from mine. I tested both:

  • From a quarantined .pkg, the installed file carries only com.apple.provenance.
    The flag does not reach the payload: installd extracts as root and doesn’t copy extended attributes across. Nothing to clear, nothing to break.
  • From a quarantined .zip expanded by Archive Utility, every extracted item is stamped — the folder and the file inside it. That is your scenario exactly, and it is why Sparkle clears the attribute itself, after it has validated the signature.

So the rule seems to be: ship updates as a package, and you can mark the download and let the Installer be checked for free. Ship them as an archive you expand yourself, and you own the verification — clearing the flag afterwards is then the honest thing to do, having actually checked first.

Thanks to you both. This goes into the next version.