microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix/dependabot-uuid-postcss-overrides

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/tests/security/Test-PSModulePins.Tests.ps1

317lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5BeforeAll {
6 . (Join-Path $PSScriptRoot '../../security/Test-PSModulePins.ps1')
7
8 Mock Write-Host {}
9
10 function script:New-PinFixtureRepo {
11 param(
12 [Parameter(Mandatory)][string]$Path,
13 [Parameter(Mandatory)][hashtable]$Files,
14 [Parameter(Mandatory)][string]$ConfigJson
15 )
16
17 New-Item -ItemType Directory -Force -Path $Path | Out-Null
18 Push-Location $Path
19 try {
20 git init --quiet --initial-branch=main 2>&1 | Out-Null
21 git config user.email 'test@example.com' 2>&1 | Out-Null
22 git config user.name 'Test' 2>&1 | Out-Null
23
24 $configDir = Join-Path $Path 'scripts/security'
25 New-Item -ItemType Directory -Force -Path $configDir | Out-Null
26 $configPath = Join-Path $configDir 'ps-module-versions.json'
27 Set-Content -LiteralPath $configPath -Value $ConfigJson -Encoding utf8
28
29 foreach ($rel in $Files.Keys) {
30 $full = Join-Path $Path $rel
31 $dir = Split-Path -Parent $full
32 if ($dir -and -not (Test-Path $dir)) {
33 New-Item -ItemType Directory -Force -Path $dir | Out-Null
34 }
35 Set-Content -LiteralPath $full -Value $Files[$rel] -Encoding utf8
36 }
37
38 git add -A 2>&1 | Out-Null
39 git commit --quiet -m 'fixture' 2>&1 | Out-Null
40 } finally {
41 Pop-Location
42 }
43
44 return $configPath
45 }
46
47 $script:CanonicalConfig = @'
48{
49 "modules": {
50 "Pester": { "version": "5.7.1" },
51 "PowerShell-Yaml": { "version": "0.4.7" },
52 "PSScriptAnalyzer":{ "version": "1.25.0" }
53 }
54}
55'@
56}
57
58Describe 'Invoke-PSModulePinScan' -Tag 'Unit' {
59 Context 'when all pins match canonical versions' {
60 It 'Returns 0 and reports zero violations' {
61 $repo = Join-Path $TestDrive 'happy'
62 $files = @{
63 'scripts/install.ps1' = @(
64 "Install-Module -Name Pester -RequiredVersion 5.7.1 -Force"
65 "Install-Module -Name PowerShell-Yaml -RequiredVersion '0.4.7' -Force"
66 ) -join "`n"
67 'workflows/lint.yml' = " Install-Module -Name PSScriptAnalyzer -RequiredVersion 1.25.0 -Scope CurrentUser"
68 }
69 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
70
71 Push-Location $repo
72 try {
73 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
74 } finally {
75 Pop-Location
76 }
77
78 $exit | Should -Be 0
79
80 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
81 $results.violationCount | Should -Be 0
82 $results.pinsFound | Should -BeGreaterOrEqual 3
83 }
84 }
85
86 Context 'when a pin does not match the canonical version' {
87 It 'Returns 1 and reports the violation with file, line, module, expected, and found fields' {
88 $repo = Join-Path $TestDrive 'violation'
89 $files = @{
90 'scripts/bad.ps1' = @(
91 "# header"
92 "Install-Module -Name Pester -RequiredVersion 5.6.0 -Force"
93 ) -join "`n"
94 }
95 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
96
97 Push-Location $repo
98 try {
99 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
100 } finally {
101 Pop-Location
102 }
103
104 $exit | Should -Be 1
105
106 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
107 $results.violationCount | Should -Be 1
108
109 $v = $results.violations[0]
110 $v.file | Should -Be 'scripts/bad.ps1'
111 $v.module | Should -Be 'Pester'
112 $v.found | Should -Be '5.6.0'
113 $v.expected | Should -Be '5.7.1'
114 $v.line | Should -Be 2
115 $v.snippet | Should -Match 'Install-Module'
116 }
117 }
118
119 Context 'when the violation is in a path on the allowed list' {
120 It 'Skips the file and returns 0' {
121 $repo = Join-Path $TestDrive 'allowed'
122 $files = @{
123 # This path is in the script's hardcoded $allowedFiles list and must be ignored.
124 'scripts/tests/security/Test-SHAStaleness.Tests.ps1' = @(
125 "Install-Module -Name Pester -RequiredVersion 9.9.9 -Force"
126 ) -join "`n"
127 }
128 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
129
130 Push-Location $repo
131 try {
132 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
133 } finally {
134 Pop-Location
135 }
136
137 $exit | Should -Be 0
138
139 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
140 $results.violationCount | Should -Be 0
141 }
142 }
143
144 Context 'when a #Requires-style hashtable pin is mismatched' {
145 It 'Detects the violation via the hashtable pattern' {
146 $repo = Join-Path $TestDrive 'requires'
147 $files = @{
148 'scripts/needs.ps1' = "#Requires -Modules @{ ModuleName='PSScriptAnalyzer'; RequiredVersion='1.20.0' }"
149 }
150 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
151
152 Push-Location $repo
153 try {
154 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
155 } finally {
156 Pop-Location
157 }
158
159 $exit | Should -Be 1
160
161 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
162 $results.violationCount | Should -Be 1
163 $results.violations[0].module | Should -Be 'PSScriptAnalyzer'
164 $results.violations[0].found | Should -Be '1.20.0'
165 $results.violations[0].expected | Should -Be '1.25.0'
166 }
167 }
168
169 Context 'when the config path does not exist' {
170 It 'Throws a descriptive error' {
171 $repo = Join-Path $TestDrive 'missing-config'
172 New-Item -ItemType Directory -Force -Path $repo | Out-Null
173 Push-Location $repo
174 try {
175 git init --quiet --initial-branch=main 2>&1 | Out-Null
176 { Invoke-PSModulePinScan -ConfigPath (Join-Path $repo 'nope.json') } |
177 Should -Throw '*Pin config not found*'
178 } finally {
179 Pop-Location
180 }
181 }
182 }
183
184 Context 'when pins use Import-Module or Update-Module verbs' {
185 It 'Detects matched and mismatched pins for all three verbs' {
186 $repo = Join-Path $TestDrive 'verbs'
187 $files = @{
188 'scripts/verbs.ps1' = @(
189 "Import-Module -Name Pester -RequiredVersion 5.7.1"
190 "Update-Module -Name PowerShell-Yaml -RequiredVersion 0.4.6"
191 ) -join "`n"
192 }
193 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
194
195 Push-Location $repo
196 try {
197 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
198 } finally {
199 Pop-Location
200 }
201
202 $exit | Should -Be 1
203
204 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
205 $results.pinsFound | Should -Be 2
206 $results.violationCount | Should -Be 1
207 $results.violations[0].module | Should -Be 'PowerShell-Yaml'
208 $results.violations[0].found | Should -Be '0.4.6'
209 }
210 }
211
212 Context 'when multiple violations exist across multiple files' {
213 It 'Aggregates every violation with correct file and line attribution' {
214 $repo = Join-Path $TestDrive 'multi'
215 $files = @{
216 'scripts/a.ps1' = @(
217 "Install-Module -Name Pester -RequiredVersion 5.0.0 -Force"
218 "Install-Module -Name PowerShell-Yaml -RequiredVersion 0.4.0 -Force"
219 ) -join "`n"
220 'scripts/b.ps1' = "Install-Module -Name PSScriptAnalyzer -RequiredVersion 1.21.0 -Force"
221 }
222 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
223
224 Push-Location $repo
225 try {
226 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
227 } finally {
228 Pop-Location
229 }
230
231 $exit | Should -Be 1
232
233 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
234 $results.violationCount | Should -Be 3
235
236 $byFile = $results.violations | Group-Object file
237 ($byFile | Where-Object Name -eq 'scripts/a.ps1').Count | Should -Be 2
238 ($byFile | Where-Object Name -eq 'scripts/b.ps1').Count | Should -Be 1
239
240 $aLines = ($results.violations | Where-Object file -eq 'scripts/a.ps1' | Sort-Object line).line
241 $aLines | Should -Be @(1, 2)
242 }
243 }
244
245 Context 'when a violation is in an untracked file' {
246 It 'Ignores the untracked file and returns 0' {
247 $repo = Join-Path $TestDrive 'untracked'
248 $files = @{
249 'scripts/clean.ps1' = "Install-Module -Name Pester -RequiredVersion 5.7.1 -Force"
250 }
251 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
252
253 # Add an untracked file containing a clear violation after the fixture commit.
254 Set-Content -LiteralPath (Join-Path $repo 'scripts/untracked.ps1') `
255 -Value "Install-Module -Name Pester -RequiredVersion 9.9.9 -Force" -Encoding utf8
256
257 Push-Location $repo
258 try {
259 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
260 } finally {
261 Pop-Location
262 }
263
264 $exit | Should -Be 0
265 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
266 $results.violationCount | Should -Be 0
267 }
268 }
269
270 Context 'when a violation appears in a file with an unsupported extension' {
271 It 'Skips the file based on extension filtering' {
272 $repo = Join-Path $TestDrive 'ext'
273 $files = @{
274 'notes.txt' = "Install-Module -Name Pester -RequiredVersion 9.9.9 -Force"
275 'scripts/keep.ps1' = "Install-Module -Name Pester -RequiredVersion 5.7.1 -Force"
276 }
277 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
278
279 Push-Location $repo
280 try {
281 $exit = Invoke-PSModulePinScan -ConfigPath $configPath
282 } finally {
283 Pop-Location
284 }
285
286 $exit | Should -Be 0
287 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
288 $results.violationCount | Should -Be 0
289 $results.pinsFound | Should -Be 1
290 }
291 }
292
293 Context 'results JSON metadata' {
294 It 'Records configPath, canonical map, filesScanned, and allowedFiles' {
295 $repo = Join-Path $TestDrive 'metadata'
296 $files = @{
297 'scripts/install.ps1' = "Install-Module -Name Pester -RequiredVersion 5.7.1 -Force"
298 }
299 $configPath = New-PinFixtureRepo -Path $repo -Files $files -ConfigJson $script:CanonicalConfig
300
301 Push-Location $repo
302 try {
303 Invoke-PSModulePinScan -ConfigPath $configPath | Out-Null
304 } finally {
305 Pop-Location
306 }
307
308 $results = Get-Content -Raw (Join-Path $repo 'logs/ps-module-pins-results.json') | ConvertFrom-Json
309 $results.configPath | Should -Match 'ps-module-versions\.json$'
310 $results.canonical.Pester | Should -Be '5.7.1'
311 $results.canonical.'PowerShell-Yaml' | Should -Be '0.4.7'
312 $results.filesScanned | Should -BeGreaterOrEqual 1
313 $results.allowedFiles | Should -Contain 'scripts/security/ps-module-versions.json'
314 $results.allowedFiles | Should -Contain 'scripts/security/Test-PSModulePins.ps1'
315 }
316 }
317}
318