Code Signing
Sign and notarize your Native SDK app for distribution.
macOS signing
Sign the bundle with a Developer ID:
native package --target macos --signing identity --identity "Developer ID Application: Your Name"Identity signing enables the hardened runtime and explicitly requests Apple's secure timestamp. If the timestamp service cannot be reached, packaging fails instead of producing a distribution signature that notarization will reject.
Signing modes:
| Mode | Description |
|---|---|
none | No signing (default) |
adhoc | Ad-hoc signing for local testing |
identity | Sign with a named identity (requires --identity) |
What Gatekeeper shows for an ad-hoc package
An ad-hoc signature carries no developer identity, so Gatekeeper cannot verify who built the app. What that means in practice depends on whether the copy carries the quarantine attribute:
- Downloaded through a browser (or anything else that sets quarantine): the first launch shows the standard dialog — macOS "cannot check it for malicious software" / cannot verify the app "is free of malware". This is not an error in the package; it is Gatekeeper's honest answer for any app without a notarized Developer ID signature. The user can still run it: right-click (or Control-click) the app in Finder, choose Open, and the dialog gains an Open button that launches the app and remembers the choice.
- Copied over scp, USB, rsync, or a local build: these transfers set no quarantine attribute, so the app launches directly with no dialog at all.
Either way the signature itself is valid — codesign --verify --strict passes on every package the toolkit signs, and the packaging test suite pins that (zig build test-package-signing). If Gatekeeper ever calls a package "damaged" instead of showing the dialog above, the bundle was modified after signing (a broken resource seal), which is a real defect — not the expected ad-hoc experience.
For distribution without any dialog, sign with a Developer ID (--signing identity) and notarize; see below.
Signing flags
| Flag | Description |
|---|---|
--signing | Signing mode: none, adhoc, or identity |
--identity | Code signing identity name |
--entitlements | Path to entitlements file (e.g. assets/native-sdk.entitlements) |
--notarize | Submit the final macOS artifact, then staple and validate the accepted ticket |
--notary-profile | Name of a notarytool Keychain profile created with store-credentials |
Notarization
Store notarization credentials in Keychain once. A team App Store Connect API key is the normal CI choice:
xcrun notarytool store-credentials "my-app-release" \
--key /path/to/AuthKey_KEYID.p8 \
--key-id KEYID \
--issuer ISSUER_UUIDApple ID credentials work too:
xcrun notarytool store-credentials "my-app-release" \
--apple-id "you@example.com" \
--team-id TEAMID \
--password APP_SPECIFIC_PASSWORDThen package, timestamp-sign, notarize, staple, and validate in one command:
native package --target macos \
--signing identity \
--identity "Developer ID Application: Your Name (TEAMID)" \
--archive \
--notarize \
--notary-profile "my-app-release"With --archive, Native SDK first submits the signed app, staples and validates it, then builds and signs the DMG and submits, staples, and validates that final image. Without --archive, only the app submission runs. Any updater ZIP is created afterward from the stapled app.
The framework repository exposes the same path for local release testing:
zig build notarize \
-Didentity="Developer ID Application: Your Name (TEAMID)" \
-Dnotary-profile="my-app-release"Chromium apps
Chromium packages include Chromium Embedded Framework.framework inside the .app. Sign and notarize the final package after CEF has been bundled so the app binary, helper executables, and embedded framework are covered by the same distribution identity.
native cef install --version <pinned-version>
zig build
native package --target macos --signing identity --identity "Developer ID Application: Your Name" --archiveUse .web_engine = "chromium" and .cef = .{ .dir = "third_party/cef/macos", .auto_install = false } in app.zon for the normal signing path. -Dweb-engine, --web-engine, -Dcef-dir, and --cef-dir remain available for temporary overrides.
If Gatekeeper rejects the app, check that the CEF framework is present in Contents/Frameworks, that every nested helper is signed, and that the package was rebuilt after any CEF version change.
DMG creation
Create a distributable disk image:
native package --target macos --archiveThis creates the conventional drag-to-Applications presentation by default: a generated background and arrow, positioned app and Applications icons, hidden Finder chrome, and a compressed final image. Customize it through the dmg fields in app.zon.
Entitlements
The project includes assets/native-sdk.entitlements as a starting point. Customize it for your app's needs (e.g. network access, file system access, camera).