Skip to content

Harden Windows signing verification in CI#28

Open
ashfame wants to merge 4 commits into
trunkfrom
hardening/windows-buildkite-signing-verification
Open

Harden Windows signing verification in CI#28
ashfame wants to merge 4 commits into
trunkfrom
hardening/windows-buildkite-signing-verification

Conversation

@ashfame

@ashfame ashfame commented Jul 6, 2026

Copy link
Copy Markdown
Member

Stacked on #27. Follow-up hardening from the PR 21 Windows signing review.

This PR keeps the Windows Azure Trusted Signing migration, but makes CI fail closed around the signed artifact and the downloaded signing tools:

  • wraps native commands in the Windows Buildkite step so failed build commands cannot be masked by later verification commands
  • moves Windows artifact signature verification into .buildkite/commands/verify_windows_signature.ps1
  • fails when no dist/*.exe artifact is present
  • requires WINDOWS_EXPECTED_SIGNER_SUBJECT and verifies the Authenticode signer subject after signtool verify /pa /v
  • optionally checks WINDOWS_EXPECTED_SIGNER_ISSUER when set
  • verifies downloaded SIGNTOOL_PATH and AZURE_CODE_SIGNING_DLIB either by SHA256 pins (WINDOWS_SIGNTOOL_SHA256 / AZURE_CODE_SIGNING_DLIB_SHA256) or by valid Microsoft Authenticode signature
  • adds a Buildkite Tests step so the node:test coverage added in PR 21 actually runs in CI

CI setup note: before this lands, the Windows queue needs WINDOWS_EXPECTED_SIGNER_SUBJECT configured to a stable substring of the expected WordPress/Automattic signing certificate subject. The SHA256 env vars are optional but preferable if the downloaded tool versions are meant to be pinned exactly.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the Windows signing verification flow in Buildkite CI by making the Windows build step fail closed (both for produced artifacts and the downloaded signing tools) and by ensuring the Node test suite actually runs in CI.

Changes:

  • Wraps native command execution in the Windows Buildkite step to prevent non-zero exit codes from being masked.
  • Moves Windows artifact signature verification into a dedicated PowerShell script that enforces presence of dist\*.exe and verifies expected signer subject (and optional issuer).
  • Adds a Buildkite “Tests” step that runs npm test on the default queue.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
.buildkite/pipeline.yml Hardens Windows step execution semantics and adds a CI test step.
.buildkite/commands/verify_windows_signature.ps1 Adds fail-closed Authenticode verification for Windows artifacts, including signer identity checks.
.buildkite/commands/setup_windows_code_signing.ps1 Verifies integrity of downloaded signing tools via SHA256 pinning or Microsoft Authenticode signature validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ashfame
ashfame force-pushed the hardening/windows-buildkite-signing-verification branch from 6656710 to 6d66d8d Compare July 6, 2026 21:39
@ashfame
ashfame requested a review from mokagio July 6, 2026 21:42

@mokagio mokagio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR as well.

As in #27, I'm cursious whether this is a refinement motivated by a failure that was encountered or by caution.

It seems that Buildkite is not reporting the build status in the commits and PR checks, which is a problem we'll need to address.

Image

In the meantime, I can confirm the new tests step passes:

Image

Unfortunately, the new code in the Windows flow fails to parse:

[2026-07-06T21:40:49Z] At line:5 char:27
[2026-07-06T21:40:49Z] +     [Parameter(Mandatory = )]
[2026-07-06T21:40:49Z] +                           ~
[2026-07-06T21:40:49Z] Missing statement after '=' in named argument.
[2026-07-06T21:40:49Z] At line:5 char:27
[2026-07-06T21:40:49Z] +     [Parameter(Mandatory = )]
[2026-07-06T21:40:49Z] +                           ~
[2026-07-06T21:40:49Z] Missing closing ')' in expression.
[2026-07-06T21:40:49Z] At line:6 char:13
[2026-07-06T21:40:49Z] +     [string],
[2026-07-06T21:40:49Z] +             ~
[2026-07-06T21:40:49Z] Parameter declarations are a comma-separated list of variable names with optional initializer expressions.
[2026-07-06T21:40:49Z] At line:6 char:13
[2026-07-06T21:40:49Z] +     [string],
[2026-07-06T21:40:49Z] +             ~
[2026-07-06T21:40:49Z] Missing ')' in function parameter list.
[2026-07-06T21:40:49Z] At line:5 char:27
[2026-07-06T21:40:49Z] +     [Parameter(Mandatory = )]
[2026-07-06T21:40:49Z] +                           ~
[2026-07-06T21:40:49Z] Attribute argument must be a constant or a script block.
[2026-07-06T21:40:49Z]     + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
[2026-07-06T21:40:49Z]     + FullyQualifiedErrorId : MissingExpressionInNamedArgument

Comment thread .buildkite/pipeline.yml Outdated
@ashfame

ashfame commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

@mokagio I don't have much experience with signing apps. I asked AI to review PR 21 at WordCamp Europe and its hardening notes seemed like a good addition to be cautious. So, I asked it to split them in smaller PRs. Feel free to make this PR your own as per your discretion :)

@mokagio

mokagio commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

@ashfame no worries! And thanks for the clarification. There's always room for improvements, and these are welcome indeed.

@juanmaguitar

Copy link
Copy Markdown
Collaborator

@mokagio @ashfame what's the next step for this PR?
It's stacked on top of #27 that is already merged

Base automatically changed from hardening/windows-sign-hook-timeout to trunk July 15, 2026 12:34
Comment thread .buildkite/pipeline.yml
agents: { queue: windows }
plugins: [$CI_TOOLKIT, $NVM_PLUGIN]
command: |
$ErrorActionPreference = "Stop"

@iangmaia iangmaia Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to move this whole command to an external script to avoid unexpected issues with the Buildkite variable interpolation (https://buildkite.com/docs/pipelines/configure/environment-variables#runtime-variable-interpolation). It also makes the code easier to read.

For an example of what might go wrong when using scripts directly in the .yml, I think $true here would need escaping so Buildkite doesn't attempt to replace it.

$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest

& "setup_azure_trusted_signing.ps1"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we run setup_azure_trusted_signing.ps1 from a8c-ci-toolkit-buildkite-plugin, which I think already executes signtool and loads the DLib? Then later in this script, we test the tool integrity after that.

I'd say installation, verification and execution need to be separate phases, so perhaps this should be in a8c-ci-toolkit, immediately after download and before the smoke test.

}

$actualSubject = $signature.SignerCertificate.Subject
if ($actualSubject.IndexOf($env:WINDOWS_EXPECTED_SIGNER_SUBJECT.Trim(), [System.StringComparison]::OrdinalIgnoreCase) -lt 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm finding this check a bit loose (IndexOf on the subject) 🤔 Not sure if this can also change?
Maybe there's a better way, but I'm not sure how it would work (I guess we need'd to look into https://learn.microsoft.com/en-us/azure/artifact-signing/concept-certificate-management#subscriber-identity-validation-eku -- )


foreach ($artifact in $artifacts) {
Write-Host "Verifying Authenticode signature for $($artifact.FullName)"
& $env:SIGNTOOL_PATH verify /pa /v $artifact.FullName

@iangmaia iangmaia Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this, I wonder if we should consider /tw so that "a warning should be generated if the signature is not time stamped"?

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.

5 participants