Skip to content

ci(release): assinar instaladores desktop assim que os secrets de assinatura existirem (E-4) - #18

Merged
LMPrado-DZ23 merged 3 commits into
release/v3.8.52from
ci/electron-signing
Sep 12, 2026
Merged

LMPrado-DZ23 merged 3 commits into
release/v3.8.52from
ci/electron-signing

Conversation

@LMPrado-DZ23

@LMPrado-DZ23 LMPrado-DZ23 commented Sep 12, 2026 •

Copy link
Copy Markdown
Owner

Resumo

Liga a assinatura de código do app desktop (E-4 em audit/RELEASE_READINESS.md) no electron-release.yml. A assinatura passa a funcionar sozinha no momento em que o dono do repositório criar os repository secrets.

Importante: este PR não assina nada por si só. Enquanto os secrets abaixo não existirem, os instaladores continuam sem assinatura, idênticos ao que sai hoje, e o workflow segue verde: o Windows mostra o aviso do SmartScreen e o macOS bloqueia a primeira abertura. Os certificados precisam ser comprados/emitidos pelo dono; nenhum certificado, chave, senha ou Apple ID foi criado ou commitado.

Secrets que o dono precisa criar

Plataforma Opção Secrets
macOS, assinatura obrigatória para assinar MAC_CSC_LINK (.p12 do Developer ID Application em base64), MAC_CSC_KEY_PASSWORD
macOS, notarização A: API key do App Store Connect (recomendada) APPLE_API_KEY_P8, APPLE_API_KEY_ID, APPLE_API_ISSUER
macOS, notarização B: Apple ID APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID
Windows A: certificado Authenticode WIN_CSC_LINK (.pfx em base64), WIN_CSC_KEY_PASSWORD
Windows B: Azure Trusted Signing (tem precedência) AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TRUSTED_SIGNING_ENDPOINT, AZURE_TRUSTED_SIGNING_ACCOUNT, AZURE_TRUSTED_SIGNING_PROFILE

O passo a passo completo está em docs/guides/ELECTRON_GUIDE.md §Code Signing: exportar em base64, pré-requisitos e custo, e verificar a assinatura. Depois de criar os secrets, dá para re-disparar o workflow para o tag existente:

gh workflow run electron-release.yml --ref release/v3.8.51 -f version=v3.8.51 -f publish_npm=false

Atenção ao que esse re-disparo faz. Todos os jobs de build fazem checkout do tag da versão (needs.validate.outputs.version), não do branch disparado. O re-disparo portanto só re-anexa artefatos construídos a partir do código no tag v3.8.51.

  • No v3.8.51, ele consegue anexar instaladores macOS assinados, mas não o Windows: o tag (1054f19) é anterior à correção do Windows mergeada em 18ec68e, e o run 34710550989, disparado de release/v3.8.51, falhou exatamente nessa perna.
  • Instaladores assinados de código novo, incluindo essa correção, exigem um tag novo (ex.: v3.8.52).

Correção de comentário (commit separado, a pedido do coordenador)

O comentário do workflow_dispatch no electron-release.yml dizia que o dispatch constrói o ref disparado, o que é falso. web-build, build e release fazem checkout de ref: ${{ needs.validate.outputs.version }}. Conferido: o run 34710550989 foi disparado de release/v3.8.51 em 18ec68e, e a perna Windows falhou construindo o tag v3.8.51 = 1054f19, que não contém 18ec68e (git merge-base --is-ancestor → NOT in tag).

  • O comentário foi reescrito.
  • O guia foi corrigido.
  • O teste guarda passou a exigir que os três jobs façam checkout do tag e que o comentário diga isso.
  • O comportamento do checkout não mudou.

O que mudou

  • .github/workflows/electron-release.yml
    • O passo de build recebe os secrets só via env:, cada um condicionado ao matrix.os: a perna Windows nunca vê credencial Apple, e vice-versa. Nada é interpolado no run:.
    • Um checkout esparso do helper, pinado por SHA como os demais e com persist-credentials: false, vem do commit do próprio workflow. Sem isso, re-disparar o build para o tag v3.8.51, que é anterior ao helper, falharia por arquivo ausente.
  • scripts/build/electron-signing.mjs: decide por perna e roda npm run build:<target> com o ambiente saneado. Decisões baseadas no código do app-builder-lib 26.15.3, a versão do lockfile:
    • platformPackager.getCscLink() usa chooseNotNull (== null), então CSC_LINK="", que é como um secret ausente chega ao job, conta como definido. Além disso, macPackager.codeSigningInfo só pula a criação do keychain quando o link é == null. Por isso o helper apaga as variáveis vazias em vez de repassá-las.
    • util/flags.isAutoDiscoveryCodeSignIdentity() é !== "false". Com certificado presente, CSC_IDENTITY_AUTO_DISCOVERY=false faz o findIdentity retornar null e o build sair sem assinatura. Por isso o valor false só é usado nas pernas mac sem certificado.
    • mac/MacTargetHelper.getNotarizeOptions() lê as variáveis por truthiness e lança erro com conjunto parcial. O helper antecipa isso e falha a perna listando só os nomes dos secrets que faltam. Com certificado mas sem nenhuma credencial de notarização, o build sai assinado sem notarização e com um warning.
    • @electron/notarize 2.5.0 passa appleApiKey para notarytool --key, ou seja, espera um caminho. O .p8 vai para um arquivo 0600 no RUNNER_TEMP, removido no finally.
    • A simples presença de win.azureSignOptions troca o winPackager para o gerenciador Azure (winPackager.js), por isso esse bloco não pode ficar fixo no package.json. O helper o injeta só na cópia do CI quando o conjunto Azure está completo e restaura o arquivo depois. O build é removido do package.json empacotado (fileTransformer.ignoredPackageMetadataProperties).
    • O log mostra uma linha por perna, signing: enabled (...) ou signing: disabled (missing secret MAC_CSC_LINK), sempre sem valores.
  • electron/package.json (build.mac) e entitlements, aplicados só em build assinado (MacTargetHelper.buildSignOptions só roda quando há identidade):
    • hardenedRuntime: true.
    • entitlements → electron/assets/entitlements.mac.plist: allow-jit e allow-unsigned-executable-memory, necessários ao V8.
    • entitlementsInherit → electron/assets/entitlements.mac.inherit.plist: os mesmos dois mais disable-library-validation. O servidor roda no Helper com ELECTRON_RUN_AS_NODE e faz dlopen dos addons nativos. É o mesmo conjunto do template padrão do electron-builder, que esses binários receberiam sem este arquivo.
  • tests/unit/electron-release-signing-e4.test.ts: faz o parse do YAML e trava:
    • secrets só em env: (ou no secrets: de workflow reutilizável), nunca em run:;
    • gating por SO e o conjunto exato de secrets;
    • checkout do helper sem ref e todas as actions pinadas por SHA;
    • ausência de valores literais de segredo;
    • fallback sem assinatura, com os secrets expandidos para "";
    • conjuntos parciais falhando sem ecoar valores;
    • Azure com precedência sobre certificado, e o certificado mac nunca chegando ao Windows;
    • entitlements e notarize, identity e forceCodeSigning não definidos.
  • Docs: docs/guides/ELECTRON_GUIDE.md §Code Signing reescrita, e a linha E-4 de audit/RELEASE_READINESS.md agora diz "pipeline pronto; pendentes só os certificados/segredos".

Decisões assumidas / divergências

  • DECISÃO ASSUMIDA: conjunto parcial de credenciais falha a perna, e não degrada em silêncio. Sem nenhum secret, o comportamento é o de hoje: build sem assinatura e verde. O job release continua fail-partial, então as outras plataformas publicam.
  • DECISÃO ASSUMIDA: não incluí com.apple.security.network.client/server, pedidos na especificação. Esses entitlements só têm efeito sob App Sandbox, e o hardened runtime não restringe sockets.
  • DECISÃO ASSUMIDA: o helper vem do commit do workflow. Um tag anterior a este PR, como o v3.8.51, assina com os padrões do electron-builder: hardened runtime ligado e o template de entitlements.

Verificação (saída real, local, Windows)

Testes (node --test): os 12 novos do primeiro commit mais os testes existentes (o commit de correção adicionou um 13º, "every build job checks out the version TAG, and the dispatch comment says so": re-execução 27/27 pass, eslint exit=0, check:docs-all exit=0) que inspecionam o workflow e o electron/package.json.

✔ secrets are referenced only via env: (or a reusable workflow's secrets:), never in run:
✔ each signing secret is handed only to its own OS legs, and the run body is expression-free
✔ the signing helper comes from the workflow's own commit, so an older tag can be re-signed
✔ every third-party action in the release workflow is pinned to a full commit SHA
✔ no secret value literal anywhere in the signing surface
✔ without secrets every leg builds unsigned, exactly as before E-4
✔ macOS signs with the certificate and prefers the App Store Connect API key for notarization
✔ a partial credential set fails loudly and names only the missing secrets
✔ Windows signs with Azure Trusted Signing when complete, else with the Authenticode certificate
✔ helper inputs are validated: build target allowlist and the .p8 encoding
✔ macOS uses the hardened runtime with minimal entitlements; notarization stays env-driven
✔ the owner documentation lists every secret and the re-attach command
ℹ tests 38
ℹ pass 38
ℹ fail 0

(os 38 incluem electron-release-desktop-channel-8949, electron-release-efficiency, build/electron-release-latest-yml.repro, distribution-identity, electron-packaging e wreq-native-manifest)

Gates:

eslint (script + teste) ............ exit=0
prettier --check (arquivos alterados) All matched files use Prettier code style!
npm run check:workflows ............ [check-workflows] SKIP — actionlint and zizmor not found in PATH. (exit=0)
YAML parse (js-yaml) ............... OK; build steps: 17
npm run check:docs-all ............. exit=0 (doc-links PASS; fabricated-docs ✓ No fabricated API/env/CLI/hook/file references found)

Caminhos de erro do helper, que saem antes de qualquer build:

--target "win && calc"  → [electron-signing] --target must be one of: win, mac-x64, mac-arm64, linux (exit=1)
--target linux (win32)  → [electron-signing] build:linux must run on linux, not win32 (exit=1)
Azure parcial           → ::error title=Electron signing::signing: incomplete Azure Trusted Signing configuration — missing secret(s) AZURE_CLIENT_ID, AZURE_TRUSTED_SIGNING_ENDPOINT, ... (exit=1, sem ecoar valores)

O que NÃO dá para verificar sem certificados reais

  • Assinatura e notarização de fato: codesign, notarytool, staple e a avaliação do Gatekeeper.
  • Assinatura Authenticode e Azure Trusted Signing de fato, incluindo o Install-Module TrustedSigning, que o electron-builder faz no runner.
  • Se o app assinado com hardened runtime sobe corretamente no macOS. O smoke da perna macos-intel roda depois do build assinado e pega crash de inicialização, mas não detecta um fallback silencioso do SQLite nativo para sql.js.
  • zizmor/actionlint não rodaram localmente (SKIP). Acompanhar o job de workflow lint no CI: o passo novo usa o mesmo actions/checkout pinado por SHA e passa os secrets só por env:, mas o ratchet zizmorFindings pode se mover se a versão do zizmor no runner tiver auditorias novas sobre uso de secrets.

🤖 Generated with Claude Code

…xist

DECISÃO ASSUMIDA: a partial credential set (e.g. APPLE_API_KEY_ID without
APPLE_API_ISSUER, or an incomplete Azure set) fails that leg with the list of
missing secret names instead of silently degrading — electron-builder would
throw mid-build anyway. No secrets at all keeps today's unsigned, green build.
DECISÃO ASSUMIDA: the signing helper is sparse-checked-out from the workflow's
own commit, not from the tag being built, so re-attaching signed installers to
v3.8.51 (a tag that predates the helper) works instead of failing on a missing
file; such a tag signs with electron-builder's defaults.
DECISÃO ASSUMIDA: no network.client/server entitlements — they only apply under
App Sandbox, which a Developer ID app does not use.

v3.8.51 ships unsigned installers (SmartScreen on Windows, Gatekeeper on
macOS) and electron-release.yml had no signing wiring. The certificates must
be bought/enrolled by the owner; this makes the pipeline sign the moment the
repository secrets exist.

- electron-release.yml: the build step receives MAC_CSC_LINK /
  MAC_CSC_KEY_PASSWORD / APPLE_API_KEY_P8 / APPLE_API_KEY_ID /
  APPLE_API_ISSUER / APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID
  (darwin legs only) and WIN_CSC_LINK / WIN_CSC_KEY_PASSWORD / AZURE_* (win32
  leg only) through env:, never interpolated into run:.
- scripts/build/electron-signing.mjs: verified against app-builder-lib
  26.15.3. An empty CSC_LINK counts as SET there (chooseNotNull, == null), so
  empty values are deleted, not forwarded. CSC_IDENTITY_AUTO_DISCOVERY=false
  only on unsigned mac legs (with a cert it would make findIdentity return
  null). The .p8 goes to a 0600 temp file (notarytool --key takes a path).
  win.azureSignOptions is added to the CI checkout only when the Azure set is
  complete, because its mere presence switches winPackager to Azure. Logs
  "signing: enabled/disabled (missing secret X)" with names only.
- electron/package.json: mac.hardenedRuntime + entitlements (JIT, unsigned
  executable memory) and entitlementsInherit (+ disable-library-validation for
  the Helper that runs the server and dlopens native addons), applied only when
  signed.
- tests/unit/electron-release-signing-e4.test.ts pins secrets-via-env-only,
  per-OS gating, the unsigned fallback, no secret literals, SHA pins and the
  entitlement set.
- docs/guides/ELECTRON_GUIDE.md: the exact secrets, .p12/.pfx base64 export,
  Apple and Windows options, prerequisites, and the re-attach command.
  audit/RELEASE_READINESS.md E-4: pipeline ready, certificates pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 13b29539-cb49-4434-b589-3e4caefa6e13


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

zodyprado-web and others added 2 commits September 12, 2026 16:39
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d ref

The workflow_dispatch comment in electron-release.yml claimed a dispatch
builds the ref it is dispatched on. It does not: web-build, the matrix build
and release all check out `ref: needs.validate.outputs.version`, the version
tag. Run 34710550989 was dispatched from release/v3.8.51 at 18ec68e and its
Windows leg still built tag v3.8.51 (1054f19), which lacks the fix merged
at 18ec68e, and failed again. The checkout behaviour is unchanged (pinning
to the tag keeps a release reproducible); only the description is corrected.

- Rewrite the comment: a dispatch re-attaches assets built from the code at
  the tag; shipping new code means a new version tag (e.g. v3.8.52).
- ELECTRON_GUIDE.md: the re-attach command only re-signs what is at tag
  v3.8.51, so it can attach signed macOS installers but not a Windows one;
  signed installers of new code need a new tag.
- Guard test: web-build, build and release must each check out
  needs.validate.outputs.version, and the header comment must say so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LMPrado-DZ23
LMPrado-DZ23 changed the base branch from release/v3.8.51 to release/v3.8.52 September 12, 2026 19:52
@LMPrado-DZ23
LMPrado-DZ23 merged commit 3d1aebf into release/v3.8.52 Sep 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants