Skip to content

Pin workflow actions to commit SHAs and sign the SSMS extension - #610

Merged
erikdarlingdata merged 3 commits into
devfrom
ci/pin-actions-sign-vsix
Sep 28, 2026
Merged

erikdarlingdata merged 3 commits into
devfrom
ci/pin-actions-sign-vsix

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

This PR hardens the GitHub Actions workflows. It has three commits.

  1. Pin every workflow action to a commit SHA. No action changes version.
  2. Sign the SSMS extension and its installer with SignPath during a release.
  3. Make two SSMS release steps fail safe, and limit where the installer looks for the VSIX.

Which component(s) does this affect?

  • Desktop App (PlanViewer.App)
  • Core Library (PlanViewer.Core)
  • CLI Tool (PlanViewer.Cli)
  • SSMS Extension (PlanViewer.Ssms)
  • Tests
  • Documentation

The SSMS box is checked because the release now signs the extension and its installer, and commit 3 changes the installer. Every other change is in .github/workflows.

Commit 1: pin actions to commit SHAs

Every uses: line in .github/workflows now names a full 40-character commit SHA. A comment after the SHA names the exact version tag. The change touches 23 lines in 8 files.

Each pin is the commit that the action's major tag pointed at. So no action moves to a new version. I resolved each SHA with gh api repos/OWNER/REPO/commits/TAG. I then found the exact version tag on that commit. After the edit, I resolved each exact tag and each major tag again. Both matched the SHA in the file.

Action Major tag before Exact tag Commit SHA
actions/checkout v7 v7.0.1 3d3c42e5aac5ba805825da76410c181273ba90b1
actions/setup-dotnet v6 v6.0.0 a98b56852c35b8e3190ac28c8c2271da59106c68
actions/upload-artifact v7 v7.0.1 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
actions/deploy-pages v5 v5.0.1 368f82528645a54fb793d4d04e342629a3f51346
actions/upload-pages-artifact v5 v5.0.0 fc324d3547104276b827a68afc52ff2a11cc49c9
microsoft/setup-msbuild v3 v3.0.0 30375c66a4eea26614e0d39710365f22f8b0af57
signpath/github-action-submit-signing-request v3 v3.0 f6d04783b4569d051e0c80105fe66e82819d0092
dorny/paths-filter v4 v4.0.3 ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d
anthropics/claude-code-action v1 v1.0.236 8ce9314fa9a404564fa7e954cd84f25bcba2b829

.github/dependabot.yml already has a github-actions entry, added in commit 06a0081. It uses directory: "/" and target-branch: dev. It has the same weekly Monday 06:00 America/New_York schedule as the nuget entry. Dependabot updates a SHA pin and its version comment together.

I added no second entry, because a second entry duplicates the first. The existing entry uses the commit prefix ci, and the nuget entry uses deps. I left the prefix alone.

Commit 2: sign the SSMS extension and installer

The release built PlanViewer.Ssms.vsix and InstallSsmsExtension.exe but uploaded them unsigned. This commit adds five steps to release.yml. They run after Replace unsigned Windows build with signed and before Create release. Each step runs only when the SSMS build step set BUILT to true.

  1. Stage SSMS files for signing copies exactly InstallSsmsExtension.exe and PlanViewer.Ssms.vsix to the root of a new folder named ssms-unsigned.
  2. Upload SSMS files for signing uploads that folder as the artifact Ssms-unsigned, with if-no-files-found: error.
  3. Sign SSMS extension and installer submits the artifact to SignPath with the Vsix artifact configuration. It uses the same organization, project, policy and 1800 second timeout as the App. It saves the signed files in signed/ssms.
  4. Replace unsigned SSMS files with signed copies both signed files over the files in releases/. It runs only when step 3 has the outcome success.
  5. Warn that SSMS files are unsigned writes a ::warning:: annotation. It runs when step 3 has any other outcome.

The upload to the GitHub release, the upload to the SSMS Gallery and the checksums all read the files in releases/. So they use the signed files.

SHA256SUMS.txt now also lists PlanViewer.Ssms.vsix and InstallSsmsExtension.exe when they were built. The line format did not change. The zips come first, and the two SSMS files follow them.

When signing fails

Steps 1 to 3 have continue-on-error: true. A failed signing request does not stop the release. An approval that times out does not stop it either. The release then ships the unsigned SSMS files, as it did before this PR. Step 5 writes the warning that the SSMS files shipped unsigned.

Step 4 copies both files or neither. If SignPath reports success but a signed file is missing, step 4 writes a warning and keeps both unsigned files. Commit 3 covers a copy that fails partway.

I also set continue-on-error: true on steps 1 and 2. The workflow already says that a VSIX problem must never block the cross-platform release. Without it, a failed artifact upload stops the job before it creates the release.

Commit 3: fail-safe steps and the installer's search

  • Replace unsigned SSMS files with signed no longer stops the release when a copy fails. The step then copies both unsigned files back from ssms-unsigned/ and writes a warning, and the release ships both unsigned files. The step fails only if that copy back fails too. The step has no continue-on-error, so the job then stops before Create release and nothing is published. A pair of one signed file and one unsigned file is never published.
  • Upload SSMS extension to release now has continue-on-error: true, like the SSMS Gallery step after it. The release already exists when this step runs. Before this change, a failed SSMS upload stopped the job, and the release had no App files.
  • InstallSsmsExtension.exe looked for PlanViewer.Ssms.vsix in three kinds of places: the path in its argument, its own folder, and build output folders near it. Release builds now look only in the first two. Debug builds still search the build folders.

Two approvals per release

SignPath requires a manual approval for each request (FOSS plan). A release run now waits for two approvals. The App request comes first. The SSMS request comes second. Each request waits up to 30 minutes. If nobody approves the SSMS request in time, the release continues with the unsigned SSMS files and the warning.

Location of the DLL in the VSIX

The Vsix artifact configuration in SignPath expects PlanViewer.Ssms.dll at the root of the VSIX. It is there. The project file sets IncludeAssemblyInVSIXContainer to true and sets no VSIXSubPath. The v1.27.0 release VSIX lists the DLL at its root, next to extension.vsixmanifest and PlanViewer.Ssms.pkgdef. That DLL is the only PE file in the VSIX. No change is needed on the SignPath side.

How was this tested?

I cannot run the release, so I checked the change in these ways.

  • Python yaml parses every workflow and dependabot.yml.
  • actionlint 1.7.12 reports no findings for .github/workflows. It reported none before my change either.
  • I re-read the release.yml diff by hand. Every if: and every steps.<id> reference points to an earlier step. The output names are artifact-id from the upload step and BUILT from the SSMS build step.
  • A script compared the new SignPath step with the App step. The two steps differ only in the artifact configuration, the artifact ID and the output folder.
  • I ran the new PowerShell blocks in PowerShell 7 with test files, in the same way the runner does, with $ErrorActionPreference = 'stop'. The stage block copies exactly two files. The replace block copies both signed files, and it copies nothing when one is missing. The checksum block writes six lines when the SSMS files were built and four when they were not. Each hash matched its file.
  • For commit 3, I ran the replace block in four cases. They were a normal copy, a missing signed file, a second copy that fails, and a copy back that fails. The first three ended with exit code 0. Both files were signed after the first case and unsigned after the other two. The fourth ended with exit code 1, which stops the job, and both files in releases/ were unsigned.
  • PlanViewer.sln does not include the installer, so pull-request CI does not build it. I built it in Release and in Debug, with 0 warnings each time. The Release exe no longer contains the build-folder paths, and the Debug exe still does.
  • I did not run the test suite. No test covers the installer, the workflow files or dependabot.yml.

Not done

  • No SignPath request ran, and no release ran. The first real release tests the Vsix configuration, the output folder signed/ssms and the approval wait. If any of them fail, the release still ships the unsigned files with the warning.
  • I did not test whether the SSMS Gallery accepts a signed VSIX.
  • nightly.yml does not build the SSMS files, so it has no signing change.
  • After this PR merges to dev, the review bot skips every pull request to dev until dev is merged to main. The bot runs only when its workflow file matches the copy on main, and this PR changes that file. A skipped review still shows as a passed check. Each merged Dependabot update to claude-code-action has the same effect.
  • The Dependabot entry keeps the commit prefix ci. If you want the prefix to match the nuget entry, change it to deps.

🤖 Generated with Claude Code

https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza

erikdarlingdata and others added 2 commits September 28, 2026 18:49
Every `uses:` line in .github/workflows now names a 40-character commit
SHA, with the exact version tag in a trailing comment. Each pin is the
commit that the major tag pointed at, so no action changes version:
checkout v7.0.1, setup-dotnet v6.0.0, upload-artifact v7.0.1,
deploy-pages v5.0.1, upload-pages-artifact v5.0.0, setup-msbuild v3.0.0,
signpath submit-signing-request v3.0, paths-filter v4.0.3 and
claude-code-action v1.0.236.

.github/dependabot.yml already has a github-actions entry (directory /,
target-branch dev, weekly on Monday), so Dependabot keeps proposing
updates to the pins and rewrites the version comments. No change there.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
release.yml built PlanViewer.Ssms.vsix and InstallSsmsExtension.exe but
uploaded them unsigned. They now go through SignPath, using the "Vsix"
artifact configuration in the PerformanceStudio project.

New steps, after the App is signed and before the release is created:
- Stage the two files at the root of ssms-unsigned/ and upload that
  folder as the Ssms-unsigned artifact (if-no-files-found: error).
- Submit it to SignPath: same organization, project, policy and 1800 s
  timeout as the App, artifact-configuration-slug Vsix, output in
  signed/ssms.
- If signing succeeded, copy both signed files over the ones in
  releases/. The release upload, the SSMS Gallery upload and the
  checksums all read from releases/, so they use the signed files.
- If it did not, write a ::warning:: annotation and ship the unsigned
  files, which is what the release did before.

SSMS problems must never block the cross-platform release, so the
staging, upload and signing steps are all continue-on-error. The copy
step is gated on the signing step's outcome, not its conclusion, and
copies both files or neither.

SHA256SUMS.txt now also lists PlanViewer.Ssms.vsix and
InstallSsmsExtension.exe when they were built. The line format is
unchanged.

Each SignPath request needs a manual approval, so a release run now
waits for two approvals: the App first, then the SSMS files.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 28, 2026 22:54
- Replace step: if copying a signed file fails, put the unsigned pair
  back from ssms-unsigned/ and warn, instead of stopping the release.
  The job stops only if that restore fails, before anything is published.
- Upload SSMS extension to release: continue-on-error, so a failed SSMS
  upload no longer stops the App files from reaching a release that
  already exists.
- Installer: Release builds look for the VSIX only in the argument or
  next to the exe. The build-folder search is now Debug-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
@erikdarlingdata
erikdarlingdata merged commit 8e3147e into dev Sep 28, 2026
4 checks passed
@erikdarlingdata
erikdarlingdata deleted the ci/pin-actions-sign-vsix branch September 28, 2026 23:28
@claude

claude Bot commented Sep 29, 2026

Copy link
Copy Markdown

Reviewed the workflow and installer changes. I found nothing that needs fixing before merge.

  • SHA pins: these match the tags in the PR table, and each pin keeps its version comment, so Dependabot can keep them updated.
  • Failure handling: the release still ships unsigned files if any signing step fails. The replace step copies both files or neither, and it fails the job only if the fallback copy fails too. That means a mixed signed/unsigned pair is never published.
  • Installer: the #if DEBUG guard removes the build-folder VSIX search from Release builds, which is the intended narrowing. The installer isn't in PlanViewer.sln, so CI won't catch a regression there. The PR says it was built manually in both configurations.
  • Version bump: none needed. No Directory.Build.props or SSMS version files are touched.
  • Tests: none exist for workflows or the installer, which the PR acknowledges. The first real release will exercise the Vsix SignPath configuration and the signed/ssms output path.

One thing to be aware of: this now needs two manual SignPath approvals per release. The SSMS one falls back to unsigned after a 30-minute timeout.

@erikdarlingdata erikdarlingdata mentioned this pull request Sep 29, 2026
2 of 8 tasks
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