Skip to content

Signed-copy drift guard (check 2 of 2)#34

Open
crutkas wants to merge 7 commits into
mainfrom
signed-copy-drift-guard
Open

Signed-copy drift guard (check 2 of 2)#34
crutkas wants to merge 7 commits into
mainfrom
signed-copy-drift-guard

Conversation

@crutkas
Copy link
Copy Markdown
Member

@crutkas crutkas commented May 28, 2026

What this PR adds

  • src/tools/check-signed-drift.ps1 — a shared PowerShell comparator that walks every file under src/{Workloads,windows-dev-config,wsl-comfort}/ and the matching three top-level signed-copy roots, pairs each one with its counterpart, and emits a JSON drift report. .ps1 files are compared after stripping the UTF-8 BOM, normalizing CRLF→LF, and dropping everything from the first # SIG # Begin signature block line to EOF on the signed side; every other file (.winget / .sh / .md / images / any extension) is compared strict byte-equal, because the sign pipeline only modifies .ps1 bodies. The script is a pure reporter — it always exits 0 and lets callers decide pass/fail.
  • .github/workflows/signed-copy-guard.yml — Check 2 of 2. Triggered by pull_request with a paths: filter on the three top-level signed-copy roots, so PRs that touch only src/ skip the check entirely. The job intersects the comparator's drift list with the PR's changed paths (under those roots) and fails with an actionable GITHUB_STEP_SUMMARY message when any intersected entry has status != "ok". Drift report is uploaded as a build artifact for debugging.
  • src/docs/development.md — new ### Signed-copy drift guard subsection under "Repo layout: signed vs source" explaining what the guard does, where the shared comparator lives, and that the guard does not replace the sign pipeline.

What this PR does NOT add

  • Check 1 (the non-blocking "Drift status" visibility workflow) is intentionally left for a follow-up PR. That PR will reuse the same src/tools/check-signed-drift.ps1 comparator on a push/schedule trigger and report drift across the whole tree without failing any build. The comparator's contract (always exit 0, full JSON, deterministic ordering) is designed for that reuse.

After merge: maintainer action

Please add Signed copy guard to the required status checks on main in branch protection. Otherwise PRs can bypass the guard by not requesting that check.

How I verified

Locally on a clean checkout of this branch:

  1. Comparator parses and exits 0: pwsh -NoProfile -Command "[void][System.Management.Automation.Language.Parser]::ParseFile('./src/tools/check-signed-drift.ps1', ...)" — passes.
  2. Workflow YAML parses: loaded via powershell-yaml ConvertFrom-Yaml — the on / permissions / concurrency / jobs.guard.steps shape is correct.
  3. Zero-drift on clean main: running the comparator against the current branch reports { ok: 30, drifted: 0, missing_in_root: 0, missing_in_src: 0 }, which matches the dev guide's expectation that the two trees only differ on the .ps1 sig block today.
  4. Strict byte-equal failure path: corrupted one byte (offset 10) of windows-dev-config/dev-config.winget and re-ran. Comparator reported status: drifted, reason: "bytes differ at offset 10 (src len=39522, root len=39522)". Reverted.
  5. .ps1 sig-strip failure path: prepended a junk X byte to windows-dev-config/install.ps1 (the signed copy) and re-ran. Comparator reported status: drifted, reason: "normalized .ps1 bytes differ at offset 0 (src len=923, root len=924)" — proving the sig block is being stripped (without it the diff would be tens of KB, not 1 byte) and the unsigned body is then compared byte-for-byte. Reverted.
  6. End-to-end workflow eval block: simulated the step that intersects the comparator's report with the PR's changed paths, against both a clean path (windows-dev-config/install.ps1) and a drifted path (windows-dev-config/dev-config.winget with one byte flipped). Exit 0 on the clean case, exit 1 with the actionable bullet list on the drifted case.

Reference

Drift definition follows the dev guide's "Repo layout: signed vs source" section: https://github.com/microsoft/WindowsDeveloperConfig/blob/main/src/docs/development.md#repo-layout-signed-vs-source

Introduces src/tools/check-signed-drift.ps1 (shared comparator) and
.github/workflows/signed-copy-guard.yml. The workflow runs on PRs
that touch the top-level signed-copy roots and fails if any
PR-touched file no longer matches its src/ counterpart (modulo the
Authenticode signature block on .ps1 files).

A follow-up PR will layer a non-blocking "Drift status" visibility
check on the same comparator (check 1 of 2).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@crutkas crutkas marked this pull request as ready for review May 28, 2026 05:56
@crutkas
Copy link
Copy Markdown
Member Author

crutkas commented May 29, 2026

after messing with a file that shouldn't be touched.

image

@crutkas
Copy link
Copy Markdown
Member Author

crutkas commented May 29, 2026

image amazing!

@JohnMcPMS
Copy link
Copy Markdown
Member

Are there already checks to ensure that the signable things in the signed location are in fact signed in PRs?

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.

2 participants