Private/SecretScanner.psm1
|
function Invoke-SecretScanAndClean { param( [Parameter(Mandatory)][string]$FilePath, [Parameter(Mandatory)][string[]]$Patterns, [Parameter(Mandatory)][string]$ReplacementToken ) if (-not (Test-Path -LiteralPath $FilePath)) { return $false } # Basic binary check by extension (can be expanded) $ext = [System.IO.Path]::GetExtension($FilePath).ToLower() $binaryExts = @('.dll', '.exe', '.zip', '.nupkg', '.pdb', '.png', '.jpg', '.jpeg', '.gif', '.pdf') if ($ext -in $binaryExts) { return $false } try { # Read raw content. We don't want to corrupt encoding if possible, but # simple UTF8 is usually safe for code. $content = [System.IO.File]::ReadAllText($FilePath, [System.Text.Encoding]::UTF8) $modified = $false foreach ($pattern in $Patterns) { # Replace using regex match evaluator to only replace the captured group if we want, # but the regexes provided capture the whole secret value. # Actually, to be safe, if the pattern contains a capture group for the secret, # we should only replace the captured group. # E.g. Password=([^\s]+) # If we just do $content -replace $pattern, $ReplacementToken it replaces the whole string 'Password=foo' -> '***REMOVED***' # which might break syntax. # Let's use Regex.Replace with a MatchEvaluator. $regex = [System.Text.RegularExpressions.Regex]::new($pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bOr [System.Text.RegularExpressions.RegexOptions]::Compiled) if ($regex.IsMatch($content)) { $content = $regex.Replace($content, { param($match) if ($match.Groups.Count -gt 1) { # If there is a capture group, we want to keep everything else and only replace the group. # This is slightly complex in PowerShell Regex.Replace. # Let's just replace the captured group's value within the full match. $fullMatch = $match.Value $secretValue = $match.Groups[1].Value return $fullMatch.Replace($secretValue, $ReplacementToken) } else { # No capture group, replace the whole match return $ReplacementToken } }) $modified = $true } } if ($modified) { [System.IO.File]::WriteAllText($FilePath, $content, [System.Text.Encoding]::UTF8) return $true } } catch { # Ignore files we can't read as text } return $false } |