Skip to content

feat(ci): conditional SignPath signing step for the Windows binary#42

Draft
omarsy wants to merge 1 commit into
mainfrom
feat/conditional-signpath-signing-step
Draft

feat(ci): conditional SignPath signing step for the Windows binary#42
omarsy wants to merge 1 commit into
mainfrom
feat/conditional-signpath-signing-step

Conversation

@omarsy

@omarsy omarsy commented May 22, 2026

Copy link
Copy Markdown
Member

Summary

Implements workstream 2 of the `greffer-windows-support` epic. Adds three new CI steps between the rename and upload of the Windows binary:

  1. Pre-signing upload — short-lived input artifact for SignPath to download
  2. Submit signing request to SignPath — `signpath/github-action-submit-signing-request@v1.2`
  3. Verify signed binary — `Get-AuthenticodeSignature` check that fails loudly on missing-or-invalid signature

All three are gated on `env.SIGNPATH_API_TOKEN != ''`. The workflow no-ops cleanly today; the moment SignPath secrets are added to repo settings, the next `cli-v*` tag produces a signed binary automatically.

Why now

The Windows binary has been publishing unsigned since PR #39 (workstream 1) landed — `iwr | iex` operators hit Defender SmartScreen on first run with no remediation short of the right-click → Properties → Unblock dance. Workstream 3 (`install.ps1` + landing-page route) is also already live, so today every Windows operator who follows the documented install path hits the warning. This PR closes that gap without requiring the SignPath account to exist yet.

Operator-side steps (your-move, not engineering)

Before this can do anything, the following one-time setup is needed:

  1. Register the `greffer-cli` project on SignPath OSS community plan (free for AGPL)
  2. Configure the `release-signing` policy and the `greffer-exe` artifact configuration referenced in the step's `with:` block
  3. Add `SIGNPATH_API_TOKEN` + `SIGNPATH_ORG_ID` to repo secrets (Settings → Secrets and variables → Actions)
  4. Cut the next `cli-v*` tag → produces the first signed binary

Notes

  • The install.ps1 banner on landing-page already mentions a SmartScreen "warm-up period" — that text is currently misleading because there's no signature at all yet. Once signing is live, it becomes accurate. Operator-facing copy fix is a separate small PR on landing-page.

Test plan

  • YAML lints clean
  • Existing PR-validation runs (no `cli-v*` tag = no publish step) still pass because every new step is conditional on Windows + SignPath secret
  • First `cli-v*` tag AFTER SignPath secrets are added produces a binary that passes `Get-AuthenticodeSignature` with Status=Valid

🤖 Generated with Claude Code

Implements workstream 2 of the greffer-windows-support epic
(``docs/features/greffer-windows-support/hld.md``). The Windows
binary has been publishing unsigned since workstream 1 landed
(PR #39) — operators downloading via ``iwr | iex`` hit Defender
SmartScreen on first run, with no path to fix it short of the
right-click → Properties → Unblock dance.

This adds three steps between the existing "Rename binary with
target suffix" and "Upload binary artifact (Windows)" steps:

1. **Pre-signing upload** — uploads the unsigned ``.exe`` as a
   short-lived input artifact (``greffer-${target}-unsigned-input``,
   1-day retention) so SignPath has an artifact-id to download.

2. **Submit signing request to SignPath** —
   ``signpath/github-action-submit-signing-request@v1.2``. Signs
   under the chosen policy, writes the signed binary back to
   ``cli/dist/``. The HLD picks SignPath because it's free for
   AGPL/OSS projects, API-driven (no HSM management), and issues
   an OV cert under the Greffon org name.

3. **Verify signed binary** — ``Get-AuthenticodeSignature`` check
   that fails the job loudly if the signed file is missing or has
   a non-Valid signature status. Without this, a silent SignPath
   failure would publish an unsigned binary indistinguishably.

All three steps are gated on ``env.SIGNPATH_API_TOKEN != ''``.
Reasoning:

- During the gap between this landing and the SignPath account
  being live, every step skips cleanly. The pre-existing unsigned-
  binary upload still runs, so behaviour is unchanged from today.
- The moment ``SIGNPATH_API_TOKEN`` and ``SIGNPATH_ORG_ID`` are
  added to repo secrets, the next ``cli-v*`` tag automatically
  produces a signed binary. No code change needed at go-live.

GitHub Actions disallows ``secrets.X`` in ``if:`` expressions for
security; the env-indirection ``env: SIGNPATH_API_TOKEN: ${{
secrets.SIGNPATH_API_TOKEN }}`` then ``if: env.SIGNPATH_API_TOKEN
!= ''`` is the workaround.

Next steps (operator-side, not engineering):

1. Register the ``greffer-cli`` project on SignPath OSS community
   plan (free for AGPL).
2. Configure the ``release-signing`` policy and the
   ``greffer-exe`` artifact configuration as referenced in the
   step's ``with:`` block.
3. Add ``SIGNPATH_API_TOKEN`` + ``SIGNPATH_ORG_ID`` to repo
   secrets (Settings → Secrets and variables → Actions).
4. Cut the next ``cli-v*`` tag.

The install.ps1 banner already mentions a SmartScreen "warm-up
period" — that text is currently misleading because there is no
signing at all yet. After SignPath is live and the first signed
binary ships, the banner becomes accurate. Operator-facing copy
fix is a separate small PR on landing-page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4cd0cf4fe2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

env:
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
run: |
$exe = "cli/dist/greffer-${{ matrix.target }}.exe"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fix signed-binary path in PowerShell verification step

The build job sets defaults.run.working-directory: cli, so this run step executes from .../cli, but it checks for cli/dist/..., which resolves to .../cli/cli/dist/.... When SignPath is enabled, the file lookup will fail even if signing succeeded, causing the Windows build to error and blocking release publication.

Useful? React with 👍 / 👎.

SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
uses: actions/upload-artifact@v4
with:
name: greffer-${{ matrix.target }}-unsigned-input

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exclude unsigned input artifact from release download pattern

This artifact name now matches the publish job’s pattern: greffer-*, so the pre-sign unsigned artifact is downloaded alongside the final Windows artifact. Because actions/download-artifact@v4 is used with merge-multiple: true, identical filenames collide and one wins last-write, which can let the unsigned greffer-windows-x86_64.exe overwrite the signed one before checksums and release upload.

Useful? React with 👍 / 👎.

@omarsy

omarsy commented May 23, 2026

Copy link
Copy Markdown
Member Author

Deferred per product framework (greffon-product 6-question test, Q6 fail)

Postponing this PR per a formal pass of the greffon-product skill's 6-question framework. 5/6 pass; Q6 ("highest-value right now") fails: no measured complaint from any Windows operator + bigger M2 levers (MVP backup, step-ca) sit ahead.

The code is reviewed and ready. Converting to draft so it doesn't sit in the merge queue, but holds its place for the moment a revival trigger fires.

Revival triggers (any one):

  1. A real Windows operator reports SmartScreen pain in the install funnel
  2. A homepage launch starts marketing Windows install as a primary path (right now landing-page serves install.ps1 but no marketing surface advertises it)
  3. A specific deal requires it (likely still no per roadmap-ransom rule, but the trigger is honest)

Companion banner-honesty fix at greffon/landing-page#14 ships independently — it's required by the marketing-honesty framework regardless of when signing lands.

Decision logged in greffon/greffon docs/product/decision-log.md (separate PR).

@omarsy omarsy marked this pull request as draft May 23, 2026 11:18
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