Code signing
Windows (MSI)
MSI targets are unsigned by default. Enable signing with code-sign = true on the
target; pyappdist build then signs each launcher .exe after it is compiled and
the .msi after it is built.
[[tool.pyappdist.targets]]
name = "win"
platform = "windows-x86_64"
format = "msi"
code-sign = true
# code-sign-command = 'signtool.exe sign ... "{file}"' # optional; default used if omitted
With code-sign = true the signing command is resolved in this order:
the
PYAPPDIST_SIGN_CMDenvironment variable (highest priority);the target’s
code-sign-command;a built-in default:
signtool.exe sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /a "{file}".
The default uses /a to auto-select the best certificate from the Windows certificate
store, so a non-secret command line can live in pyproject.toml; use
PYAPPDIST_SIGN_CMD to override per machine (for example a .pfx whose password
must not be committed). The token {file} is replaced with the path of the artifact
being signed (appended to the command if absent).
When code-sign is unset (or false), signing is skipped regardless of
PYAPPDIST_SIGN_CMD.
Note
Obtaining and managing code-signing certificates is out of scope for pyappdist. Unsigned installers will trigger a Windows SmartScreen warning.
Note
MSIX is not covered by code-sign. Packages submitted to the Microsoft Store are
re-signed on ingestion; for sideloading the package must be signed with a certificate
whose subject matches the manifest Publisher. MSIX is signed only when
PYAPPDIST_SIGN_CMD is set.
macOS (macapp / dmg)
The macapp / dmg formats are always code-signed. Without a
configured identity the bundle is ad-hoc signed (codesign -s -): it runs on
the build machine but Gatekeeper rejects it elsewhere. To distribute, sign with a
Developer ID Application certificate and notarize.
Prerequisites (one-time):
Enroll in the Apple Developer Program and create a Developer ID Application certificate (Xcode → Settings → Accounts, or the Developer portal). Confirm it is installed:
security find-identity -v -p codesigning # 1) ... "Developer ID Application: Your Name (TEAMID)"
Create a
notarytoolkeychain profile from an app-specific password (Sign-In & Security → App-Specific Passwords). The password authorizes the tool, not one app — one profile notarizes every build. pyappdist never sees the password; it lives only in the keychain:xcrun notarytool store-credentials your-notary-profile \ --apple-id you@example.com --team-id TEAMID
Then configure the target (or use the PYAPPDIST_SIGNING_IDENTITY /
PYAPPDIST_NOTARY_PROFILE environment variables):
[[tool.pyappdist.targets]]
name = "macos-arm-dmg"
platform = "macos-aarch64"
format = "dmg"
signing-identity = "Developer ID Application: Your Name (TEAMID)"
notary-profile = "your-notary-profile"
pyappdist build then deep-signs every Mach-O in the bundle with a hardened
runtime, signs the .dmg, submits it to Apple’s notary service, waits, and staples
the ticket so it validates offline. Verify the result:
codesign --verify --deep --strict --verbose=2 dist/MyApp.app
spctl -a -t open --context context:primary-signature dist/MyApp-1.0.dmg
xcrun stapler validate dist/MyApp-1.0.dmg
Notarization runs only when both a Developer ID identity and a notary profile are
set; an ad-hoc build skips it. PYAPPDIST_SIGN_CMD (above) is also applied to the
.dmg as an extra hook if set.