Public/Get-ComplianceCrosswalk.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Get-ComplianceCrosswalk { <# .SYNOPSIS Maps audit findings to compliance framework requirements. .DESCRIPTION Cross-references audit check IDs against FERPA, COPPA, CIPA, NIST SP 800-171, and state education technology privacy requirements. Returns findings grouped by compliance framework with citations. .PARAMETER Findings Array of audit finding objects. If not provided, reads from latest state. .PARAMETER Framework Filter to a specific compliance framework. If not specified, returns all frameworks. .PARAMETER FailOnly Only include findings with FAIL status (exclude WARN and PASS). .PARAMETER ConfigPath Override config file path. .EXAMPLE Get-ComplianceCrosswalk -Framework FERPA Returns all findings mapped to FERPA requirements. .EXAMPLE Get-ComplianceCrosswalk -FailOnly Returns only failing checks mapped to compliance frameworks. .EXAMPLE $findings = Invoke-Reconnaissance -PassThru; Get-ComplianceCrosswalk -Findings $findings -Framework COPPA Maps specific findings to COPPA requirements. #> [CmdletBinding()] param( [PSCustomObject[]]$Findings, [ValidateSet('FERPA', 'COPPA', 'CIPA', 'NIST-171', 'STATE-EDTECH')] [string]$Framework, [switch]$FailOnly, [Alias('RuntimeConfig')] [string]$ConfigPath ) # Load findings from state if not provided if (-not $Findings -or $Findings.Count -eq 0) { $dataDir = Get-PSGuerrillaDataRoot $findingsFiles = @() if (Test-Path $dataDir) { $findingsFiles = @(Get-ChildItem -Path $dataDir -Filter '*.findings.json' -ErrorAction SilentlyContinue) } if ($findingsFiles.Count -gt 0) { $Findings = @() foreach ($f in $findingsFiles) { try { $data = Get-Content -Path $f.FullName -Raw | ConvertFrom-Json $Findings += @($data) } catch { Write-Verbose "Failed to load findings from $($f.Name): $_" } } } } if (-not $Findings -or $Findings.Count -eq 0) { Write-Warning 'No audit findings available. Run a scan first.' return @() } # Load crosswalk data $crosswalkPath = Join-Path $PSScriptRoot '../Data/ComplianceCrosswalk.json' if (-not (Test-Path $crosswalkPath)) { Write-Warning "ComplianceCrosswalk.json not found at $crosswalkPath" return @() } $crosswalk = Get-Content -Path $crosswalkPath -Raw | ConvertFrom-Json -AsHashtable $results = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($finding in $Findings) { if ($FailOnly -and $finding.Status -ne 'FAIL') { continue } if ($finding.Status -in @('SKIP', 'ERROR')) { continue } $checkId = $finding.CheckId ?? $finding.Id ?? '' $mapping = $crosswalk.mappings.$checkId if (-not $mapping) { continue } if (-not $mapping.frameworks) { continue } $frameworks = $mapping.frameworks if ($Framework) { if (-not $frameworks.$Framework) { continue } $frameworks = @{ $Framework = $frameworks.$Framework } } foreach ($fw in $frameworks.GetEnumerator()) { $results.Add([PSCustomObject]@{ PSTypeName = 'PSGuerrilla.ComplianceMapping' CheckId = $checkId CheckName = $mapping.checkName ?? $finding.Name ?? $checkId Status = $finding.Status Severity = $finding.Severity ?? 'Medium' Framework = $fw.Key FrameworkName = $crosswalk.frameworks.($fw.Key).fullName ?? $fw.Key Requirement = $fw.Value.requirement ?? '' Citation = $fw.Value.citation ?? '' Category = $finding.Category ?? '' RemediationSteps = $finding.RemediationSteps ?? '' }) } } return @($results | Sort-Object -Property Framework, Severity, CheckId) } |