Skip to content

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

Merged
AmirMS (AmelBawa-msft) merged 7 commits into
mainfrom
signed-copy-drift-guard
Jun 15, 2026
Merged

Signed-copy drift guard (check 2 of 2)#34
AmirMS (AmelBawa-msft) merged 7 commits into
mainfrom
signed-copy-drift-guard

Conversation

@crutkas

Copy link
Copy Markdown
Member

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
Clint Rutkas (crutkas) marked this pull request as ready for review May 28, 2026 05:56
@crutkas

Copy link
Copy Markdown
Member Author

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

image
@crutkas

Copy link
Copy Markdown
Member Author
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?

@AmelBawa-msft
AmirMS (AmelBawa-msft) merged commit 94c04d3 into main Jun 15, 2026
11 checks passed
@AmelBawa-msft
AmirMS (AmelBawa-msft) deleted the signed-copy-drift-guard branch June 15, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants