Repository navigation
Fix all findings of the October 2026 audit - #11
Merged
Merged
Conversation
- Record every Windows Terminal change in wt-changes.json and revert it value by value in all installed variants on uninstall, keeping later user edits; keep the pre-install file as settings.json.psp-original; honor windowsTerminal.manage = false in uninstall (F-01, F-05). - Bind .psprc.ps1 trust to the approved file's SHA-256 (F-02). - Add -Ref to Update-Profile and Invoke-ProfileWizard, resolve it to a commit, verify release tags against SHA256SUMS.txt, and install tools only with -InstallMissingTools (F-03, F-08). - Parse JSONC safely, read files as UTF-8 on Windows PowerShell 5.1, keep the encoding in sed, and never overwrite an unparseable user-settings.json (F-06, F-18, F-19). - Guard destructive commands (killport, Stop-ListeningPort, Remove-LockedItem, Clear-Cache, pkill, dstop, dprune, extract, trash) and harden winutil, certcheck, tlscert, env, pwnd, nscan, Copy-SshKey, wifipass and Get-WslDistro (F-09, F-10, F-20, F-22, F-23, F-24, F-26). - Keep state files in Clear-ProfileCache and remove profile backups with -RemoveUserData (F-16, F-17). - Pin the Oh My Posh theme, Nerd Font, PSFzf and http-server (F-11). - Add a version, comment-based help, NO_COLOR support through Write-PspHost, a prompt render timeout, cheaper load-time probes and a kubeconfig cache; keep load-time state in $script:PSP (F-25, F-27, F-30). - Record Windows Terminal values that an earlier version wrote against the pre-install copy, keep single-element arrays as arrays, handle bare profiles lists, keep an action the user bound to more keys, drop the record of a hard-reset file, and keep settings.json.psp-original unless -RemoveUserData. - Run the .psprc.ps1 text that was hashed (with $PSScriptRoot set) and merge trust changes into user-settings.json as it is on disk. - winutil -ExpectedSha256/-Force need an elevated shell; Invoke-ProfileWizard checks a release's setup.ps1 against SHA256SUMS.txt and passes the tag on; trash refuses sessions where Windows would delete permanently; an unreadable user-settings.json leaves settings.json alone; a copy of this profile is never restored as profile.original.ps1. - Name load-time temporaries _psp* and remove only those after load.
- Resolve -Ref to a commit, download the bundle from it and verify it (release SHA256SUMS.txt, -ExpectedSha256 or explicit -SkipHashCheck) before any change; load shared helpers from the verified profile (F-03, F-27). - Run without elevation: per-user font and telemetry scope, machine-wide only when elevated, no elevated restart (F-09). - Strict mode, Stop on errors, backups that stop the write on failure, atomic writes, a transcript log, -WhatIf, exit codes and Get-Help text (F-14). - Keep unparseable user-settings.json, re-validate resumed wizard state, quote the editor name, ask before removing Terminal-Icons (F-06, F-07, F-21). - Pin fonts and themes with SHA-256 and PSFzf by version (F-11). - setprofile.ps1: help, -WhatIf, profile.original.ps1, exit codes. - Fix the wizard quick start, which never selected its font. - Never keep an installed copy of the profile as profile.original.ps1 (setup and setprofile.ps1); leave Windows Terminal alone when user-settings.json does not parse; no restart offer after a failed install; the quick start uses an installed editor; wizard replies match names literally; a script-block run removes its variables from the caller's session; a local install prints its bundle hash.
- tests/lib/Sandbox.ps1: redirect LOCALAPPDATA, TEMP and $PROFILE, block machine-changing commands, keep font removal in the sandbox, refuse elevated runs without -AllowElevated (F-04). - tests/unit.ps1: regression tests for the audit fixes on Windows PowerShell 5.1 and PowerShell 7 (F-12). - tests/test.ps1: run every step in a sandbox, test the shipped functions, soft-fail only on transport exceptions, require an executed share. - tests/ci-functional.ps1: mock third-party endpoints (-LiveNetwork for the scheduled run), never upload to the public paste, require 60% executed. - tests/rawhunt.ps1 needs -AllowSideEffects; locallab uses setup.ps1 instead of its own Windows Terminal merge; tests/smoke.ps1 adds a load-time budget; PSScriptAnalyzer 1.25.0 with PSScriptAnalyzerSettings.psd1 (F-28, F-30). - Put a failing, logging winget.cmd first on PATH in every sandbox and run Update-Tools against a recording winget; test.ps1 no longer installs PSScriptAnalyzer or overwrites the clipboard outside CI, and skips the install check on a machine without an install. - Regression cases for every fix of the follow-up review; each fails against the code before the fix.
- ci.yml: windows-2025 image, persist-credentials: false, timeouts, concurrency, PSScriptAnalyzer 1.25.0 with the settings file, sandboxed loads with a load-time budget, setup structure checks, unit tests on PowerShell 5.1 and 7, mocked functional run (F-12, F-28, F-30). - release.yml: publish tagged releases with SHA256SUMS.txt and a build provenance attestation (F-03, F-15). - scheduled.yml: weekly live-network functional run and pin check (F-11, F-28). - scheduled.yml: the PSFzf pin check fails when the version is missing.
- README: verified release install, no elevation, what setup changes and how uninstall reverts it, accurate command and test descriptions, limitations. - SECURITY.md: release verification, content-bound .psprc.ps1 trust, pinned components, complete data flows. CONTRIBUTING.md: tests, regression-test rule, required checks, releases (F-02, F-13). - Add CHANGELOG.md, AGENTS.md, CODE_OF_CONDUCT.md, .editorconfig and .mailmap; point the issue template at Troubleshooting instead of the disabled Discussions; extend the PR checklist (F-15, F-29).
Run tests/lint.ps1 with -NoInstall and skip the step with the install command when the pinned analyzer is missing, as tests/test.ps1 does.
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.
Fixes all 30 findings of the full-project audit of 2026-10-16 (commit
798d1c6): 4 High, 11 Medium, 15 Low. Everything that can be changed in the repository is changed here; the GitHub settings and the first release tag are listed under Maintainer follow-ups because a PR cannot change them.How it was verified
All runs were sandboxed (
LOCALAPPDATA,TEMPand$PROFILEredirected, machine-changing commands stubbed, a failingwinget.cmdfirst onPATH), on Windows PowerShell 5.1 and PowerShell 7.6:tests/unit.ps1(new): 65/65 on both editions. The 31 cases added or tightened in the review pass below were also run against a copy of the code with those fixes reverse-applied: all 31 fail there, the other 34 pass.tests/test.ps1: 28/28 steps; 110 commands executed, 0 failures.tests/ci-functional.ps1(mocked endpoints): 5/5, 106 of 166 exports executed (64%).tests/smoke.ps1: median profile load 175 ms (PowerShell 7) and 139 ms (5.1)..ps1without errors; no BOM or non-ASCII characters.scheduled.ymllogic) against upstream: every pinned hash matches, and the pins equal the latest upstream releases (Nerd Fonts v3.5.1, Oh My Posh v31.8.0).Findings and fixes
wt-changes.json(what was there before, what was written) and reverted value by value in every installed variant; values changed after install are kept. The untouched pre-install file is kept assettings.json.psp-original. Installs without a record restore the oldest backup and keep the current file. Backups go only after a successful revert.setup.ps1,Update-ProfileandSet-TerminalBackgroundall use the same recorder (Update-WindowsTerminalSettingsFile)..psprc.ps1trust is bound to the file's SHA-256 (trustedRcHashes); a changed file is skipped with a warning until re-approved withAdd-TrustedDirectory, likedirenv allow.psp-doctorlists stale approvals. SECURITY.md corrected.tests/lib/Sandbox.ps1: every suite redirectsLOCALAPPDATA,TEMPand$PROFILEbefore loading the profile, installs fail-closed stubs (winget, Install-/Uninstall-Module, Set-ExecutionPolicy, Start-Process, registry writes, firewall), stubs the oh-my-posh cache clear, and points font removal at the sandbox ($script:PSP.TestFontScopes). Suites refuse to run elevated without-AllowElevated.rawhunt.ps1needs-AllowSideEffects. Terminal-Icons is no longer removed.Uninstall-Profile(including-HardResetWindowsTerminal) leavessettings.jsonalone whenwindowsTerminal.manage = false. Regression test added.user-settings.jsonis parsed with comment/trailing-comma support on both editions; a file that still does not parse is never overwritten (wizard, trust and background writes); the previous file is kept as.bak.Update-Profileinstalls nothing unless-InstallMissingTools(documented: accepts winget agreements, pinned PSFzf).setup.ps1needs no elevation (per-user font and telemetry scope; machine-wide when elevated); no restart from an elevated setup; the wizard runs setup in the current session;winutilhashes and runs in-memory bytes; elevated launches use resolved paths (Get-PspShellPath,wt.exebyGet-Command -CommandType Application).killport/Stop-ListeningPortrefuse core Windows processes;Remove-LockedItemlists lockers and confirms each (-Treeopt-in);Clear-Cachevalidates the temp path, refuses drive roots/home/system folders and asks first (ConfirmImpact High);dstop/dpruneask first;pkillrefuses match-all patterns and asks for wildcards.v31.8.0with SHA-256 (default and presets), wizard catalog read at that tag; Nerd Fonts pinned tov3.5.1with every curated archive's SHA-256; PSFzf-RequiredVersion 2.7.12;npx http-server@14.1.1. Weekly pin check inscheduled.yml.tests/unit.ps1regression suite (WT revert, manage=false,.psprc.ps1re-approval, hash gates inUpdate-Profileandsetup.ps1with stubbed downloads, wizard input, encoding, guards, wizard resume). CI tests the shipped functions instead of copies. Coverage audits require at least 60% of exports to execute (local gate: 70% of covered); network soft-fail only on transport exception types. CONTRIBUTING: bug fix => regression test.-Allleftovers, test/coverage claims,Update-Profiledescription, Clear-Cache scope, Find-FileLocker on folders, required checks wording,.psprc.ps1trust, full data-flow table incl.GITHUB_TOKENand VirusTotal sharing, Discussions link). New "What setup changes" and "Limitations" sections.setup.ps1:Set-StrictMode,$ErrorActionPreference = 'Stop'with explicit handling, native calls isolated, backups stop the write on failure, atomic writes, transcript log in%LOCALAPPDATA%\PowerShellProfile\logs,[CmdletBinding(SupportsShouldProcess)]with-WhatIfhonored end to end,profile_user.ps1backed up and not rewritten when unchanged, documented exit codes, comment-based help, Windows build check.Versionin the profile,CHANGELOG.md,release.yml. Ruleset changes are maintainer follow-ups below.Clear-ProfileCachekeepstelemetry.owned,wt-changes.json,installed.json; the marker is deleted only after the variable is removed; scope (User/Machine) recorded in the marker.oldprofile.*.ps1copies are removed with-RemoveUserData/-All(or whenprofile.original.ps1was restored) and otherwise listed under Preserved; empty profile folders are removed.ConvertFrom-JsonC: string-aware tokenizer for//,/* */and trailing commas, used for every settings read.seddetects and keeps the encoding/BOM (UTF-8, UTF-16 LE/BE, UTF-32, ANSI); all settings reads useGet-Utf8FileText/Read-JsonFile; relative paths resolve against the PowerShell location.wifipassparsesnetsh wlan export profileXML;Get-WslDistrouses--running --quietplus position-based parsing;portsfallback matches the empty remote endpoint instead of the localized state.-Ref/-ExpectedSha256is given, requiressetprofile.ps1, never the temp root.certcheck/tlscertcaptureSslPolicyErrorsand print name-mismatch and chain problems.envmasks secret-looking names and values unless-ShowValues;pwndprompts with-AsSecureStringwhen called without an argument.Copy-SshKeyremote script adds a newline guard and skips present keys (tested under Git'ssh);trashusesMicrosoft.VisualBasic.FileIOwith a cancel check and reports permanent deletes;extractrefuses to overwrite without-Force..SYNOPSIS,.EXAMPLEfor the main commands) andsetup.ps1/setprofile.ps1; version shown;NO_COLORhonored throughWrite-PspHost; oh-my-posh render timeout (PromptTimeoutMs) with fallback prompt.nscan(andportscan) print an authorization notice; Full/Stealth/Vuln need confirmation (ShouldContinue) or-Force.setup.ps1loads its helpers from the verified profile (five pre-bundle helpers are copies checked by CI);locallab -Installusessetup.ps1 -CiModeinstead of its own merge; load-time state lives in$script:PSPand temporaries are removed after load.scheduled.ymlwithout uploading.persist-credentials: false, job timeouts, pinnedwindows-2025image, concurrency group. Harden-runner stays in audit mode: block mode is not available on Windows runners (comment corrected)..editorconfig,CHANGELOG.md,CODE_OF_CONDUCT.md,.mailmap(both author names ->26zl), trackedAGENTS.md. Old commit subjects are left as they are (rewritingmainwould need a force-push).-CommandType Application(no module discovery on a miss), kubeconfig re-read only when it changes, load-time budget in CI (tests/smoke.ps1: 3 s PS7, 4 s PS5.1).Found while testing and fixed too: user keybindings from
user-settings.jsonwere merged as a nested array (shipped bindings dropped); the wizard's quick-start preset never selected its font; empty arrays the profile created insettings.jsonstayed behind after uninstall.Review pass
Two independent reviews of this branch (profile; setup, tests and CI) found further problems, fixed here with a regression case each:
settings.json.psp-original(or the oldest backup), so uninstall restores the pre-install value instead of keeping ours; single-element arrays are no longer unrolled; bareprofileslists and profiles missing from the baseline are handled; an action the user bound to more keys is kept;-HardResetWindowsTerminaldrops the record of the deleted file;settings.json.psp-originalsurvives uninstall unless-RemoveUserData;Undo-WindowsTerminalSettingsFileisConfirmImpact High; an unreadableuser-settings.jsonmakes setup,Update-Profileand uninstall leavesettings.jsonalone..psprc.ps1that runs is the text that was hashed (no swap between check and run;$PSScriptRootstill set); trust changes merge into the file on disk, so another session's entries survive;winutil -ExpectedSha256/-Forceneed an elevated shell (unelevated, WinUtil relaunches itself from its latest, unchecked release);Invoke-ProfileWizard -Ref vX.Y.Zcheckssetup.ps1against the release checksums and passes the tag on;trashrefuses SSH/WinRM/service sessions, where Windows deletes permanently.profile.original.ps1is never a copy of this profile (installs made before Read config files as UTF-8 and keep durable profile backups #9 could end up with one); setup andsetprofile.ps1use the oldest backup that is the user's own, and uninstall never restores a copy of this profile, so it no longer deletes the user's real original.irm | iexdocumented as unsupported); no restart offer after a failed install; the quick start no longer installs VS Code when it is missing;[in a wizard reply no longer aborts the wizard;-LocalRepoprints the local bundle hash (a clone's CRLF files never match a release hash).Update-Profile -InstallMissingToolsworks on an up-to-date install; load-time temporaries are_psp*and only those are removed (a plugin's$cfgsurvives); oh-my-posh timeouts kill the whole process tree and the fallback no longer runs the prompt hooks twice;Set-PspInstallStatedrops stale values; internal writes never prompt or honor-WhatIftwice;Clear-CacheacceptsTemp\2and continues past files in use;Clear-ProfileCachekeeps background images;extractaccepts PSDrive paths;nscannames its target;envmasks more secret names and connection-string values.Update-Toolsis really executed against a recording winget;test.ps1no longer installs PSScriptAnalyzer or overwrites the clipboard, and skips the install check without an install; the scheduled PSFzf pin check can now fail; a unit case keeps$script:SharedFunctionNamescomplete.Upgrade notes for existing users
.psprc.ps1with an earlier version have no stored hash: their.psprc.ps1is skipped with a warning untilAdd-TrustedDirectoryis run once more in that directory.Update-Profileno longer installs missing tools by itself; use-InstallMissingTools.setup.ps1unelevated; elevation is no longer required. Runwinutil -ExpectedSha256/-Forcefrom an elevated shell.irm | iexis not supported.Maintainer follow-ups (not possible in a PR)
functionalto the required status checks; enable "Restrict deletions" and "Block force pushes"; change the Admin role bypass to "For pull requests only"; review the three app bypasses (IDs 29110, 946600, 1143301) and remove the ones that do not need to write tomain. Current settings:gh api repos/26zl/PowerShellPerfect/rulesets/13089750.git tag v1.0.0 <merge commit>andgit push origin v1.0.0.release.ymlchecks the tag againstVersion, publishes the files withSHA256SUMS.txtand the attestation, and takes the notes fromCHANGELOG.md(adjust the 1.0.0 date first if needed). Until then the README release command has no release to download; themaininstall path still works.gh repo edit 26zl/PowerShellPerfect --delete-branch-on-merge; enable Discussions if you want a Q&A space (the issue template now links to Troubleshooting instead).main). Harden-runnerblockmode is unavailable on Windows runners; switchrelease.yml(Ubuntu) toblockonce its first run shows the endpoints.Update-Profile -Ref, uninstall with WT Stable + Preview;wifipass/Get-WslDistrounder a non-English display language; behavior under WDAC/Constrained Language Mode is documented as unsupported.