Verifying releases

If you install Floe from a GitHub Release and update it in the app, macOS and Sparkle do these checks for you. This page is for checking a download yourself.

What is signed

LayerWhat it provesCovers
Apple Developer ID and notarizationThe app came from Floe’s Apple developer team (A7CKWF99ML) and passed Apple’s checksThe app and the DMG
Sparkle EdDSAAn update matches what the release workflow signed with Floe’s Sparkle keyUpdate ZIPs and deltas
Sigstore (cosign)The file matches a signature made by Floe’s release workflowThe DMG and the SBOM
SLSA build provenanceThe shared thaw-app/org-ci build workflow built the file from the tagged commit (SLSA v1.0 Build L3)The DMG and the SBOM
GPG tag signatureThe maintainer tagged that commitGit version tags, after 0.2.1

Each release also has a CycloneDX SBOM, Floe_<version>.cdx.json, which lists the Swift packages linked into the app.

The Apple credentials and the Sparkle private key are CI secrets, held in the repository’s prod environment.

Check the app

Open the DMG, then run:

spctl --assess --type execute -v /Volumes/Floe/Floe.app
xcrun stapler validate /Volumes/Floe/Floe.app
codesign --verify --deep --strict --verbose=2 /Volumes/Floe/Floe.app
codesign -dv --verbose=4 /Volumes/Floe/Floe.app 2>&1 | grep -E ‘Authority|TeamIdentifier|Timestamp’

spctl should say accepted and source=Notarized Developer ID, and TeamIdentifier should be A7CKWF99ML.

Check the checksum and the Sigstore signature

Download Floe.dmg, Floe.dmg.sha256 and Floe.dmg.sigstore.json from the release into one folder. You need cosign for the second command.

shasum -a 256 -c Floe.dmg.sha256

cosign verify-blob \
  --bundle Floe.dmg.sigstore.json \
  --certificate-identity-regexp ‘^https://github\.com/thaw-app/Floe/\.github/workflows/release\.yml@refs/tags/[0-9][^/]*$' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  Floe.dmg

The identity must end in a version tag, so a signature made from a branch or a pull request fails. The SBOM works the same way, with Floe_<version>.cdx.json in place of Floe.dmg.

Releases up to 0.2.1 were signed from development. For those, use this identity instead:

--certificate-identity ‘https://github.com/thaw-app/Floe/.github/workflows/release.yml@refs/heads/development'

Check the build provenance

This needs the GitHub CLI. For releases after 0.2.1:

gh attestation verify Floe.dmg --repo thaw-app/Floe \
  --signer-workflow thaw-app/org-ci/.github/workflows/build-macos-app.yml

--signer-workflow ties the check to the workflow that built the file. This repository’s release workflow can call that workflow but can’t change it.

Releases up to 0.2.1 were attested at Build L2 by a workflow in this repository. Check those with:

gh attestation verify Floe.dmg --repo thaw-app/Floe \
  --signer-workflow thaw-app/Floe/.github/workflows/attest-build-provenance.yml

The same attestations are attached to each release as *.intoto.jsonl.

The provenance names the commit the file was built from. To print it (this also needs jq):

gh attestation verify Floe.dmg --repo thaw-app/Floe --format json \
  --signer-workflow thaw-app/org-ci/.github/workflows/build-macos-app.yml \
  | jq -r ‘.[].verificationResult.statement.predicate.buildDefinition.resolvedDependencies[]?.digest.gitCommit’

For releases up to 0.2.1, use the --signer-workflow value above for those releases. The commit must be the one the tag points at, which the next section prints.

Check a Git tag

Tags after 0.2.1 are signed with the maintainer’s GPG key, whose fingerprint is DC99 B6ED B84A 220A E164 38D1 1EDC BC96 0CD9 1D90. GitHub lists the same key at https://github.com/diazdesandi.gpg. Compare the two before you trust the key.

curl -s https://github.com/diazdesandi.gpg | gpg --import
git fetch --tags origin
git verify-tag <tag>
git rev-parse ‘<tag>^{}’

The last command prints the tagged commit, which should match the provenance and the commit on the GitHub Release page.

Sparkle updates

Floe checks every update against this public key, which is SUPublicEDKey in project.yml:

hEUA26fN0uTXfnilG4w6FP1zhtaEHVbS7TsUqGW8VsU=

The app reads updates from https://thaw-app.github.io/Floe/appcast.xml. The feed itself is served over HTTPS and isn’t signed; the updates it lists are.

Report a problem

If a check fails on an official release, report it privately under the Security Policy.

On this page