Repository navigation
fix(proxy): remove obsolete Phase AI CA roots on Windows with certutil - #324
Merged
Merged
Conversation
phase ai enable failed on Windows whenever an older Phase AI CA was still
trusted in the current-user Root store:
inspect and rotate Phase AI CA in Windows current-user Root store:
exit status 1: Remove-Item : The operation is on user root store and
UI is not allowed.
The rotation script deleted obsolete roots with Remove-Item on the Cert:
drive, but trustWindows runs PowerShell with -NonInteractive, and Windows
PowerShell's certificate provider refuses every removal from
CurrentUser\Root when -NonInteractive is on its command line.
The PowerShell step is now read-only: it prints PHASE_CA_OBSOLETE with
the SHA-1 thumbprint of each older Phase-owned root, and trustWindows
removes each one with certutil -user -delstore before adding the new CA
with -addstore, as before. Thumbprints must be 40 hex characters, since
-delstore also matches store indexes and common names. The Windows trust
notice now mentions the confirmation shown for each removed root.
A background job accepts the Root store confirmations certutil shows, since nobody is at the runner's desktop. Remove once it has passed.
Deploying phase-cli-install-script with
|
| Latest commit: |
5efa78f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://57dbbedd.phase-cli-install-script.pages.dev |
| Branch Preview URL: | https://fix--windows-ca-rotation.phase-cli-install-script.pages.dev |
… step Launched from PowerShell 7, Windows PowerShell inherits that shell's PSModulePath, cannot auto-load the certificate provider, and the trust step fails because the Cert: drive does not exist.
It proved the delete-and-add rotation on a real user Root store once, but the confirmation behaviour on hosted Windows runners is not deterministic, so it cannot stay as a gate.
nimish-ks
approved these changes
Oct 26, 2026
nimish-ks
left a comment
Member
There was a problem hiding this comment.
Verified: unit tests and vets on both platforms, and a temporary Windows runner job proved the delete-and-add rotation against a real current-user Root store (initial trust, forced CA replacement leaving only the new root, then already-trusted). Also fixed on this branch: Windows PowerShell now gets its own module path, so the trust step works when enable is launched from PowerShell 7.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Windows,
phase ai enablefails at the trust step whenever an older Phase AI CA is still trusted in the current-user Root store. That happens every time the CA is replaced after it was trusted: renewal inside the 14-day window, a lost private key, or, as hit in testing, a laterphase ai enablethat generated a new CA.trustWindowsruns the rotation script aspowershell.exe -NoLogo -NoProfile -NonInteractive, and the script deleted obsolete roots withRemove-Itemon theCert:drive. Windows PowerShell's certificate provider only deletes fromCurrentUser\Rootwhen it can show the confirmation dialog, and it decides it can't whenever-NonInteractiveis on the process command line (IsUIAllowedin CertificateProvider.cs). So the removal fails on every Windows machine, whatever the session type.Hit on Windows 11 with this PR's CI build (2.3.4-next). With the obsolete root still in the store,
Remove-Item -WhatIfon it fails with the same error under-NonInteractiveand succeeds without it.CI doesn't reach this path: the Windows smoke test runs with
--trust-ca=falsebecause the consent dialog would block a headless runner, and the recorded-runner unit test only checks the script text. Linux and macOS remove obsolete roots withcertutil -Dandsecurity delete-certificate, which aren't affected.Fix
PHASE_CA_CURRENTfor the already-trusted fast path, and otherwise printsPHASE_CA_OBSOLETE <SHA-1 thumbprint>for each older Phase-owned root, using the same ownership filter as before.trustWindowsremoves each obsolete root withcertutil -user -delstore Root <thumbprint>, then adds the new CA withcertutil -user -addstore -f Rootas before. certutil shows Windows' confirmation dialog for each removal;-NonInteractiveonly affects PowerShell.-delstorealso matches store indexes and common names (0is the first root in the store), so anything else is rejected before anything is deleted.Testing
go vet ./...andgo test ./pkg/proxy/pass locally on Windows (Go 1.27.3).-addstore; a malformed thumbprint is rejected with nothing deleted; a failed removal stops before the new CA is added. The existing Windows test now also asserts that the inspection script contains noRemove-Item.phase ai enable --trust-cashould show one removal confirmation and one add confirmation, then report the CA as trusted.