Package and ship

Sign the application

Understand why macOS and Windows warn about unsigned software and prepare platform credentials without committing secrets.

Download an unsigned app on macOS and you get “cannot be opened because the developer cannot be verified”. On Windows, SmartScreen shows a blue warning. Most users stop right there. Code signing is how you avoid that.

A signature does two things. It ties the artifact to a publisher identity, and it lets the operating system detect if the file changed after you signed it.

Two platforms, two systems

macOS wants a Developer ID certificate from Apple, plus notarization: you upload the signed app to Apple, an automated check scans it, and Apple issues a ticket you staple to the app. Without notarization, Gatekeeper still blocks it.

Windows wants an Authenticode certificate from a certificate authority. The two systems have nothing in common. Different certificates, different tools, different prices.

Signing configuration changes often. Certificate providers change requirements, Apple changes the notarization tooling. So follow the current Forge code-signing guide for the exact osxSign, osxNotarize, and Windows maker options. Don’t copy flags from a two-year-old tutorial.

Secrets stay out of the repo

Certificate files, their passwords, Apple app-specific passwords, and signing tokens go in your CI’s secret store. Never in forge.config.js. Never in a .env file committed to Git. Never printed in build logs. Reference them through environment variables and let the release system inject them.

Sign in one place

Sign in a controlled CI job or on a dedicated release machine, not on whichever laptop is handy. Then verify the signature on the final artifact, not on an intermediate app folder.

On macOS:

codesign --verify --deep --strict "out/Desktop Notes-darwin-arm64/Desktop Notes.app"
spctl --assess --type execute "out/Desktop Notes-darwin-arm64/Desktop Notes.app"

On Windows, right-click the installer, open Properties, and check the Digital Signatures tab, or run signtool verify /pa on it.

Then download the published artifact from wherever users get it and verify again. This catches an upload that replaced the file with an unsigned build.

What signing does not prove

A signature proves who published the file and that it’s intact. It doesn’t prove the app is harmless. A signed app can still ship a malicious dependency. Keep the dependency review, malware scanning where required, and the least-privilege work we did in the security module.

Make the failure loud

Run one test release with the signing credentials removed. The pipeline must fail, or at minimum mark the artifact as unsigned in a way nobody can miss. A release job that quietly publishes an unsigned build because a secret expired will do the same in production.

Lesson completed