Skip to content

Add SignPath GitHub policy and raise the signing timeout - #400

Merged
erikdarlingdata merged 1 commit into
devfrom
feature/signpath-policy
Jul 25, 2026
Merged

Add SignPath GitHub policy and raise the signing timeout#400
erikdarlingdata merged 1 commit into
devfrom
feature/signpath-policy

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Adds the SignPath GitHub policy file and fixes the timeout that broke the v1.17.0 release.

Correction to something I said earlier

The policy file does not enable automatic approval. Having read the actual schema, it does close to the opposite kind of job: it lets SignPath impose constraints on a build before it is willing to sign it. Approval mode is a separate setting on the signing policy in the SignPath dashboard. Two different mechanisms.

What the file does

.signpath/policies/PerformanceStudio/release-signing.yml - the path is significant and must match the project-slug and signing-policy-slug passed to the action in release.yml.

github-policies:
  runners:
    require_github_hosted: true

That means a self-hosted runner - one an attacker registered, or one carrying dirty state between jobs - can never produce a signed build. Modest, but real, and it is true of release.yml today (windows-latest).

Like the Claude review workflows, this only takes effect from the default branch, so it is inert until the next dev -> main merge.

Two fields deliberately left out

Both are documented inline in the file, because adding either without the matching repository change would break signing outright:

  • build.disallow_reruns: true blocks signing any build from a workflow re-run. A re-run is currently the documented recovery path for this repo - gh release create runs before signing, so a signing failure leaves a published-but-empty release that gets fixed by re-running (all uploads use --clobber). v1.17.0 was recovered exactly that way, minutes ago. Enabling this would have made that recovery impossible.
  • branch_rulesets requires the protections it names to actually exist on the branch. main currently has no branch protection configured at all, so any ruleset rule here would reject every signing request. Worth adding later, but branch protection has to come first.

Timeout fix

The SignPath action defaults to wait-for-completion-timeout-in-seconds: 600. With a policy that requires manual approval, that means the approval must be caught inside a 10-minute window or the job dies after the release is already public - which is precisely how v1.17.0 ended up published with only the VSIX attached. Raised to 1800s. Irrelevant once approval is automatic, harmless either way.

On moving gh release create after signing

I suggested this earlier and you pushed back that there were ordering dependencies. You were right, and I was too glib. gh release upload requires the release to already exist, and the SSMS extension uploads at step 11, well before signing at step 15. So creation cannot simply move to the end without also moving that upload.

There are two real options, neither included here:

  1. Create the release as a draft, publish at the very end. Uploads work against drafts, so ordering is preserved. The catch: GitHub does not create the git tag until a draft is published, and vpk upload github looks the release up by tag - so Velopack may not find it. Needs testing before trusting.
  2. Move both the release creation and the SSMS upload after signing. No draft weirdness, but a larger reordering of a workflow that only executes on a real release, making it awkward to test.

Given release.yml cannot be exercised outside an actual release, I would rather do that deliberately than fold it into this PR.

Test plan

  • Policy file parses; resolves to {'github-policies': {'runners': {'require_github_hosted': True}}}
  • release.yml still parses; signing timeout reads 1800
  • Cannot be verified until it is on main and a release runs - policy files are read from the default branch only

🤖 Generated with Claude Code

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>
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review

Scope check first: this PR only touches .github/workflows/release.yml (one new input) and adds a new .signpath/policies/PerformanceStudio/release-signing.yml. Nothing here touches plan-XML parsing, T-SQL generation, or the PlanViewer.Core/Web/Ssms surfaces, so the usual correctness/untrusted-input/version-sync checks for this repo don't apply — this is a CI/CD-only change.

What it does:

  • Raises wait-for-completion-timeout-in-seconds on the SignPath signing step from the action's 600s default to 1800s, so a manual-approval window won't cause the job to die after gh release create has already published a release (the v1.17.0 failure mode described in the PR body).
  • Adds a SignPath GitHub policy file that requires GitHub-hosted runners for a build to be eligible for signing.

Verified:

  • project-slug: PerformanceStudio / signing-policy-slug: release-signing in release.yml:156-157 match the new file's path .signpath/policies/PerformanceStudio/release-signing.yml, which is what SignPath uses to resolve the policy.
  • The 1800s comment's math checks out, and the reasoning for it (avoid killing a job mid-release after gh release create already ran) is consistent with the existing recovery-via-rerun pattern already in release.yml (--clobber uploads at line 115).
  • The two intentionally-omitted fields (build.disallow_reruns, branch_rulesets) are correctly reasoned: enabling either today would break the documented re-run recovery path or reject every signing request outright since main has no branch protection yet. Good instinct to leave them out rather than add them speculatively.

One thing worth flagging, not blocking: the policy file's schema (github-policies.runners.require_github_hosted) can't be validated by CI — a typo'd key would just be silently ignored by SignPath rather than erroring, and the PR's own test plan already notes this can't be confirmed until it runs against main on a real release. Worth a quick double-check against the SignPath schema docs before/after merge, and confirming the next release actually gets signed by a GitHub-hosted runner rather than skipping the check unnoticed.

No correctness, security, or convention issues found otherwise. This is also inert until merged to the default branch, matching the PR's own note, so the blast radius of a mistake here is low.

@erikdarlingdata
erikdarlingdata merged commit 9b3d16f into dev Jul 25, 2026
3 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/signpath-policy branch July 25, 2026 16:12
erikdarlingdata added a commit that referenced this pull request Jul 25, 2026
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>
This was referenced Jul 25, 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