Skip to content

Adds code signing to tagged windows builds#4473

Open
rekhoff wants to merge 1 commit intomasterfrom
rekhoff/windows-code-signing
Open

Adds code signing to tagged windows builds#4473
rekhoff wants to merge 1 commit intomasterfrom
rekhoff/windows-code-signing

Conversation

@rekhoff
Copy link
Contributor

@rekhoff rekhoff commented Feb 26, 2026

Note: This change requires the addition of new entries in the secrets to work properly. These should be added prior to this merging.

Description of Changes

  • Add a tag-only Windows signing job that runs on a self-hosted signing runner.
    • This is an alternative/separate code-path just for the signing job. See Alternatives Considered for details.
  • Skip the unsigned Windows matrix build on tags so signed artifacts are the only Windows release outputs.
  • Sign spacetimedb-update.exe, spacetimedb-cli.exe, and spacetimedb-standalone.exe before packaging, then upload the signed artifacts as usual.

Alternatives Considered

Inline signing in the existing Windows packaging step. This was rejected because it would require all Windows builds (including non-tag builds) to run on the signing-capable runner or to install/signing tooling on GitHub-hosted runners. The chosen approach isolates signing to tag releases, avoids exposing credentials in standard builds, and keeps routine CI behavior unchanged.

API and ABI breaking changes

None

Expected complexity level and risk

2 – low risk. CI-only change that adds a new signing job and preserves existing artifact layout.

Testing

  • None (Not running, workflow change only)

@rekhoff rekhoff self-assigned this Feb 26, 2026
Comment on lines 13 to 90
@@ -85,3 +86,89 @@ jobs:
source_dir: build
endpoint: https://nyc3.digitaloceanspaces.com
destination_dir: ${{ steps.extract_branch.outputs.branch }}

build-cli-windows-signed:
if: ${{ startsWith(github.ref, 'refs/tags/') }}
name: Build and sign CLI for x86_64 Windows
runs-on: [self-hosted, windows, signing]
environment: codesign
concurrency:
group: codesign-${{ github.ref }}
cancel-in-progress: false

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install Rust
uses: dsherret/rust-toolchain-file@v1

- name: Install rust target
run: rustup target add x86_64-pc-windows-msvc

- name: Compile
run: |
cargo build --release --target x86_64-pc-windows-msvc -p spacetimedb-cli -p spacetimedb-standalone -p spacetimedb-update

- name: Write certificate file
shell: powershell
env:
DIGICERT_CERT_B64: ${{ secrets.DIGICERT_CERT_B64 }}
run: |
[IO.File]::WriteAllBytes("digicert.crt", [Convert]::FromBase64String($env:DIGICERT_CERT_B64))

- name: Sign binaries
shell: powershell
env:
DIGICERT_KEYPAIR_ALIAS: ${{ secrets.DIGICERT_KEYPAIR_ALIAS }}
run: |
$ErrorActionPreference = 'Stop'
$targetDir = Join-Path $env:GITHUB_WORKSPACE 'target\x86_64-pc-windows-msvc\release'
$certFile = Join-Path $env:GITHUB_WORKSPACE 'digicert.crt'

$signtool = Get-Command signtool.exe -ErrorAction Stop

$files = @(
(Join-Path $targetDir 'spacetimedb-update.exe'),
(Join-Path $targetDir 'spacetimedb-cli.exe'),
(Join-Path $targetDir 'spacetimedb-standalone.exe')
)

foreach ($file in $files) {
& $signtool.Path sign /csp "DigiCert Signing Manager KSP" /kc $env:DIGICERT_KEYPAIR_ALIAS /f $certFile /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 $file
& $signtool.Path verify /v /pa $file
}

- name: Package (windows)
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
New-Item -ItemType Directory -Force -Path build | Out-Null
$releaseDir = Join-Path $env:GITHUB_WORKSPACE 'target\x86_64-pc-windows-msvc\release'

Copy-Item (Join-Path $releaseDir 'spacetimedb-update.exe') (Join-Path $env:GITHUB_WORKSPACE 'build\spacetimedb-update-x86_64-pc-windows-msvc.exe')
Compress-Archive -Force -Path @(
(Join-Path $releaseDir 'spacetimedb-cli.exe'),
(Join-Path $releaseDir 'spacetimedb-standalone.exe')
) -DestinationPath (Join-Path $env:GITHUB_WORKSPACE 'build\spacetime-x86_64-pc-windows-msvc.zip')

- name: Extract branch name
shell: powershell
run: |
$ErrorActionPreference = 'Stop'

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 4 days ago

To fix this, explicitly scope down the GITHUB_TOKEN permissions used by this workflow to the minimum needed. Since the jobs only need to read repository contents (for actions/checkout) and do not modify GitHub resources, contents: read at the workflow or job level is sufficient.

The simplest, non‑disruptive fix is:

  • Add a top‑level permissions: block just under name: in .github/workflows/package.yml, setting contents: read.
  • This applies to all jobs (build-cli and build-cli-windows-signed) that don’t define their own permissions, so no further changes are necessary.

No extra imports, methods, or definitions are needed; this is purely a YAML configuration change in .github/workflows/package.yml.

Suggested changeset 1
.github/workflows/package.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
--- a/.github/workflows/package.yml
+++ b/.github/workflows/package.yml
@@ -1,4 +1,6 @@
 name: Package SpacetimeDB CLI
+permissions:
+  contents: read
 
 on:
   push:
EOF
@@ -1,4 +1,6 @@
name: Package SpacetimeDB CLI
permissions:
contents: read

on:
push:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +91 to +174

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 4 days ago

In general, the fix is to explicitly declare a permissions block for the workflow or for individual jobs, limiting the GITHUB_TOKEN to the least privilege necessary. Here, the jobs only need to read repository contents (for actions/checkout) and then use external secrets for uploads; they do not create releases, write to issues, or push tags. Therefore, setting contents: read at the workflow root is sufficient and applies to all jobs that don’t override it.

The single best fix is to add a top-level permissions block near the top of .github/workflows/package.yml, alongside name and on, with contents: read. This keeps behavior unchanged for the workflow while ensuring the token can’t be used for unintended write operations. No imports or additional methods are needed, as this is purely a YAML configuration change.

Concretely:

  • Edit .github/workflows/package.yml.
  • Insert:
permissions:
  contents: read

between the existing name: Package SpacetimeDB CLI and the on: block (or anywhere at the root level, but that spot is conventional and clear).

Suggested changeset 1
.github/workflows/package.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
--- a/.github/workflows/package.yml
+++ b/.github/workflows/package.yml
@@ -1,5 +1,8 @@
 name: Package SpacetimeDB CLI
 
+permissions:
+  contents: read
+
 on:
   push:
     tags:
EOF
@@ -1,5 +1,8 @@
name: Package SpacetimeDB CLI

permissions:
contents: read

on:
push:
tags:
Copilot is powered by AI and may make mistakes. Always verify output.
@rekhoff rekhoff marked this pull request as ready for review February 26, 2026 01:35
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