Binary distribution is the root of trust — code signing / notarization story #63

Closed
opened 2026-07-12 17:16:16 -04:00 by the_og · 1 comment
Owner

Why (raised by the adversarial review on #61)

We are building signature verification for the server catalog (#61) on the premise that CATALOG_PUBKEYS compiled into the binary is trustworthy. That premise is the whole thing.

If BCC releases aren't code-signed/notarized and fetched over a channel users verify, an attacker who can tamper with the binary replaces CATALOG_PUBKEYS and every catalog control evaporates. PyInstaller datas have no integrity of their own — the binary's signature is the only root of trust.

Polishing catalog security while the binary itself is unsigned is polishing the wrong surface. This doesn't block #61 (the catalog work is still correct and worth doing), but it caps how much the catalog controls are actually worth, and it should be understood and decided on rather than left implicit.

What to figure out

  • macOS: Developer ID signing + notarization. Without it, users get Gatekeeper warnings anyway — so this is a UX win as well as a security one. Costs an Apple Developer account ($99/yr).
  • Windows: Authenticode. Historically expensive; cheaper options now exist (Azure Trusted Signing, and cert issuers with cheaper OV certs). Unsigned = SmartScreen warnings on every download.
  • Linux: no equivalent mechanism; publish detached signatures + checksums alongside the release archives and document verification.
  • At minimum, and free right now: publish SHA-256 checksums for every release artifact, and sign the checksum file with the same Ed25519 key used for the catalog (or a dedicated release key). Costs nothing, and makes tampering detectable by anyone who bothers to check.
  • Decide honestly whether the paid options are worth it at this project's scale, and write the decision down — including "we accept this risk," if that's the call. An accepted, documented risk is fine; an unexamined one is not.

Note

Doing the free checksum+signature step alone is a meaningful improvement and should probably happen regardless of what's decided about paid certificates.

Related: #61 (signed catalog — depends on this being sound), #19 (update checker: notify-only, no self-replacement — that decision looks better and better in this light).

## Why (raised by the adversarial review on #61) We are building signature verification for the server catalog (#61) on the premise that `CATALOG_PUBKEYS` compiled into the binary is trustworthy. **That premise is the whole thing.** > If BCC releases aren't code-signed/notarized and fetched over a channel users verify, an attacker who can tamper with the binary replaces `CATALOG_PUBKEYS` and every catalog control evaporates. PyInstaller `datas` have no integrity of their own — the binary's signature is the only root of trust. Polishing catalog security while the binary itself is unsigned is polishing the wrong surface. This doesn't block #61 (the catalog work is still correct and worth doing), but it caps how much the catalog controls are actually worth, and it should be understood and decided on rather than left implicit. ## What to figure out - [ ] **macOS:** Developer ID signing + notarization. Without it, users get Gatekeeper warnings anyway — so this is a UX win as well as a security one. Costs an Apple Developer account ($99/yr). - [ ] **Windows:** Authenticode. Historically expensive; cheaper options now exist (Azure Trusted Signing, and cert issuers with cheaper OV certs). Unsigned = SmartScreen warnings on every download. - [ ] **Linux:** no equivalent mechanism; publish detached signatures + checksums alongside the release archives and document verification. - [ ] **At minimum, and free right now:** publish SHA-256 checksums for every release artifact, and sign the checksum file with the same Ed25519 key used for the catalog (or a dedicated release key). Costs nothing, and makes tampering detectable by anyone who bothers to check. - [ ] Decide honestly whether the paid options are worth it at this project's scale, and **write the decision down** — including "we accept this risk," if that's the call. An accepted, documented risk is fine; an unexamined one is not. ## Note Doing the free checksum+signature step alone is a meaningful improvement and should probably happen regardless of what's decided about paid certificates. Related: #61 (signed catalog — depends on this being sound), #19 (update checker: notify-only, no self-replacement — that decision looks better and better in this light).
the_og added the P1 label 2026-07-12 17:16:16 -04:00
Author
Owner

Decision (AJ, 2026-07-12): no paid certificates. Do the free half, document the limit honestly.

Investigated, all dead ends for a zero-budget project:

  • macOS — Gatekeeper trusts only Apple-issued Developer ID certs ($99/yr). Self-signed and ad-hoc signing are treated identically to unsigned. No free tier exists.
  • Windows Authenticode — needs a CA-issued cert (~$200–400/yr). Azure Trusted Signing is cheaper but requires a paid Azure subscription and ~3 years of verifiable org history; we'd fail eligibility regardless.
  • Sigstore / cosign keyless — would be free, but requires an OIDC identity from a Fulcio-trusted CI provider. Self-hosted Gitea is not one.

What we WILL do (free, and a real integrity guarantee):

  • Publish SHA256SUMS for every release artifact (macOS/Windows/Linux archives), generated in release.yml.
  • Sign SHA256SUMS with the same Ed25519 key used for the catalog (#61) → SHA256SUMS.sig, attached to the release.
  • Publish the public key in the README (and it's already compiled into the binary via CATALOG_PUBKEYS).
  • Document the verification one-liner in the README.
  • Document the limit plainly: this makes tampering detectable by anyone who checks; it does NOT remove Gatekeeper / SmartScreen warnings. Users will still right-click→Open on macOS. Say so rather than implying more safety than exists.

Accepted risk, recorded: BCC binaries are unsigned. A user who downloads over TLS from git.avezzano.io and does not verify checksums is trusting the transport and the host. Signed checksums move that from assumed to verifiable, which is the most that can be done at zero cost. Revisit if the project ever has a budget.

## Decision (AJ, 2026-07-12): no paid certificates. Do the free half, document the limit honestly. **Investigated, all dead ends for a zero-budget project:** - **macOS** — Gatekeeper trusts only Apple-issued Developer ID certs ($99/yr). Self-signed and ad-hoc signing are treated identically to unsigned. No free tier exists. - **Windows Authenticode** — needs a CA-issued cert (~$200–400/yr). Azure Trusted Signing is cheaper but requires a paid Azure subscription *and* ~3 years of verifiable org history; we'd fail eligibility regardless. - **Sigstore / cosign keyless** — would be free, but requires an OIDC identity from a Fulcio-trusted CI provider. Self-hosted Gitea is not one. **What we WILL do (free, and a real integrity guarantee):** - [ ] Publish `SHA256SUMS` for every release artifact (macOS/Windows/Linux archives), generated in `release.yml`. - [ ] Sign `SHA256SUMS` with the same Ed25519 key used for the catalog (#61) → `SHA256SUMS.sig`, attached to the release. - [ ] Publish the public key in the README (and it's already compiled into the binary via `CATALOG_PUBKEYS`). - [ ] Document the verification one-liner in the README. - [ ] **Document the limit plainly**: this makes tampering *detectable by anyone who checks*; it does NOT remove Gatekeeper / SmartScreen warnings. Users will still right-click→Open on macOS. Say so rather than implying more safety than exists. **Accepted risk, recorded:** BCC binaries are unsigned. A user who downloads over TLS from git.avezzano.io and does not verify checksums is trusting the transport and the host. Signed checksums move that from *assumed* to *verifiable*, which is the most that can be done at zero cost. Revisit if the project ever has a budget.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: the_og/better-claude-config#63