On this page

Native macOS code signatures

Stado signs native code itself, with stado product signing. A Darwin release worker signs the staged native executables with its own executable before it creates the immutable release archive, so the archive digest describes the signed bytes. Scripts, data files and non-Darwin builds are not re-signed.

One signer: Stado

Release-worker signing, product installation, the Apple challenge helper and runner apphost reconciliation all use stado product signing. There is no second signing program to acquire: the code is the stado-product crate inside Stado.

  • A release worker signs with the executable running the job, and puts that executable's directory first on every build step's PATH, so a build script's stado product signing sign is the same Stado.
  • A provisioned Darwin host signs with the Stado the fleet installed there, ~/.stado/bin/stado. Before signing, Stado asks it for product signing sign --help; a host whose Stado predates the command is refused with <host>: <path> cannot sign native code: \stado product signing sign` is not available there (); converge Stado on this host first`, and no other signer is looked for.

Inspect and repair installed code

stado product signing report jeden --surface cli --json
stado product signing reconcile jeden --surface cli --json
stado product signing inspect ~/.stado/bin/jeden --json

The report includes actual paths, identifiers, Apple teams, authorities, designated requirements, CDHashes, signature states and operating-system errors. Ad-hoc, unsigned, invalid, missing and untrusted code is not a stable installed identity. An executable permission bit or a successful plain codesign --verify is insufficient.

reconcile operates on the product's recorded installed paths. It signs staged copies and replaces a file only after verification. A replacement must satisfy the preceding stable designated requirement. The operation does not restart services, reset TCC, grant Photos or Full Disk Access, or open a browser. An initial migration from ad-hoc signing can require a new macOS consent; later updates preserve the identifier and Apple team.

stado product signatures PRODUCT [--apply], which did the same through the downloaded program, was removed in 0.22.0.

Signing credentials and policy

Without supplied material, stado product signing reads the fleet's Apple certificate and key from the vault item that plays the signing role macos-development-signing, the one live item carrying the tag stado:role:macos-development-signing: a machine running Stado keeps no signing identity of its own, and no item id is named anywhere, so renaming or replacing the item changes nothing. WISENT_CODESIGN_ROLE names another role, and WISENT_CODESIGN_CERTIFICATE_PEM with WISENT_CODESIGN_PRIVATE_KEY_PEM supply the pair directly; either replaces the fleet role. No item in the role, or two, is refused with the role and the command that stores it (stado credentials item put --host <vault owner> --role <role>); an existing item takes the role with stado credentials item retag --host <vault owner> <item> --tags stado:role:<role>. The same role read answers stado credentials get --role <role> --field <field>, and a source installation reads every role#field of a release manifest's secret_env the same way. WISENT_CODESIGN_IDENTITY or --identity selects an exact certificate name or hash. Missing or ambiguous identities refuse the operation. An explicit identity or credential failure never silently selects a different identity. Ad-hoc identity - is refused.

stado product signing sign --product jeden target/release/jeden target/release/jeden-sandbox-helper
stado product signing sign --identifier com.wisent.stado.apple-challenge-capture /path/to/staged-helper
stado product signing stage --manifest .wisent-release.json --output .wisent-output --platform darwin-arm64 --json

--entitlements PATH supplies an explicit entitlement dictionary. Repeatable --boolean-entitlement NAME=true|false merges Boolean values into each target's own signed dictionary, or into that explicit dictionary. Other entries remain unchanged. Empty names, invalid Boolean values and duplicate names are refused before credentials are opened. --hardened-runtime requests the runtime flag; without it, existing runtime flags are preserved.

stado product signing sign --product stado --hardened-runtime \
  --boolean-entitlement com.apple.security.cs.allow-jit=true \
  --boolean-entitlement com.apple.security.cs.allow-unsigned-executable-memory=true \
  --boolean-entitlement com.apple.security.cs.allow-dyld-environment-variables=true \
  --boolean-entitlement com.apple.security.cs.disable-library-validation=true \
  /path/to/staged/Runner.Listener /path/to/staged/Runner.Worker --json

Runner reconciliation runs this command on the host as "$STADO_BIN" product signing sign. Each target's final signed entitlement dictionary and runtime policy are independently inspected. A changed identifier, incompatible requirement or concurrently changed file refuses replacement.

A signing scope signs with exactly one source: the operator's own keychain (WISENT_CODESIGN_KEYCHAIN=<path>, or --signing-keychain <path> on stado product install and update), a vault role (WISENT_CODESIGN_ROLE), a supplied PEM pair (WISENT_CODESIGN_CERTIFICATE_PEM and WISENT_CODESIGN_PRIVATE_KEY_PEM), or, when none is named, the fleet's signing role read from Skarbiec. The identity is named by its name or SHA-1 with WISENT_CODESIGN_IDENTITY, --signing-identity on stado product install|update, or --identity on stado product signing sign; an identity named without a keychain is chosen from the user's keychain search list, and the fleet role is not read either. A named keychain or identity is never overridden by the fleet role. Naming two sources is refused with both names (WISENT_CODESIGN_ROLE and WISENT_CODESIGN_KEYCHAIN each name a signing source; name one), a flag beside its variable set to another value is refused naming both, and a keychain path that cannot be read is refused naming it.

A Darwin source installation resolves its signing source before it exports or builds anything, and reads a role's certificate and key then. If the broker's resolver reports a held, unanswered read, the operation refuses naming that read and the observed wait instead of starting a build. A direct broker endpoint without resolver hold evidence cannot be called healthy from this preflight alone.

An installation recipe can declare signing_keychain: $HOME/Library/Keychains/login.keychain-db. On Darwin, stado product install skarbiec --surface cli (or service / desktop) uses the local keychain on the machine doing a source installation, before building or reading the fleet signing role. A missing named file refuses with WISENT_CODESIGN_KEYCHAIN names the keychain <path>, which cannot be read; a directory refuses as not a regular file. An explicit different --signing-keychain or WISENT_CODESIGN_KEYCHAIN, or a role/PEM source, refuses rather than replacing the catalog declaration. --signing-identity may select a valid identity in that keychain; an absent or ambiguous identity refuses with the keychain and available identities. Native source output is signed from the declared keychain even when the build brought a stable signature; an installed identity change still requires the operator to name it. The receipt's signing names the selected keychain and each native path's observed authority, team, identifier and CDHash. A verified release archive is already signed and is never re-signed.

The real installation journey uses the built Stado, the canonical Skarbiec checkout and an actual valid identity, with a stopped isolated loopback Skarbiec endpoint. Run it on a dedicated test account whose STADO_TEST_INSTALL_HOME contains that checkout and has no Skarbiec installation or catalog keychain. Supply a separate STADO_TEST_SIGNING_KEYCHAIN containing the real signing identity, STADO_TEST_SIGNING_IDENTITY as that identity's full authority name, STADO_TEST_STOPPED_SKARBIEC_URL, STADO_TEST_SKARBIEC_SOURCE and STADO_BIN; run node tests/install/skarbiec_signing.mjs from the Stado checkout. The journey checks the missing-keychain and conflicting-source refusals, installs and removes the product, reads the receipt and independently inspects the placed native signature. The retained .build/skarbiec-signing-*/report.json binds source revision, binary and test digests, commands, exit statuses, stderr, stdout and observed state. A missing real input fails the journey; a written test or a build alone is not an installation result.

The identity comes from the scope's keychain (or the search list) in this order: the one named; the one a role's or PEM pair's certificate carries; the identity the installed code is signed by; the keychain's only valid identity. A keychain the operator named that holds several valid identities and none named is refused listing them. Signing with another identity than the one the installed code is signed by changes its designated requirement, so macOS grants given to the installed program no longer apply to it: that change is only the operator's explicit choice. Without a named identity the change is refused (the installed code is signed by <previous>, which the keychain <path> does not hold as a valid identity; … name the keychain with WISENT_CODESIGN_KEYCHAIN (--signing-keychain) and the new identity, by its name or SHA-1, with WISENT_CODESIGN_IDENTITY (--signing-identity)). With it, the new signature is not held to the installed requirement, and the signature report and the installation's receipt carry identity_change: {"previous": <installed identity>, "new": <new identity>} (the receipt under identity_changes, per installed path).

Stado updating itself while its own resolver, and with it Skarbiec, is down is that case: the installed Stado may be signed by the fleet identity while the operator's keychain holds only his own development identity. Name both:

stado product update stado --surface cli --source-commit <commit> \
  --signing-keychain ~/Library/Keychains/login.keychain-db \
  --signing-identity '<identity name or SHA-1 from security find-identity -v -p codesigning>'

The update's report names the change from the fleet identity to the operator's.

A supplied certificate and key are checked against each other, staged as owner-only files under ~/.stado/signing/.wisent-identity-<uuid>/ and the identity alone is imported into a temporary keychain there. Signing scopes on one host take turns: each waits for, then holds, ~/.stado/signing/scope.lock from making its keychain until it has deleted it, and a waiting scope prints waiting for …/scope.lock: held by pid <pid> (running) since <time>: <command>. The scope adds its own keychain to the user's keychain search list and takes only that entry off again when it ends, leaving every other entry as the list holds it then; it removes its temporary material, and cleanup errors remain errors. Apple's public intermediates, and the issuers a supplied certificate carries, live in ~/.stado/signing/apple-issuers.keychain-db, made once and never deleted, which every scope unlocks and keeps on the search list: an intermediate imported into a temporary keychain and deleted with it left the next scope's codesign failing unable to build chain to self-signed root with errSecInternalComponent. It does not import into a login keychain or open a system dialog. WISENT_CODESIGN_ISSUERS_FILE supplies an optional public issuer bundle; repeated certificates are imported once. The owner-local build guide describes the credential read and its refusals.

Each signature unlocks Stado's own temporary keychain again and puts it back on the search list immediately before codesign reads the key, because a release build signs minutes after the keychain was made and a keychain that locked in between fails a finished build with errSecInternalComponent. A keychain the operator named with WISENT_CODESIGN_KEYCHAIN is never unlocked. A failed codesign carries signing keychain <path> and what each of these answered at that moment, exit status and words: security show-keychain-info on it, security find-identity -v -p codesigning on it, security list-keychains -d user, security find-certificate -a -c "Apple Worldwide Developer Relations" -Z, security dump-trust-settings and security verify-cert -c <leaf> -p codeSign; together they show whether the keychain was locked or absent, whether its identity validates, whether codesign searches it, which intermediates the search list holds and where the chain evaluation stops.

The Apple challenge helper and runner reconciliation receive the fleet's certificate, with Apple's pinned issuer chain appended, and its key through the host channel and the signing child's environment, never process arguments. After signing, the challenge helper's preparation still reconciles Accessibility and performs the prompt-free readiness check. A matching helper version alone does not establish a reusable stable signature or a macOS permission grant.

Stado Desktop

Settings → Native code signatures on the Stado API host runs stado product signing report through the selected API host. Read signatures is read-only. Repair signatures shows the selected product and endpoint before it runs stado product signing reconcile. The screen retains stdout, stderr, exit status, endpoint and any refusal or truncation; the application has no second signing implementation. A host running a Stado older than 0.22.0 refuses both, because it has no product signing command.

Source: this website