microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-copilot-code-review

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/security/Test-DependencyPinning.ps1

730lines · modecode

1#!/usr/bin/env pwsh
2<#
3.SYNOPSIS
4 Verifies and reports on SHA pinning compliance for supply chain security.
5
6.DESCRIPTION
7 Cross-platform PowerShell script that analyzes GitHub Actions workflows, Docker images,
8 and other dependency declarations to verify compliance with SHA pinning security practices.
9 Identifies unpinned dependencies and provides remediation guidance.
10
11.PARAMETER Path
12 Root path to scan for dependency files. Defaults to current directory.
13
14.PARAMETER Recursive
15 Scan recursively through subdirectories. Default is true.
16
17.PARAMETER Format
18 Output format for compliance report. Options: json, sarif, csv, markdown, table.
19 Default is 'json' for programmatic processing.
20
21.PARAMETER OutputPath
22 Path where compliance results should be saved. Defaults to 'dependency-pinning-report.json'
23 in the current directory.
24
25.PARAMETER FailOnUnpinned
26 Exit with error code if pinning violations are found. Default is false for reporting mode.
27
28.PARAMETER ExcludePaths
29 Comma-separated list of paths to exclude from scanning (glob patterns supported).
30
31.PARAMETER IncludeTypes
32 Comma-separated list of dependency types to check. Options: github-actions, npm, pip.
33 Default is all types.
34
35.PARAMETER Threshold
36 Minimum compliance score percentage required for passing grade (0-100).
37 Script will exit with code 1 if compliance falls below threshold when -FailOnUnpinned is set.
38 Default is 95%.
39
40.PARAMETER Remediate
41 Generate remediation suggestions with specific SHA pins for unpinned dependencies.
42
43.EXAMPLE
44 ./Test-DependencyPinning.ps1
45 Scan current directory for dependency pinning compliance.
46
47.EXAMPLE
48 ./Test-DependencyPinning.ps1 -Path "/workspace" -Format "sarif" -FailOnUnpinned
49 Scan workspace directory, output SARIF format, fail on violations.
50
51.EXAMPLE
52 ./Test-DependencyPinning.ps1 -IncludeTypes "github-actions,pip" -Remediate
53 Check only GitHub Actions and pip dependencies with remediation suggestions.
54
55.EXAMPLE
56 ./Test-DependencyPinning.ps1 -Threshold 90 -FailOnUnpinned
57 Enforce 90% compliance threshold and fail build if not met.
58
59.EXAMPLE
60 ./Test-DependencyPinning.ps1 -Threshold 100 -IncludeTypes "github-actions"
61 Require 100% SHA pinning for GitHub Actions only.
62
63.EXAMPLE
64 ./Test-DependencyPinning.ps1 -Threshold 80
65 Report compliance against 80% threshold but continue on violations.
66
67.NOTES
68 Requires:
69 - PowerShell 7.0 or later for cross-platform compatibility
70 - Internet connectivity for SHA resolution (with -Remediate)
71 - GitHub API access for action SHA resolution (optional)
72
73 Compatible with:
74 - Windows PowerShell 5.1+ (limited cross-platform features)
75 - PowerShell 7.x on Windows, Linux, macOS
76 - GitHub Actions runners (ubuntu-latest, windows-latest, macos-latest)
77 - Azure DevOps agents (Microsoft-hosted and self-hosted)
78
79.LINK
80 https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions
81#>
82
83[CmdletBinding()]
84param(
85 [Parameter(Mandatory = $false)]
86 [string]$Path = ".",
87
88 [Parameter(Mandatory = $false)]
89 [switch]$Recursive,
90
91 [Parameter(Mandatory = $false)]
92 [ValidateSet('json', 'sarif', 'csv', 'markdown', 'table')]
93 [string]$Format = 'json',
94
95 [Parameter(Mandatory = $false)]
96 [string]$OutputPath = 'logs/dependency-pinning-results.json',
97
98 [Parameter(Mandatory = $false)]
99 [switch]$FailOnUnpinned,
100
101 [Parameter(Mandatory = $false)]
102 [string]$ExcludePaths = "",
103
104 [Parameter(Mandatory = $false)]
105 [string]$IncludeTypes = "github-actions,npm,pip",
106
107 [Parameter(Mandatory = $false)]
108 [ValidateRange(0, 100)]
109 [int]$Threshold = 95,
110
111 [Parameter(Mandatory = $false)]
112 [switch]$Remediate
113)
114
115# Set error action preference for consistent error handling
116$ErrorActionPreference = 'Stop'
117
118# Define dependency patterns for different ecosystems
119$DependencyPatterns = @{
120 'github-actions' = @{
121 FilePatterns = @('**/.github/workflows/*.yml', '**/.github/workflows/*.yaml')
122 VersionPatterns = @(
123 @{
124 Pattern = 'uses:\s*([^@\s]+)@([^#\s]+)'
125 Groups = @{ Action = 1; Version = 2 }
126 Description = 'GitHub Actions uses statements'
127 }
128 )
129 SHAPattern = '^[a-fA-F0-9]{40}$'
130 RemediationUrl = 'https://api.github.com/repos/{0}/commits/{1}'
131 }
132
133 'npm' = @{
134 FilePatterns = @('**/package.json', '**/package-lock.json')
135 VersionPatterns = @(
136 @{
137 Pattern = '"([^"]+)":\s*"([^@][^"]*)"'
138 Groups = @{ Package = 1; Version = 2 }
139 Description = 'NPM dependencies in package.json'
140 }
141 )
142 SHAPattern = '^[a-fA-F0-9]{40}$'
143 RemediationUrl = 'https://registry.npmjs.org/{0}/{1}'
144 }
145
146 'pip' = @{
147 FilePatterns = @('**/requirements*.txt', '**/Pipfile', '**/pyproject.toml', '**/setup.py')
148 VersionPatterns = @(
149 @{
150 Pattern = '([a-zA-Z0-9\-_]+)==([^#\s]+)'
151 Groups = @{ Package = 1; Version = 2 }
152 Description = 'Python pip requirements'
153 }
154 )
155 SHAPattern = '^[a-fA-F0-9]{40}$'
156 RemediationUrl = 'https://pypi.org/pypi/{0}/{1}/json'
157 }
158}
159
160class DependencyViolation {
161 [string]$File
162 [int]$Line
163 [string]$Type
164 [string]$Name
165 [string]$Version
166 [string]$CurrentRef
167 [string]$Severity
168 [string]$Description
169 [string]$Remediation
170 [hashtable]$Metadata
171
172 DependencyViolation() {
173 $this.Metadata = @{}
174 }
175}
176
177class ComplianceReport {
178 [string]$ScanPath
179 [datetime]$Timestamp
180 [int]$TotalFiles
181 [int]$ScannedFiles
182 [int]$TotalDependencies
183 [int]$PinnedDependencies
184 [int]$UnpinnedDependencies
185 [decimal]$ComplianceScore
186 [DependencyViolation[]]$Violations
187 [hashtable]$Summary
188 [hashtable]$Metadata
189
190 ComplianceReport() {
191 $this.Timestamp = Get-Date
192 $this.Violations = @()
193 $this.Summary = @{}
194 $this.Metadata = @{}
195 }
196}
197
198function Write-PinningLog {
199 param(
200 [Parameter(Mandatory = $true)]
201 [string]$Message,
202
203 [Parameter(Mandatory = $false)]
204 [ValidateSet('Info', 'Warning', 'Error', 'Success')]
205 [string]$Level = 'Info'
206 )
207
208 $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
209 Write-Output "[$timestamp] [$Level] $Message"
210}
211
212function Get-FilesToScan {
213 <#
214 .SYNOPSIS
215 Discovers files to scan based on dependency type patterns.
216 #>
217 param(
218 [string]$ScanPath,
219 [string[]]$Types,
220 [string[]]$ExcludePatterns,
221 [switch]$Recursive
222 )
223
224 $allFiles = @()
225
226 foreach ($type in $Types) {
227 if ($DependencyPatterns.ContainsKey($type)) {
228 $patterns = $DependencyPatterns[$type].FilePatterns
229
230 foreach ($pattern in $patterns) {
231 # Convert glob pattern to PowerShell-compatible path
232 $searchPath = Join-Path $ScanPath $pattern
233
234 try {
235 if ($Recursive) {
236 $files = Get-ChildItem -Path $searchPath -Recurse -File -ErrorAction SilentlyContinue
237 }
238 else {
239 $files = Get-ChildItem -Path $searchPath -File -ErrorAction SilentlyContinue
240 }
241
242 # Apply exclusion filters
243 if ($ExcludePatterns) {
244 foreach ($exclude in $ExcludePatterns) {
245 $files = $files | Where-Object { $_.FullName -notlike "*$exclude*" }
246 }
247 }
248
249 $allFiles += $files | ForEach-Object {
250 @{
251 Path = $_.FullName
252 Type = $type
253 RelativePath = [System.IO.Path]::GetRelativePath($ScanPath, $_.FullName)
254 }
255 }
256 }
257 catch {
258 Write-PinningLog "Error scanning for $type files with pattern $pattern`: $($_.Exception.Message)" -Level Warning
259 }
260 }
261 }
262 }
263
264 return $allFiles | Sort-Object Path -Unique
265}
266
267function Test-SHAPinning {
268 <#
269 .SYNOPSIS
270 Tests if a version reference is properly SHA-pinned.
271 #>
272 param(
273 [string]$Version,
274 [string]$Type
275 )
276
277 if ($DependencyPatterns.ContainsKey($Type) -and $DependencyPatterns[$Type].SHAPattern) {
278 $shaPattern = $DependencyPatterns[$Type].SHAPattern
279 return $Version -match $shaPattern
280 }
281
282 return $false
283}
284
285function Get-DependencyViolation {
286 <#
287 .SYNOPSIS
288 Scans a file for dependency pinning violations.
289 #>
290 param(
291 [hashtable]$FileInfo
292 )
293
294 $violations = @()
295 $filePath = $FileInfo.Path
296 $fileType = $FileInfo.Type
297
298 if (!(Test-Path $filePath)) {
299 return $violations
300 }
301
302 try {
303 $content = Get-Content -Path $filePath -Raw
304 $lines = Get-Content -Path $filePath
305
306 $patterns = $DependencyPatterns[$fileType].VersionPatterns
307
308 foreach ($patternInfo in $patterns) {
309 $pattern = $patternInfo.Pattern
310 $description = $patternInfo.Description
311
312 $regexMatches = [regex]::Matches($content, $pattern, [System.Text.RegularExpressions.RegexOptions]::Multiline)
313
314 foreach ($match in $regexMatches) {
315 # Find line number
316 $lineNumber = 1
317 $position = $match.Index
318 for ($i = 0; $i -lt $position; $i++) {
319 if ($content[$i] -eq "`n") {
320 $lineNumber++
321 }
322 }
323
324 # Extract dependency information
325 $dependencyName = $match.Groups[1].Value
326 $version = $match.Groups[2].Value
327
328 # Check if properly pinned
329 if (!(Test-SHAPinning -Version $version -Type $fileType)) {
330 $violation = [DependencyViolation]::new()
331 $violation.File = $FileInfo.RelativePath
332 $violation.Line = $lineNumber
333 $violation.Type = $fileType
334 $violation.Name = $dependencyName
335 $violation.Version = $version
336 $violation.CurrentRef = $match.Value
337 $violation.Description = "Unpinned dependency: $description"
338 $violation.Severity = if ($fileType -eq 'github-actions') { 'High' } else { 'Medium' }
339 $violation.Metadata['PatternDescription'] = $description
340 $violation.Metadata['LineContent'] = $lines[$lineNumber - 1]
341
342 $violations += $violation
343 }
344 }
345 }
346 }
347 catch {
348 Write-PinningLog "Error scanning file $filePath`: $($_.Exception.Message)" -Level Warning
349 }
350
351 return $violations
352}
353
354function Get-RemediationSuggestion {
355 <#
356 .SYNOPSIS
357 Generates remediation suggestions for unpinned dependencies.
358 #>
359 param(
360 [DependencyViolation]$Violation,
361
362 [switch]$Remediate
363 )
364
365 $type = $Violation.Type
366 $name = $Violation.Name
367 $version = $Violation.Version
368
369 if (!$Remediate) {
370 return "Enable -Remediate flag for specific SHA suggestions"
371 }
372
373 try {
374 switch ($type) {
375 'github-actions' {
376 # For GitHub Actions, resolve tag to commit SHA
377 $apiUrl = "https://api.github.com/repos/$name/commits/$version"
378 $headers = @{}
379
380 if ($env:GITHUB_TOKEN) {
381 $headers['Authorization'] = "Bearer $env:GITHUB_TOKEN"
382 }
383
384 $response = Invoke-RestMethod -Uri $apiUrl -Headers $headers -TimeoutSec 30
385 $sha = $response.sha
386
387 if ($sha) {
388 return "Pin to SHA: uses: $name@$sha # $version"
389 }
390 }
391
392 default {
393 return "Research and pin to specific commit SHA or content hash for $type dependencies"
394 }
395 }
396 }
397 catch {
398 Write-PinningLog "Could not generate automatic remediation for $($Violation.Name): $($_.Exception.Message)" -Level Warning
399 }
400
401 return "Manually research and pin to immutable reference"
402}
403
404function Get-ComplianceReportData {
405 <#
406 .SYNOPSIS
407 Generates a comprehensive compliance report.
408 #>
409 param(
410 [DependencyViolation[]]$Violations,
411 [hashtable[]]$ScannedFiles,
412 [string]$ScanPath,
413 [switch]$Remediate
414 )
415
416 $report = [ComplianceReport]::new()
417 $report.ScanPath = $ScanPath
418 $report.ScannedFiles = $ScannedFiles.Count
419 $report.Violations = $Violations
420
421 # Calculate metrics
422 $totalDeps = ($Violations | Measure-Object).Count
423 $unpinnedDeps = ($Violations | Where-Object { $_.Severity -ne 'Info' } | Measure-Object).Count
424 $pinnedDeps = $totalDeps - $unpinnedDeps
425
426 $report.TotalDependencies = $totalDeps
427 $report.PinnedDependencies = $pinnedDeps
428 $report.UnpinnedDependencies = $unpinnedDeps
429
430 if ($totalDeps -gt 0) {
431 $report.ComplianceScore = [math]::Round(($pinnedDeps / $totalDeps) * 100, 2)
432 }
433 else {
434 $report.ComplianceScore = 100.0
435 }
436
437 # Generate summary by type
438 $report.Summary = @{}
439 foreach ($type in ($Violations | Group-Object Type)) {
440 $report.Summary[$type.Name] = @{
441 Total = $type.Count
442 High = ($type.Group | Where-Object { $_.Severity -eq 'High' } | Measure-Object).Count
443 Medium = ($type.Group | Where-Object { $_.Severity -eq 'Medium' } | Measure-Object).Count
444 Low = ($type.Group | Where-Object { $_.Severity -eq 'Low' } | Measure-Object).Count
445 }
446 }
447
448 # Add metadata
449 $report.Metadata = @{
450 PowerShellVersion = $PSVersionTable.PSVersion.ToString()
451 Platform = $PSVersionTable.Platform
452 ScanTimestamp = $report.Timestamp.ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
453 IncludedTypes = $IncludeTypes
454 ExcludedPaths = $ExcludePaths
455 RemediationEnabled = $Remediate.IsPresent
456 ComplianceThreshold = $Threshold
457 }
458
459 return $report
460}
461
462function Export-ComplianceReport {
463 <#
464 .SYNOPSIS
465 Exports compliance report in specified format.
466 #>
467 param(
468 [ComplianceReport]$Report,
469 [string]$Format,
470 [string]$OutputPath
471 )
472
473 # Ensure parent directory exists
474 $parentDir = Split-Path -Path $OutputPath -Parent
475 if ($parentDir -and -not (Test-Path $parentDir)) {
476 New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
477 }
478
479 switch ($Format.ToLower()) {
480 'json' {
481 $Report | ConvertTo-Json -Depth 10 | Out-File -FilePath $OutputPath -Encoding UTF8
482 }
483
484 'sarif' {
485 $sarif = @{
486 version = "2.1.0"
487 "`$schema" = "https://json.schemastore.org/sarif-2.1.0.json"
488 runs = @(@{
489 tool = @{
490 driver = @{
491 name = "dependency-pinning-analyzer"
492 version = "1.0.0"
493 informationUri = "https://github.com/microsoft/hve-core"
494 }
495 }
496 results = @($Report.Violations | ForEach-Object {
497 @{
498 ruleId = "dependency-not-pinned"
499 level = switch ($_.Severity) { 'High' { 'error' } 'Medium' { 'warning' } default { 'note' } }
500 message = @{ text = $_.Description }
501 locations = @(@{
502 physicalLocation = @{
503 artifactLocation = @{ uri = $_.File }
504 region = @{ startLine = $_.Line }
505 }
506 })
507 properties = @{
508 dependencyName = $_.Name
509 currentVersion = $_.Version
510 remediation = $_.Remediation
511 }
512 }
513 })
514 })
515 }
516 $sarif | ConvertTo-Json -Depth 10 | Out-File -FilePath $OutputPath -Encoding UTF8
517 }
518
519 'csv' {
520 $Report.Violations | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
521 }
522
523 'markdown' {
524 $markdown = @"
525# Dependency Pinning Compliance Report
526
527**Scan Date:** $($Report.Timestamp.ToString('yyyy-MM-dd HH:mm:ss'))
528**Scan Path:** $($Report.ScanPath)
529**Compliance Score:** $($Report.ComplianceScore)%
530
531## Summary
532
533| Metric | Count |
534|--------|--------|
535| Total Files Scanned | $($Report.ScannedFiles) |
536| Total Dependencies | $($Report.TotalDependencies) |
537| Pinned Dependencies | $($Report.PinnedDependencies) |
538| Unpinned Dependencies | $($Report.UnpinnedDependencies) |
539
540## Violations by Type
541
542"@
543 foreach ($type in $Report.Summary.Keys) {
544 $summary = $Report.Summary[$type]
545 $markdown += @"
546
547### $type
548- **Total:** $($summary.Total)
549- **High Severity:** $($summary.High)
550- **Medium Severity:** $($summary.Medium)
551- **Low Severity:** $($summary.Low)
552
553"@
554 }
555
556 if ($Report.Violations.Count -gt 0) {
557 $markdown += @"
558
559## Detailed Violations
560
561| File | Line | Type | Dependency | Current Version | Severity | Remediation |
562|------|------|------|------------|----------------|----------|-------------|
563"@
564 foreach ($violation in $Report.Violations) {
565 $markdown += "|$($violation.File)|$($violation.Line)|$($violation.Type)|$($violation.Name)|$($violation.Version)|$($violation.Severity)|$($violation.Remediation)|`n"
566 }
567 }
568
569 $markdown | Out-File -FilePath $OutputPath -Encoding UTF8
570 }
571
572 'table' {
573 # Display formatted table to console and save simple text format
574 if ($Report.Violations.Count -gt 0) {
575 $Report.Violations | Format-Table -Property File, Line, Type, Name, Version, Severity -AutoSize | Out-File -FilePath $OutputPath -Encoding UTF8 -Width 200
576 }
577 else {
578 "No dependency pinning violations found." | Out-File -FilePath $OutputPath -Encoding UTF8
579 }
580 }
581 }
582
583 Write-PinningLog "Compliance report exported to: $OutputPath" -Level Success
584}
585
586function Export-CICDArtifact {
587 <#
588 .SYNOPSIS
589 Exports compliance report as CI/CD artifacts for both GitHub Actions and Azure DevOps.
590 #>
591 param(
592 [ComplianceReport]$Report,
593 [string]$ReportPath
594 )
595
596 Write-PinningLog "Preparing compliance artifacts for CI/CD systems..." -Level Info
597
598 # GitHub Actions artifact export
599 if ($env:GITHUB_ACTIONS -eq 'true') {
600 Write-PinningLog "Detected GitHub Actions environment - setting up artifacts" -Level Info
601
602 # Set outputs for GitHub Actions
603 if ($env:GITHUB_OUTPUT) {
604 "dependency-report=$ReportPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding UTF8
605 "compliance-score=$($Report.ComplianceScore)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding UTF8
606 "unpinned-count=$($Report.UnpinnedDependencies)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding UTF8
607 }
608
609 # Set up for actions/upload-artifact@v4
610 $artifactDir = Join-Path $PWD "dependency-pinning-artifacts"
611 New-Item -ItemType Directory -Path $artifactDir -Force | Out-Null
612 Copy-Item -Path $ReportPath -Destination $artifactDir -Force
613
614 # Create GitHub summary
615 $summaryPath = Join-Path $artifactDir "github-summary.md"
616 @"
617# 📌 Dependency Pinning Analysis
618
619**Compliance Score:** $($Report.ComplianceScore)%
620**Unpinned Dependencies:** $($Report.UnpinnedDependencies)
621**Total Dependencies Scanned:** $($Report.TotalDependencies)
622
623$(if ($Report.UnpinnedDependencies -gt 0) { "⚠️ **Action Required:** $($Report.UnpinnedDependencies) dependencies are not properly pinned to immutable references." } else { "✅ **All Clear:** All dependencies are properly pinned!" })
624"@ | Out-File -FilePath $summaryPath -Encoding UTF8
625
626 if ($env:GITHUB_STEP_SUMMARY) {
627 Copy-Item -Path $summaryPath -Destination $env:GITHUB_STEP_SUMMARY -Force
628 }
629 }
630
631 # Azure DevOps artifact export
632 if ($env:TF_BUILD -eq 'True' -or $env:AZURE_PIPELINES -eq 'True') {
633 Write-PinningLog "Detected Azure DevOps environment - setting up artifacts" -Level Info
634
635 # Set Azure DevOps variables
636 Write-Output "##vso[task.setvariable variable=dependencyReport;isOutput=true]$ReportPath"
637 Write-Output "##vso[task.setvariable variable=complianceScore;isOutput=true]$($Report.ComplianceScore)"
638 Write-Output "##vso[task.setvariable variable=unpinnedCount;isOutput=true]$($Report.UnpinnedDependencies)"
639
640 # Publish as pipeline artifact
641 Write-Output "##vso[artifact.upload containerfolder=dependency-pinning;artifactname=dependency-pinning-report]$ReportPath"
642
643 # Add to build summary
644 Write-Output "##[section]Dependency Pinning Compliance Report"
645 Write-Output "Compliance Score: $($Report.ComplianceScore)%"
646 Write-Output "Unpinned Dependencies: $($Report.UnpinnedDependencies)"
647 Write-Output "Total Dependencies: $($Report.TotalDependencies)"
648 }
649
650 Write-PinningLog "Compliance artifacts prepared for CI/CD consumption" -Level Success
651}
652
653# Main execution
654try {
655 Write-PinningLog "Starting dependency pinning compliance analysis..." -Level Info
656 Write-PinningLog "PowerShell Version: $($PSVersionTable.PSVersion)" -Level Info
657 Write-PinningLog "Platform: $($PSVersionTable.Platform)" -Level Info
658
659 # Parse include types and exclude paths
660 $typesToCheck = $IncludeTypes.Split(',') | ForEach-Object { $_.Trim() }
661 $excludePatterns = if ($ExcludePaths) { $ExcludePaths.Split(',') | ForEach-Object { $_.Trim() } } else { @() }
662
663 Write-PinningLog "Scanning path: $Path" -Level Info
664 Write-PinningLog "Include types: $($typesToCheck -join ', ')" -Level Info
665 if ($excludePatterns) { Write-PinningLog "Exclude patterns: $($excludePatterns -join ', ')" -Level Info }
666
667 # Discover files to scan
668 $filesToScan = Get-FilesToScan -ScanPath $Path -Types $typesToCheck -ExcludePatterns $excludePatterns -Recursive:$Recursive
669 Write-PinningLog "Found $($filesToScan.Count) files to scan" -Level Info
670
671 # Scan for violations
672 $allViolations = @()
673 foreach ($fileInfo in $filesToScan) {
674 Write-PinningLog "Scanning: $($fileInfo.RelativePath)" -Level Info
675 $violations = Get-DependencyViolation -FileInfo $fileInfo
676
677 # Add remediation suggestions
678 foreach ($violation in $violations) {
679 $violation.Remediation = Get-RemediationSuggestion -Violation $violation -Remediate:$Remediate
680 }
681
682 $allViolations += $violations
683 }
684
685 Write-PinningLog "Found $($allViolations.Count) dependency pinning violations" -Level Info
686
687 # Generate compliance report
688 $report = Get-ComplianceReportData -Violations $allViolations -ScannedFiles $filesToScan -ScanPath $Path -Remediate:$Remediate
689
690 # Export report
691 Export-ComplianceReport -Report $report -Format $Format -OutputPath $OutputPath
692
693 # Export CI/CD artifacts
694 Export-CICDArtifact -Report $report -ReportPath $OutputPath
695
696 # Display summary
697 Write-PinningLog "Compliance Analysis Complete!" -Level Success
698 Write-PinningLog "Compliance Score: $($report.ComplianceScore)%" -Level Info
699 Write-PinningLog "Total Dependencies: $($report.TotalDependencies)" -Level Info
700 Write-PinningLog "Unpinned Dependencies: $($report.UnpinnedDependencies)" -Level Info
701
702 if ($report.UnpinnedDependencies -gt 0) {
703 Write-PinningLog "$($report.UnpinnedDependencies) dependencies require SHA pinning for security compliance" -Level Warning
704
705 # Check threshold compliance
706 if ($report.ComplianceScore -lt $Threshold) {
707 Write-PinningLog "Compliance score $($report.ComplianceScore)% is below threshold $Threshold%" -Level Error
708
709 if ($FailOnUnpinned) {
710 Write-PinningLog "Failing build due to compliance threshold violation (-FailOnUnpinned enabled)" -Level Error
711 exit 1
712 }
713 else {
714 Write-PinningLog "Threshold violation detected but continuing (soft-fail mode)" -Level Warning
715 }
716 }
717 else {
718 Write-PinningLog "Compliance score $($report.ComplianceScore)% meets threshold $Threshold%" -Level Info
719 }
720 }
721 else {
722 Write-PinningLog "All dependencies are properly pinned! ✅ (100% compliance, exceeds $Threshold% threshold)" -Level Success
723 }
724
725}
726catch {
727 Write-PinningLog "Dependency pinning analysis failed: $($_.Exception.Message)" -Level Error
728 Write-PinningLog "Stack trace: $($_.ScriptStackTrace)" -Level Error
729 exit 1
730}
731