Skip to content

Release v1.18.0 - #405

Merged
erikdarlingdata merged 12 commits into
mainfrom
dev
Jul 25, 2026
Merged

Release v1.18.0#405
erikdarlingdata merged 12 commits into
mainfrom
dev

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Release v1.18.0. Merging this triggers release.yml: builds and publishes win-x64, linux-x64, osx-x64, osx-arm64, signs Windows via SignPath, packs and uploads Velopack, builds the SSMS VSIX and pushes it to the SSMS Gallery.

This is the release that turns on everything built today

Each of these only takes effect from the default branch, so they have been inert on dev until now:

Change What it starts doing
Dependabot (#399) Weekly update PRs across all 8 project directories - listed explicitly because PlanViewer.sln omits server/PlanShare and both SSMS projects, which is how PlanShare drifted 3.5 months
PlanShare auto-deploy (#399) Deploys on push to main touching server/PlanShare/**; backs up binary, native lib and DB, restarts, verifies through nginx, rolls back on failure. Runs as an unprivileged deploy user, not root
SignPath policy + timeout (#400) Requires GitHub-hosted runners; signing window raised 600s -> 1800s
PR reviewer fix (#401) The reviewer had been denied Read/Grep/Glob, so it spent its turns on permission denials and posted nothing

Security fixes

Consent before executing a plan file's query (#402). "Get Actual Plan" on a loaded plan prompted for a connection and then ran the query. The query-session path already confirmed first; this one did not - and it is the riskier of the two, since the SQL comes from a .sqlplan the user opened rather than something they typed. The dialog names the target server and database. Extracted into a shared ConfirmationDialog rather than copied, because two independent copies is exactly how one path ended up with no confirmation at all.

SSMS launcher hardening (#403). PATH was searched before the known install locations, so any writable directory on PATH containing a PlanViewer.App.exe would be launched instead of the installed app. And every analysis left a %TEMP%\ssms_plan_*.sqlplan containing query text and literal parameter values, permanently.

Not included

#388 (Repl pilot) remains open and deliberately excluded - it is still a draft, and its author describes it as an unsolicited proposal for which "declining or closing it is a completely valid outcome".

Verification

  • Clean Release build: 0 warnings
  • 202/202 tests pass
  • All four version sources at 1.18.0; no stale 1.17.0
  • vsixmanifest matches, so the VSIX version-mismatch warning will not fire
  • SSMS VSIX built locally with MSBuild - CI never compiles that project, and release.yml builds it under continue-on-error, so this is its only pre-release signal
  • main at 1.17.0, so check-version-bump passes and release.yml will create a new release rather than clobbering v1.17.0

Signing will pause and needs you

SignPath offers manual approval only. The job will sit at "Sign Windows build" waiting. If it is not approved, the run fails after the release is already published, leaving it public with only the VSIX attached - exactly what happened to v1.17.0. The window is now 30 minutes rather than 10.

🤖 Generated with Claude Code

erikdarlingdata and others added 12 commits July 25, 2026 11:49
Dependabot (.github/dependabot.yml):
Covers all 8 project directories explicitly rather than the solution,
because PlanViewer.sln does not contain server/PlanShare,
PlanViewer.Ssms, or PlanViewer.Ssms.Installer - a solution-level scan
silently skips them, which is how PlanShare drifted. Also covers
github-actions. Minor/patch are grouped into one PR per ecosystem;
majors stay separate so a breaking change is never buried in a batch.

Ignore rules encode the deliberate pins, without which Dependabot would
file PRs that break the build or ship a broken runtime:
- Avalonia* major: 12 is a parked migration (upgrade/avalonia-12)
- ScottPlot.Avalonia >= 5.1.59: requires Avalonia 12 and NU1605s. This
  is a PATCH bump, so an update-type ignore would not catch it.
- SkiaSharp.NativeAssets.* major: the pin exists to keep native libs in
  the range managed SkiaSharp 3.x needs (GitHub issue #139)
- Microsoft.VisualStudio.SDK / VSSDK.BuildTools major: 18.x targets
  VS 18 and is not restorable from nuget.org
- Velopack: must move in lockstep with the vpk CLI pin in release.yml

Repository-side Dependabot alerts and automated security fixes were
disabled; both are now enabled.

Deployment (.github/workflows/deploy-planshare.yml):
PlanShare had no deploy path at all - server/** is paths-ignored by CI
and it is absent from the solution, so a merged PR shipped nothing.
Production had drifted ~3.5 months behind main, missing the XSS fix
(#249) and the change from Random.Shared to RandomNumberGenerator for
share IDs, where the 8-char id is the only access control on a shared
plan.

Triggers on push to main touching server/PlanShare/**, plus manual
dispatch. Backs up binary, native lib, and plans.db before swapping,
restarts, verifies through nginx, and rolls back automatically if
verification fails. Keeps the 3 most recent backup sets.

Security posture: authenticates as a dedicated unprivileged `deploy`
user, not root, with a CI-only keypair (not a personal key). Its sudo
rights are limited to restarting and querying this one unit; verified
that stopping nginx and reading /etc/shadow are refused. The host key
is pinned rather than accepted on first use.

Verified end-to-end using the restricted key: upload, backup, swap,
restart, and nginx verification all succeed, service active, existing
shared plan still returns 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Policy (.signpath/policies/PerformanceStudio/release-signing.yml):
Path must match the project-slug and signing-policy-slug passed to the
SignPath action in release.yml. Only takes effect from the default
branch (main), so it is inert until the next dev -> main merge.

It requires GitHub-hosted runners, so a self-hosted runner - which an
attacker could register, or which could carry dirty state between jobs
- can never produce a signed build.

Note this file does NOT enable automatic approval; approval mode is a
setting on the signing policy in the SignPath dashboard. This file adds
constraints SignPath enforces before it will sign.

Two fields are deliberately omitted, documented inline, because adding
them without the matching repo change would break signing outright:
- build.disallow_reruns would have blocked the v1.17.0 recovery, since
  re-running is the current remedy for a signing failure that already
  published an empty release.
- branch_rulesets requires the protections it names to actually exist,
  and main currently has no branch protection at all, so any ruleset
  rule would reject every signing request.

Timeout (release.yml): the SignPath action defaults to 600s, so a
policy requiring manual approval must be approved inside 10 minutes or
the job fails after the release is already published - which is exactly
how v1.17.0 ended up public with only the VSIX attached. Raised to 1800s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First live run of the review workflow failed on PR #399:

  permission_denials_count: 10
  ##[error]Execution failed: Reached maximum number of turns (20)

--allowedTools REPLACES the default tool set rather than adding to it,
so the resolved list was exactly the four entries I passed - no Read,
Grep, or Glob. The prompt tells the reviewer "the PR branch is already
checked out in the working directory", then denied it every tool needed
to open a file. It spent its turns on denied calls and hit the cap
without posting a review.

PR #400 passed only because its diff is two small files, so it fit.

Adds Read/Grep/Glob plus read-only git diff/log, and raises the cap to
40 turns since a real review needs to read several files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add SignPath GitHub policy and raise the signing timeout
…lanshare-deploy

Add Dependabot config and automated PlanShare deployment
Fix PR reviewer: it was denied the tools needed to read code
"Get Actual Plan" on a loaded plan file prompted for a connection and
then ran the query. The query-session path already confirmed first; this
one did not, and it is the riskier of the two - the SQL comes out of a
.sqlplan the user opened rather than something they typed, and plan
files get emailed around.

The dialog names the target server and database, so "which server was
that?" is answered before the query runs rather than after.

Extracts the dialog into Dialogs/ConfirmationDialog.cs instead of
copying it. The two paths having separate implementations is how one of
them ended up with no confirmation at all; DdlScripter has the same
history of a duplicated helper drifting. QuerySessionControl now
delegates to the shared version, so its behavior is unchanged.

Verified: clean build 0 warnings, 202/202 tests, app launches and
paints. AppButton resolves from Application.Resources, so looking it up
from the owner window works the same as from the control.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both from the quarterly maintenance security review.

Search order: FindApp checked PATH before the known install locations,
so any writable directory on PATH holding a PlanViewer.App.exe would be
launched instead of the installed app. PATH is now the last resort,
behind the installer-written registry value and the real install paths.

Temp files: every analysis wrote %TEMP%\ssms_plan_<random>.sqlplan and
never removed it, so query text and literal parameter values from every
plan ever opened accumulated at rest. Files are swept on the way in
rather than deleted after launch, because the file is handed to another
process to open and deleting it immediately would race that read;
anything older than an hour has certainly been read. Cleanup is
best-effort throughout - a file that cannot be deleted must never break
opening a plan.

Verified by building the VSIX locally with MSBuild (Build Tools 2022):
produces PlanViewer.Ssms.vsix with no errors or warnings. Worth doing
by hand since ci.yml paths-ignores this project and release.yml only
builds it under continue-on-error, so a break here would surface as a
release silently missing the VSIX.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Confirm before executing a plan file's query against a server
Harden SSMS launcher: PATH search order and temp file cleanup
All four version sources, kept in sync:
- src/Directory.Build.props (single source for SDK-style projects)
- src/PlanViewer.Ssms/source.extension.vsixmanifest
- src/PlanViewer.Ssms/Properties/AssemblyInfo.cs
- CITATION.cff

The two SSMS files are not covered by Directory.Build.props because
PlanViewer.Ssms is a legacy non-SDK project, so they are bumped by hand.
date-released in CITATION.cff is already today.

Verified: clean Release build 0 warnings, 202/202 tests, no stale
1.17.0 strings remaining.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@erikdarlingdata
erikdarlingdata merged commit 196c8d9 into main Jul 25, 2026
4 checks passed
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