Free tool, no signup, nothing to install

Point this at the Mac app you already shipped.

Fifteen checks against your own DMG or .app, and three more against the update feed it points at, all run on your machine. It tells you which parts of your release are quietly broken for the people who downloaded it, and fine for you.

  1. Download it

    One file. No installer, no dependencies beyond the Xcode command line tools you already have.

    curl -fsSLO https://hawserkit.com/hawser-check.sh
  2. Read it before you run it

    It is 400 commented lines and it is a stranger's shell script, so do not take our word for what it does. It is also readable straight in your browser.

    less hawser-check.sh
  3. Run it on something you shipped

    Point it at a DMG, an app bundle, or a zip containing one. If the app names a Sparkle feed, that feed is fetched and checked too.

    bash hawser-check.sh ~/Desktop/YourApp.dmg
    
    bash hawser-check.sh /Applications/YourApp.app \
         --appcast https://yourapp.com/appcast.xml

What it prints

hawser-check 1.0 YourApp.dmg The DMG you distribute Signature is valid Signed with a Developer ID certificate The DMG has no stapled notarisation ticket This is the single most common miss: notarising the app is a different submission from notarising the DMG you actually ship. Without the ticket, the download fails to open on any Mac that cannot reach Apple, and opens perfectly on yours, which already has the ticket cached. The app inside it Deep signature verification passes Hardened Runtime is enabled No secure timestamp on the signature Signed offline, or without --timestamp. A signature with no timestamp stops being valid when your certificate expires instead of remaining valid for what it signed. Copies already on disk break years later. Your published update feed No item in the feed is signed Sparkle 2 rejects every one of these updates. There is no failure dialog and no log entry your users will ever see: they simply stay on the version they have. 3 of 18 checks failed. Most of the failures above are invisible from where you are sitting: the app works for you and fails for the people who downloaded it.

Illustrative output. Yours will be shorter or longer depending on what it finds, and it may well be all green.

What it checks

Nothing here is exotic. Every verdict comes from codesign, spctl, stapler, plutil or lipo, all already on your Mac and free. The tool's only contribution is knowing which questions to ask and what the answers mean, which is the same thing the kit is for.

The DMG you distribute

  • The signature is valid, and made with a Developer ID certificate rather than a development or ad-hoc identity.
  • The DMG itself is notarised and stapled, not just the app inside it. Two separate submissions, and the second is the one people skip.
  • Gatekeeper accepts it as an install.

The app inside it

  • Deep signature verification passes, so nothing nested in the bundle is unsigned or modified. Xcode re-signs embedded SPM binary frameworks ad-hoc, which looks fine locally and is refused once every inner binary is checked.
  • Hardened Runtime is on, and there is a secure timestamp.
  • The debug entitlement get-task-allow did not survive into the shipped build.
  • The app carries its own stapled ticket, so it opens on a Mac that is offline.
  • It is a universal binary and declares a minimum macOS.

Updates

  • Sparkle is actually configured: there is a feed URL, it is not still the placeholder, it is not plain HTTP, and there is a signing key in the app.
  • Your published feed is reachable, has releases in it, and every release is signed. An unsigned feed is rejected by every installed copy with no dialog and no log entry. This runs by default against the feed your app names; --appcast points it somewhere else.

What it will not do

  • It reads. It writes nothing, installs nothing, uploads nothing and reports nothing anywhere. No telemetry, no account, no licence check, no counter. A DMG is mounted read-only to look inside and unmounted on the way out, which is a side effect rather than a write, and the one we would want disclosed if this were someone else's script.
  • One request of its own: the Sparkle update feed your app already points at, so it can tell you whether that feed still answers. --offline removes it. Separately, spctl is a Gatekeeper evaluation and macOS may consult Apple during one, which is outside any script's control.
  • It will not invent failures. Point it at a Mac App Store app or one of Apple's own and it says so and skips the checks, because those are distributed under different rules.

The first real thing it caught was ours

We wrote this tool, then ran it against our own shipped app. It came back with one failure, and the failure was real.

Hawser 1.0.1 looks for updates at a GitHub Pages address. That address has returned 404 since 12 August, when we moved this site to a different host. The site move went perfectly. The domain worked, every page worked, everything was green. It did not occur to anyone that the app already out in the world was still asking the old address.

Nothing reported it, and nothing could have. Sparkle reads an unreachable feed as "no update right now" rather than as an error. No dialog, no log line, no sign of any kind. Every copy in circulation simply could not update, and would never have said so.

It had been that way for five weeks, in the product that is sold to prevent exactly this.

The repository is fixed, so the next build is correct. The copies already distributed are not, and cannot be: the update feed is the one component that cannot deliver its own fix, because the fix would have to travel through it. That is the whole reason it is worth checking from outside.

One clarification, because these are two separate facts and they are easy to read as one. The address baked into the app is dead by accident, as described above. The feed we do serve is deliberately empty, for the reason set out in the traps write-up: a Sparkle update archive is downloadable by anyone, and ours would carry the paid kit. Both are true, and only the first is a mistake.

We are telling you this because it is the strongest thing we can say about the tool. We know this pipeline well enough to sell it, we wrote the checks, and we still missed this one for five weeks.

If it comes back clean

Then your pipeline is in good shape and you should keep your $99. Genuinely: this is the outcome we expect for anyone who has been shipping outside the App Store for a while, and it is worth knowing rather than guessing.

If it comes back with red

Every failure it prints tells you what to fix, and you can go and fix each one by hand. The difference the kit makes is not knowing about them once. It is that these eighteen checks, plus the rest that only make sense while a build is being produced, run on every release, before the upload to Apple rather than after, and get updated when Apple moves the pipeline again.

See what that costs · $99

You can also verify a finished DMG we built, or read the ten traps in full. Both free.