Skip to content

ci: sign the Windows release executable with SignPath - #394

Merged
thcp merged 1 commit into
mainfrom
feat/signpath-windows-signing
Aug 19, 2026
Merged

ci: sign the Windows release executable with SignPath#394
thcp merged 1 commit into
mainfrom
feat/signpath-windows-signing

Conversation

@thcp

@thcp thcp commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Signs StemDeck.exe in both Windows release zips using SignPath.io with a SignPath Foundation OSS certificate.

Why the workflow had to be split

SignPath's OSS tier requires that all jobs leading up to a signing request run on GitHub-hosted agents (docs). The Windows release runs entirely on the self-hosted runner, so it could not submit a request as-is.

Rather than move the whole build (ClamAV needs a Linux Docker image, the zips are near the 2 GiB release-asset cap, torch venvs are heavy), only the executable build moves to windows-latest:

  • sign-exe (GitHub-hosted): stamp version -> tauri build -> upload stemdeck.exe -> submit to SignPath -> upload signed exe
  • build-and-upload (self-hosted, needs: sign-exe): download signed exe -> package both zips around it -> ClamAV -> release upload

Only StemDeck.exe is signed. The zips are not Authenticode containers; they stay covered by the published .sha256. Bundled python.exe/*.dll keep their python.org signatures.

Side effect: the CPU package no longer triggers a second full Rust rebuild, since both variants reuse the one signed binary.

Changes

  • .github/workflows/windows-release.yml - new sign-exe job; build-and-upload consumes its artifact; workflow_dispatch dry-run entry point with a release-only guard on the upload step; Authenticode status reported in the scan step.
  • scripts/windows/make-portable.ps1 - new -PrebuiltExe parameter that skips the npm/Tauri build and packages a supplied executable, throwing if it carries no signature.
  • .signpath/policies/stemdeck/release-signing.yml - disallow_reruns: true. Branch rulesets left out until the repo has matching rulesets configured, since SignPath rejects requests that do not satisfy them.
  • README.md, packaging/windows/README-WINDOWS.txt - code signing policy and attribution required by the SignPath Foundation terms.

Required before this can run

Portal side (not in this PR):

  1. Install the SignPath GitHub App on this repo.
  2. Create the project (slug stemdeck) and link the GitHub.com trusted build system.
  3. Artifact configuration - upload-artifact zips by default, so the root element is zip-file:
    <artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
      <zip-file>
        <pe-file path="stemdeck.exe">
          <authenticode-sign />
        </pe-file>
      </zip-file>
    </artifact-configuration>
  4. Signing policies test-signing and release-signing.
  5. CI user with Submitter permission + API token.

Repo side:

  • Secret SIGNPATH_API_TOKEN
  • Variable SIGNPATH_ORGANIZATION_ID

Verification

  1. workflow_dispatch on this branch with signing_policy: test-signing - nothing is published; confirm the signing request appears in the SignPath console and the scan step reports a non-NotSigned status.
  2. Cut a pre-release tag and confirm the run blocks on approval, completes inside the 3600s timeout, and the extracted StemDeck.exe reports Status: Valid with a SignPath Foundation signer.
  3. Run the published exe on a clean Windows machine and confirm SmartScreen shows a verified publisher.

Risk

SignPath's origin check is documented as covering jobs "leading up to" the request. build-and-upload is self-hosted but runs strictly after via needs:, so it should be out of scope. If SignPath rejects it anyway, the fallback is moving sign-exe into its own workflow file.

SignPath's OSS tier requires every job leading up to a signing request to run
on a GitHub-hosted agent, but the Windows release runs entirely on the
self-hosted runner. Split the workflow instead of moving the whole build:

- New `sign-exe` job on `windows-latest` builds only StemDeck.exe, uploads it
  as a workflow artifact, submits it to SignPath, and republishes the signed
  binary as an artifact. Version files are stamped before the build because
  SignPath restricts Foundation projects on PE product name and version.
- `build-and-upload` now depends on it, downloads the signed executable, and
  packages both variants around it via a new `-PrebuiltExe` flag on
  make-portable.ps1. That also removes the redundant second Rust build the CPU
  package used to trigger.
- make-portable.ps1 rejects an unsigned prebuilt binary before it reaches the
  zip, and the scan step reports the Authenticode status of what was packaged.
- workflow_dispatch entry point plus a release-only guard on the upload step so
  the integration can be exercised against a test-signing policy without
  cutting a tag.

Requires repo secret SIGNPATH_API_TOKEN and repo variable
SIGNPATH_ORGANIZATION_ID.

Adds the code signing policy and attribution required by the SignPath
Foundation terms.
@thcp
thcp marked this pull request as ready for review August 19, 2026 09:42
@thcp
thcp merged commit 7eba634 into main Aug 19, 2026
14 of 16 checks passed
@thcp
thcp deleted the feat/signpath-windows-signing branch August 19, 2026 09:43
thcp added a commit that referenced this pull request Aug 20, 2026
SIGNPATH_API_TOKEN and SIGNPATH_ORGANIZATION_ID aren't configured in the
repo yet, so a release-triggered run of the sign-exe job added in #394
fails on main today, not just waits on approval. That blocks cutting any
release directly from main until SignPath is finished.

This reverts #394 so main goes back to the pre-SignPath, working Windows
release path. That's what let v0.11.2 skip the blocker (cut from a
branch off the v0.11.1 tag) instead of going through main; with this
reverted, main itself is unblocked for normal direct-to-main work and
future patch releases.

Re-apply when picking SignPath back up: git revert this commit (or
cherry-pick 7eba634 again) once the secrets/variable are set and the
SignPath project/policy is approved, then cut v0.12.0.

## Test plan
- [x] Clean revert, no conflicts beyond the already-open Unraid pin PR
(#398), which targets a different file section
meefs pushed a commit to meefs/stemdeck that referenced this pull request Aug 20, 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