Skip to content

feat(release): code signing ready to switch on, and a code signing policy - #51

Merged
httpsphl merged 3 commits into
mainfrom
feat/code-signing
Sep 30, 2026
Merged

httpsphl merged 3 commits into
mainfrom
feat/code-signing

Conversation

@httpsphl

Copy link
Copy Markdown
Owner

What

Botloft's releases are still unsigned, and 0.6.0 showed why that matters: Microsoft Defender's machine-learning detection quarantined the installer and then the installed app on the owner's PC (Wacatac.C!ml, a false positive, reported to Microsoft). This PR gets everything ready for a certificate, without one yet.

  • One signing script. app/scripts/sign.mjs <file> signs a binary with signtool and the certificate picked by BOTLOFT_SIGN_THUMBPRINT from the Windows certificate store, timestamps it (RFC 3161) and checks the result.
    • Without the variable it does nothing, so dev builds and today's releases stay unsigned.
    • With the variable but no certificate it fails, so a release never goes out unsigned by mistake.
  • Who runs it. Tauri (bundle.windows.signCommand) for everything that goes into the installer: Botloft.exe, the botloftd.exe sidecar, the NSIS plugins, the uninstaller and the installer. setup.mjs for the setup window, which Tauri does not build. release.yml passes the repository variables BOTLOFT_SIGN_THUMBPRINT and BOTLOFT_SIGN_TIMESTAMP.
  • botloftd.exe has version information. It had none. Now: product "Botloft", description "Botloft background service", the crate's version and the copyright. A signing service checks this, and a binary with no metadata is one of the things antivirus heuristics distrust.
  • Code signing policy (docs/code-signing-policy.md, linked from the README): the status (not signed yet), how releases are made and approved, who may release, what goes over the network, what Botloft changes on the computer and how to remove it.
  • Spec 15.5 describes the signing path, the certificate options looked at for an individual in Brazil (Azure Artifact Signing is not available; SignPath Foundation, Certum Open Source and SSL.com are) and the Defender false positive. Spec 15.7 records the real update from 0.5.0 to 0.6.0.

Tests

  • cargo fmt, cargo clippy --workspace --all-targets -D warnings and the daemon's tests pass; Biome passes.
  • sign.mjs without the variable: prints "not signing" and exits 0. With a thumbprint that does not exist: finds signtool in the Windows SDK and fails with "No certificates were found".
  • pnpm bundle locally: Tauri called the script for the sidecar, Botloft.exe, five NSIS plugins, the uninstaller and the installer, and the installer was built. Only the updater signature step failed, since that key lives in CI.
  • node scripts/setup.mjs --rehearse: the setup window went through the script too.
  • botloftd.exe now shows its product, description, version and copyright in its file properties.

Still to do, once there is a certificate

  • The workflow step that makes the certificate available on the runner (signing in to the cloud key's client), before "Build the signed installer".
  • Set the repository variable BOTLOFT_SIGN_THUMBPRINT, then check a real signature on a release and scan it again.

The daemon's executable had no version information at all. It now
carries the product name "Botloft", the description "Botloft background
service", the crate's version and the copyright, from a VERSIONINFO block
in botloftd.rc that build.rs fills in. Windows shows it in the file's
properties and in Task Manager, a code signing service checks it against
the product it signs, and a binary with no metadata is one of the things
antivirus heuristics distrust.
… is a certificate

scripts/sign.mjs signs one binary with signtool and the certificate
picked by BOTLOFT_SIGN_THUMBPRINT from the Windows certificate store,
timestamps it and checks the result. Without the variable it does
nothing, so dev builds and releases made before the certificate exists
stay unsigned; with it and no certificate it fails, so a release never
goes out unsigned by mistake.

Tauri runs it (bundle.windows.signCommand) for everything that goes into
the installer: the app, the daemon, the NSIS plugins, the uninstaller and
the installer itself. setup.mjs runs it for the setup window. release.yml
passes the repository variables. The updater's signature and the build
attestation are made afterwards, over the signed files.

Spec 15.5 describes it, the certificate options looked at for an
individual in Brazil, and the Defender false positive on 0.6.0 that made
signing necessary; 15.7 records the real update from 0.5.0 to 0.6.0.
… the computer

Says that releases are not signed yet and what that means, how releases
are made and approved, who may release, what goes over the network (the
update check and the bots' own Claude Code), what Botloft changes on the
computer (per-user install, the scheduled task, the startup entry, keep
awake) and how to remove it. The README links to it from the privacy
section and from the FAQ.
@httpsphl
httpsphl enabled auto-merge September 30, 2026 13:17
@httpsphl
httpsphl merged commit 8509d71 into main Sep 30, 2026
3 checks passed
@httpsphl httpsphl mentioned this pull request Sep 30, 2026
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.

1 participant