-
Notifications
You must be signed in to change notification settings - Fork 125
Description
Summary
Get-ComplianceReportData in scripts/security/Test-DependencyPinning.ps1 produces a compliance score that is always either 0% or 100%, never a meaningful partial value. The root cause is that TotalDependencies is set to the violation count rather than the total number of scanned dependencies, and a dead-code severity filter (-ne 'Info') masks the problem.
Root Cause
The function computes compliance like this (lines 779–793):
$totalDeps = @($Violations).Count
$unpinnedDeps = @($Violations | Where-Object { $_.Severity -ne 'Info' }).Count
$pinnedDeps = $totalDeps - $unpinnedDeps
# ...
$report.ComplianceScore = [math]::Round(($pinnedDeps / $totalDeps) * 100, 2)Problem 1 — TotalDependencies equals violation count, not dependency count.
The function only receives $Violations and $ScannedFiles, with no information about total scanned dependencies. It sets TotalDependencies = @($Violations).Count, so the denominator is only ever the number of failures. The ComplianceReport class (scripts/security/Modules/SecurityClasses.psm1) was designed for partial compliance — CalculateScore() computes PinnedDependencies / TotalDependencies — but the function never provides a real total, rendering the formula meaningless.
Problem 2 — Dead-code severity filter.
The Where-Object { $_.Severity -ne 'Info' } filter is intended to exclude informational items from the unpinned count and treat them as "pinned." However, all three DependencyViolation creation sites in the codebase assign only 'Medium' or 'High':
| Location | Line | Severity |
|---|---|---|
Get-DownloadDependencyViolations |
~377 | 'Medium' |
Get-NpmDependencyViolations |
~466 | 'Medium' |
| Generic pattern matching | ~693 | 'High' (github-actions) / 'Medium' (other) |
Because no violation ever carries 'Info' severity, $unpinnedDeps always equals $totalDeps, $pinnedDeps is always 0, and the score is always 0.0% when any violations exist.
Problem 3 — Summary section also skips 'Info'.
The summary loop (lines 800–807) counts only 'High', 'Medium', and 'Low' severities, reinforcing that 'Info' was never a real category in the scanning pipeline.
Semantic Gap with ComplianceReport Class
ComplianceReport in SecurityClasses.psm1 has distinct TotalDependencies and PinnedDependencies fields and a CalculateScore() method that computes a meaningful ratio. This design implies the report should reflect all scanned dependencies (passing and failing), not just failure counts. The function needs access to total dependency counts for the score to be useful.
Expected Behavior
TotalDependenciesreflects the total number of dependencies scanned across all files (both compliant and non-compliant).PinnedDependenciesreflects the count of compliant (properly pinned) dependencies.UnpinnedDependenciesreflects the count of non-compliant dependencies (the violation list).ComplianceScorecomputes a meaningful percentage:(PinnedDependencies / TotalDependencies) * 100.- Remove or repurpose the dead
'Info'severity filter since no code path produces it.
Actual Behavior
TotalDependencies= violation count (not total scanned dependencies).PinnedDependencies= 0 (always, due to dead severity filter).ComplianceScore= 0.0% whenever any violation exists, 100.0% otherwise.- The score provides no actionable signal about incremental compliance progress.
Suggested Fix
- Thread total dependency counts through the scanning pipeline so
Get-ComplianceReportDatareceives both total scanned dependencies and violations. - Set fields correctly:
TotalDependencies= total scanned,UnpinnedDependencies= violations count,PinnedDependencies= total − violations. - Remove the dead
$_.Severity -ne 'Info'filter — or, if'Info'severity will be introduced later, add a[ValidateSet('High','Medium','Low','Info')]constraint toDependencyViolation.SeverityinSecurityClasses.psm1to make the contract explicit. - Update tests in
Test-DependencyPinning.Tests.ps1to validate partial compliance scores (e.g., 3 violations out of 10 total → 70% compliance).
Affected Files
scripts/security/Test-DependencyPinning.ps1—Get-ComplianceReportDatafunction and upstream callersscripts/security/Modules/SecurityClasses.psm1—DependencyViolation.Severity(missingValidateSet),ComplianceReportclassscripts/tests/security/Test-DependencyPinning.Tests.ps1— needs partial compliance test coveragescripts/tests/security/SecurityClasses.Tests.ps1— existing partial compliance tests onCalculateScore()already pass; ensure alignment
Related
- PR Fix compliance score calculation in Get-ComplianceReportData #928 identified the dead
'Info'filter symptom but proposed hard-coding0.0rather than fixing the underlying denominator problem.