Notarization¶
Spectre ships the macOS ScreenCaptureKit recorder as SpectreCaptureHelper.app inside the
spectre-recording-macos jar. Distribution builds must Developer ID sign, notarize, and
staple the full app bundle before the jar is published, otherwise macOS Gatekeeper can reject
the extracted helper when a consumer starts ScreenCaptureKitRecorder.
TCC Screen Recording grants pin to bundle ID + code signature. Ad-hoc-signed local builds
lose Settings grants on every rebuild; release builds keep the grant across versions when the
same Developer ID identity and bundle id (dev.sebastiano.spectre.screencapture) are used.
Local development builds do not need Apple credentials. The notarization path is opt-in and is only intended for release builds.
What gets notarized¶
The release build creates a universal arm64 + x86_64 helper binary, packages it as:
Gradle then:
- Packages the universal Mach-O into
SpectreCaptureHelper.app(Info.plist,LSUIElement, icon, nestedContents/MacOS/spectre-screencapture). - Developer ID signs the bundle with
codesign --sign <identity> --options runtime --timestamp --force --deep. - Archives the signed app with
ditto -c -k --keepParent. - Submits the archive with
xcrun notarytool submit --wait(30 minute timeout). - Staples the ticket with
xcrun stapler stapleonto the.app. - Verifies with
codesign --verify --deep --strictandxcrun stapler validate. - Best-effort
spctl -a -vv --type execute(warn-only if the host policy database rejects it). - Stages the signed+stapled app into
native/macos/SpectreCaptureHelper.appin thespectre-recording-macosjar resources.
Unlike bare command-line tools, app bundles support stapling and Gatekeeper assessment of the distributed artifact.
Local-dev signing story (ad-hoc vs release)¶
| Build | Signature | TCC behaviour | When to use |
|---|---|---|---|
Default ./gradlew :recording:jar |
Ad-hoc (codesign -s -) on the host-arch app |
Grant may bind to path/ad-hoc identity; re-grant after clean rebuilds | Day-to-day development |
-PuniversalHelper without notarize |
Ad-hoc universal app | Same as above | Fat-binary packaging smoke |
-PuniversalHelper -PnotarizeScreenCaptureKitHelper |
Developer ID + notary + staple | Stable Settings row for Spectre Capture Helper; grant survives updates with the same identity | Release / release smoke |
Do not Developer ID sign local iteration builds with a personal cert and then ship an ad-hoc rebuild under the same install path without expecting a re-prompt — keep release identity on the release path only.
Local setup¶
Install Xcode or the Xcode Command Line Tools so these commands are available:
Create and install a Developer ID Application certificate through Apple Developer, then confirm Keychain can see it:
The identity should look like:
Store notarization credentials in Keychain once. Let notarytool prompt for the app-specific
password:
xcrun notarytool store-credentials <notary-profile> \
--apple-id "developer@example.com" \
--team-id "TEAMID"
Do not pass the app-specific password to Gradle or notarytool on the command line. macOS
process listings include command arguments while a submission is running.
Local release smoke¶
Add non-secret release properties to ~/.gradle/gradle.properties:
compose.desktop.mac.signing.identity=Developer ID Application: Example Developer (TEAMID)
compose.desktop.mac.notarization.keychainProfile=<notary-profile>
Then run:
./gradlew :recording:assembleScreenCaptureKitHelperUniversal \
-PuniversalHelper \
-PnotarizeScreenCaptureKitHelper
If Apple accepts the submission, the build staples the ticket, verifies the app signature, and
stages the app into jar resources. If Apple keeps the submission in In Progress past the
timeout, the Gradle task fails locally but the submission continues processing on Apple's side.
Query it with:
Quarantined Gatekeeper check¶
After a successful local or CI notarization, validate as a user would:
APP=recording/build/generated/screenCaptureHelper/native/macos/SpectreCaptureHelper.app
# Simulate download quarantine
xattr -w com.apple.quarantine "0081;00000000;Safari;..." "$APP" # or copy via browser/zip
codesign --verify --deep --strict --verbose=2 "$APP"
xcrun stapler validate "$APP"
spctl -a -vv --type execute "$APP"
CI release workflow¶
The tag workflow at
.github/workflows/release.yml
builds the universal helper on macos-latest, imports the Developer ID certificate into a
temporary keychain, signs / notarizes / staples SpectreCaptureHelper.app, and uploads the
notarized app artifact for the publish job to package into spectre-recording-macos.
Set these repository secrets:
| Secret | Purpose |
|---|---|
APPLE_DEVELOPER_ID_P12 |
Base64-encoded Developer ID Application .p12. |
APPLE_DEVELOPER_ID_P12_PASSWORD |
Password for that .p12 export. |
APPLE_SIGNING_KEYCHAIN_PASSWORD |
Temporary CI keychain password. Use a long random value. |
APPLE_DEVELOPER_IDENTITY |
Exact codesign identity, for example Developer ID Application: Example Developer (TEAMID). |
APPLE_NOTARY_API_KEY |
Base64-encoded App Store Connect API .p8 key. |
APPLE_NOTARY_API_KEY_ID |
App Store Connect API key ID. |
APPLE_NOTARY_API_ISSUER |
App Store Connect API issuer UUID. |
Use App Store Connect API key auth in CI so no app-specific password is passed as a process
argument. The workflow writes the .p8 key to $RUNNER_TEMP and passes only the file path,
key id, and issuer to Gradle.
Rotation¶
To rotate the Developer ID certificate:
- Create or renew a Developer ID Application certificate in Apple Developer.
- Export it from Keychain Access as a password-protected
.p12. - Base64-encode it with
base64 -i DeveloperID.p12 | pbcopy. - Update
APPLE_DEVELOPER_ID_P12,APPLE_DEVELOPER_ID_P12_PASSWORD, andAPPLE_DEVELOPER_IDENTITYin the repository secrets. - Note: TCC grants bound to the old Team ID may require users to re-approve once after a Team ID change.
To rotate notarization credentials:
- Create a new App Store Connect API key with access to notarization.
- Base64-encode the
.p8file. - Update
APPLE_NOTARY_API_KEY,APPLE_NOTARY_API_KEY_ID, andAPPLE_NOTARY_API_ISSUER. - Push a test tag and verify the release workflow reaches the
stapler validatestep.
Validation¶
After a notarized release, validate on a fresh macOS user or machine:
codesign --verify --deep --strict --verbose=2 SpectreCaptureHelper.app
xcrun stapler validate SpectreCaptureHelper.app
spctl -a -vv --type execute SpectreCaptureHelper.app
Then run a small ScreenCaptureKitRecorder scenario (or spectre permissions check) from the
released jar. The Settings row should show Spectre Capture Helper, and Gatekeeper should not
reject the extracted app.