Skip to content

bug(windows): unsigned release binaries are blocked by Smart App Control — sign via Azure Artifact Signing (Thinkery AG) #1820

Closed

Description

@yvgude

Summary

Released lean-ctx.exe binaries carry no Authenticode signature. On a clean Windows 11 install, Smart App Control (SAC) is enabled by default and blocks unsigned downloaded executables outright — with no "run anyway" path, unlike SmartScreen.

Reported by a community member; confirmed against release.yml.

Evidence

.github/workflows/release.yml builds and zips the Windows targets with no signing step:

line 275:  Compress-Archive -Path "rust/target/${{ matrix.target }}/release/lean-ctx.exe" ...

No signtool, no certificate, no signing service. (macOS is unsigned too, but Gatekeeper still offers an override, so the user impact is lower.)

Who this affects

Smart App Control only runs on clean Windows 11 installs — an in-place upgrade from Windows 10 leaves it off, and once a user disables it, it stays off until a reinstall. So this does not affect the existing Windows install base, which evidently works.

It does affect new users on new machines — precisely at first contact, before anyone knows whether the tool is worth any effort. The README promises "Get started (30 seconds)"; these users instead hit a security block.

Workarounds (now documented in the README)

  • cargo install lean-ctx — builds locally; SAC treats a self-compiled binary far more leniently than a download.
  • WSL — lean-ctx runs as a native Linux binary there; agents reach it via wsl lean-ctx mcp.
  • Disabling SAC works but is a bad trade: system-wide, permanent, reversible only by reinstalling Windows. We should not recommend it.

Fix: Azure Artifact Signing, certificate issued to Thinkery AG

Switzerland is a supported country for Public Trust certificates, and there is a Switzerland North region (https://swn.codesigning.azure.net). The service was formerly called Trusted Signing.

This is preferred over the free SignPath Foundation route for three reasons:

SignPath Foundation Azure Artifact Signing
Certificate issued to SignPath Foundation Thinkery AG
Covers the OSS project only everything, including commercial builds
"No commercial dual-licensing on any component" clause a real risk for us not applicable
Revocation "without notice, potentially retroactively" ordinary contract
Cost free paid SKU (Basic/Premium)

The decisive point: a SignPath certificate may only sign the open-source project. Any Enterprise binary, installer or Windows package for paying customers needs our own certificate anyway — doing this twice is waste. Secondarily, the Windows signature dialog then reads Thinkery AG rather than a foundation's name.

What organisation validation requires

  • Legal entity name exactly as registered — Thinkery AG
  • Business identifier — the CHE/UID number
  • Registered business address
  • Website on a company-owned domain
  • Two email addresses on a company-owned domain, both able to receive external links
  • Individual identity validation by an authorised representative: government ID, via AU10TIX on a phone (QR code + Microsoft Authenticator)

Plus an Azure subscription and a Microsoft Entra tenant.

Processing time: 1–20 business days, longer if Microsoft requests further documents.

Two traps worth avoiding up front

  1. Details must be exact. Per the docs, corrections after submission require an entirely new identity validation, which affects certificates already issued for signing.
  2. Public records must be current. Microsoft reconciles against the commercial register. Supporting documents must have been issued within the last 12 months and remain valid for at least two more.

CI integration

  • GitHub Action: azure/artifact-signing-action
  • Endpoint must match the account's region exactly — a mismatch surfaces as 403 Forbidden with an internal SignerSign() failure
  • Timestamping is mandatory, not optional: signing certificates are valid for only three days, so a signature without a timestamp stops validating almost immediately. Use http://timestamp.acs.microsoft.com/

Caveat

Signing alone does not clear Smart App Control immediately. SAC additionally weighs reputation, which accrues with distribution. Expect a lag after the first signed release.

Fallback

If the Azure route is rejected or stalls, SignPath Foundation remains available free of charge — Apache 2.0, public repo, 3,818 stars all satisfy their published criteria. The open question there is their "no commercial dual-licensing applied to any component" clause; it should be declared in the application rather than worked around, since they can revoke retroactively. Accepting also obliges us to publish a "Code signing policy" page, define Author/Reviewer/Approver roles, and approve every release manually.

Acceptance criteria

  • Windows install section documents the SAC block and both clean workarounds
  • Azure subscription + Entra tenant in place for Thinkery AG
  • Organisation identity validation submitted (register details verified beforehand)
  • Certificate profile created in Switzerland North
  • Signing step in release.yml via azure/artifact-signing-action, with timestamping
  • Verify a signed release actually passes Smart App Control on a clean Windows 11 VM

Activity

  1. changed the title [-]bug(windows): unsigned release binaries are blocked by Smart App Control — free Authenticode signing via SignPath[/-] [+]bug(windows): unsigned release binaries are blocked by Smart App Control — sign via Azure Artifact Signing (Thinkery AG)[/+] on Sep 20, 2026
  2. yvgude commented on Sep 21, 2026

    @yvgude
    OwnerAuthor

    Closing this: the signing work is done and proven in CI. The one remaining criterion is not something this issue can wait on, so it moves to its own ticket (see below).

    What is in place

    Verified against main, not assumed:

    • Azure subscription + Entra tenant — release.yml and windows-signing-check.yml authenticate via secretless OIDC using vars.AZURE_CLIENT_ID / AZURE_TENANT_ID / AZURE_SUBSCRIPTION_ID, gated by the windows-signing GitHub environment. The login succeeds in real runs.
    • Organisation identity validation — completed by implication and by outcome: a Public Trust certificate profile cannot issue signatures before the organisation passes validation, and signatures are being issued.
    • Certificate profile in Switzerland North — .github/actions/windows-signing/action.yml targets https://swn.codesigning.azure.net/, signing account thinkery-signing, profile leanctx-public-trust. That is the regional endpoint this issue called for, and the 403 Forbidden trap described above does not apply.
    • Signing step with timestamping — azure/artifact-signing-action@v2 (SHA-pinned), file-digest: SHA256, timestamp-rfc3161: http://timestamp.acs.microsoft.com, timestamp-digest: SHA256. Mandatory timestamping was the trap flagged in this issue; it is configured, not left to chance.
    • Verification is enforced, not hoped for — the action's final step runs scripts/verify-windows-signature.ps1, so an unsigned or untimestamped artifact fails the job instead of shipping.

    Evidence: run 35576765725 — Sign and verify x86_64-pc-windows-msvc: success and Sign and verify x86_64-pc-windows-gnu: success. Both Windows targets, signed and verified.

    Landed in #1823.

    What is not done, and why it does not belong here

    • Verify a signed release actually passes Smart App Control on a clean Windows 11 VM

    This cannot be checked yet: no signed release has shipped. And per this issue's own caveat, SAC additionally weighs reputation that accrues with distribution — so even the first signed release may still be blocked, and a single early test would tell us little either way.

    Keeping this issue open until that clears would leave it open for an unknown period on a question the signing work cannot answer. Tracked separately in #1825.

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions