Docs Ship
Alpha documentation — APIs may change between 0.x releases.

Distribution: signing, notarization, and packaging

vidra build turns a project into something you can hand to someone else: a macOS .dmg or a self-contained Windows .zip. Producing that file is the easy half. The other half is convincing the operating system to run it.

An app that arrives over the internet is treated differently from one you built locally. macOS attaches a quarantine flag to downloaded files and asks Gatekeeper for a verdict; Windows shows a SmartScreen warning for unrecognized binaries. Neither cares that the app works on your machine — they care whether it carries a signature they trust.

Nothing here is mandatory to build. With no signing configured, vidra build still produces a working artifact — it warns about what’s missing and carries on. Everything below is opt-in by environment variable.

Default build artifacts

TargetArtifactRuns without extra installs?
macOSdist/<App>-<version>-macos.dmg (contains the .app and an /Applications symlink)Yes
Windowsdist/<App>-<version>-windows.zip (self-contained, unpackaged)Yes — except the WebView2 runtime, see below

Windows builds still produce a ZIP. Extract it and run the host executable inside the app directory.

With --native-update, the public artifacts keep the same names and the build also writes an update feed:

TargetAdditional output
Windowsdist/<App>-<version>-Setup.exe and dist/release/
macOSdist/release/

The Windows -windows.zip is a hand-packaged app directory in a default build and a renamed Velopack portable archive in a native-update build. Treat both as portable app ZIPs; do not depend on an identical internal directory layout.

Preview every step, and the exact artifact name, without running anything:

npx vidra build --target macos --plan

The plan reflects your actual environment: it reports whether a Developer ID certificate is present and whether notarization credentials are configured.

macOS

Choosing a signing identity

Vidra picks a certificate based on what the signature is for:

ContextPreferred certificateWhy
vidra dev / vidra runApple Development:Local launches only. Free with any Apple ID.
vidra buildDeveloper ID Application:The only kind that can be notarized.

If vidra build can only find a development certificate it will use it and warn loudly — the result runs locally and will be rejected on every other Mac. With no certificate at all it falls back to ad-hoc signing (-).

Override the choice with VIDRA_MACOS_CODESIGN_KEY:

export VIDRA_MACOS_CODESIGN_KEY="Developer ID Application: Your Name (TEAMID)"

List available identities with security find-identity -v -p codesigning, or run npx vidra doctor.

The hardened runtime and entitlements

Notarization requires the hardened runtime (codesign --options runtime). The hardened runtime, by default, forbids exactly the things .NET needs. Enabling it without matching entitlements produces an app that signs and notarizes but dies when it launches.

Scaffolded projects ship src/<App>.Host/Entitlements.plist:

EntitlementWhy it’s needed
com.apple.security.cs.allow-jit.NET’s JIT compiles into executable memory
com.apple.security.cs.allow-unsigned-executable-memorythe runtime writes then executes that memory
com.apple.security.cs.disable-library-validationit loads dylibs signed by a different team
com.apple.security.network.clientthe WebView reaches the Vite dev server
com.apple.security.files.user-selected.read-writefilePicker returns paths the app then reads
com.apple.security.app-sandbox = falseVidra’s unrestricted filesystem API is incompatible with an App Store sandbox

Both vidra build and MAUI’s signing pass use this file.

Do not add XML comments to Entitlements.plist. The MacCatalyst SDK’s PList reader rejects comments in the document body.

Projects scaffolded before this file existed must copy it and the <CodesignEntitlements> property from a new project.

How the bundle is signed

Signing runs inside-out: nested .dylib/.so files and .framework bundles are signed first, deepest path first, then the .app. Vidra deliberately does not use codesign --deep, which Apple does not support for submission. Every signature is timestamped. The .dmg is signed once a real identity exists.

Notarization

Notarization uploads the artifact to Apple, waits for a verdict, and staples the ticket so it validates offline. It requires an Apple Developer Program membership.

Store credentials once:

xcrun notarytool store-credentials vidra-notary \
  --apple-id you@example.com --team-id ABCDE12345 --password <app-specific-password>

export VIDRA_NOTARY_PROFILE=vidra-notary
npx vidra build --target macos

Or supply credentials directly for CI:

export VIDRA_APPLE_ID=you@example.com
export VIDRA_TEAM_ID=ABCDE12345
export VIDRA_APP_PASSWORD=abcd-efgh-ijkl-mnop

With no credentials, notarization is skipped. A rejected submission fails the build, and Vidra fetches and prints the notarytool rejection log automatically.

Verifying the result

After signing, vidra build reports:

  • codesign --verify --strict — the signature is well-formed and the bundle has not changed. This must pass.
  • spctl --assess — Gatekeeper’s verdict. Rejection is expected until the build is both Developer ID signed and notarized.

Inspect an existing artifact without rebuilding:

npx vidra verify
npx vidra verify dist/MyApp-0.1.0-macos.dmg
npx vidra verify dist/MyApp-0.1.0-windows.zip
npx vidra verify path/to/MyApp.app

To simulate a downloaded macOS artifact, apply quarantine and open it:

xattr -w com.apple.quarantine "0081;$(printf %x $(date +%s));Safari;" dist/*.dmg
open dist/*.dmg

Windows

Authenticode

vidra build --target windows signs the app executable before zipping. Point Vidra at a certificate file:

$env:VIDRA_WINDOWS_CERT_PATH = "C:\certs\vidra.pfx"
$env:VIDRA_WINDOWS_CERT_PASSWORD = "..."

Or use a certificate already in the store:

$env:VIDRA_WINDOWS_CERT_THUMBPRINT = "4FB0491E0F13CDBDAE24BF16F09E4989F038471B"

Signatures use SHA-256 and are timestamped. Override the default DigiCert timestamp authority with VIDRA_WINDOWS_TIMESTAMP_URL. Vidra finds signtool.exe from the installed Windows SDK, or accepts VIDRA_SIGNTOOL_PATH.

With nothing configured, the build is unsigned and still runs. SmartScreen warns users until the binary develops reputation.

The WebView2 runtime

The ZIP bundles .NET and the WindowsAppSDK, but WebView2 is a machine-wide runtime. It ships with Windows 11 and alongside Edge on most Windows 10 systems. On stripped images, a Vidra app may launch to a blank window. npx vidra doctor reports whether WebView2 is installed.

Environment variable reference

VariablePlatformEffect
VIDRA_MACOS_CODESIGN_KEYmacOSOverride automatic signing identity selection
VIDRA_NOTARY_PROFILEmacOSnotarytool keychain profile; enables notarization
VIDRA_APPLE_IDmacOSApple ID for notarization
VIDRA_TEAM_IDmacOSApple Developer team identifier
VIDRA_APP_PASSWORDmacOSApp-specific notarization password
VIDRA_WINDOWS_CERT_PATHWindowsPath to a .pfx certificate
VIDRA_WINDOWS_CERT_PASSWORDWindowsPassword for the .pfx
VIDRA_WINDOWS_CERT_THUMBPRINTWindowsCertificate-store thumbprint
VIDRA_WINDOWS_TIMESTAMP_URLWindowsTimestamp authority
VIDRA_SIGNTOOL_PATHWindowsExplicit path to signtool.exe

VIDRA_NOTARY_PROFILE takes precedence over the Apple ID credential set.

Troubleshooting

SymptomCause
App cannot be opened because the developer cannot be verifiedNot notarized, or not Developer ID signed
App is killed immediately after signingHardened runtime without JIT entitlements
Failed to parse PList data type:An XML comment inside Entitlements.plist
no Entitlements.plist foundProject predates the template file
Notarization rejectedInspect the notarytool log output Vidra prints
Blank window on WindowsWebView2 runtime missing
Signature later stops validatingThe signature was not timestamped

Updating without a native release

Shipping only a new web UI does not require signing, notarization, or an installer. See Updates. Native changes still use the flow on this page, and contract fingerprints prevent an incompatible web bundle from running against an older binary.