Public/Drift/Test-RecentSecretChange.ps1
|
function Test-RecentSecretChange { [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory)] [ValidatePattern('^[a-zA-Z0-9._-]+$')] [string]$Owner, [Parameter(Mandatory)] [string]$Token, [ValidatePattern('^[a-zA-Z0-9._-]+$')] [string]$Repo, [ValidateRange(1, 720)] [int]$SinceHours = 168, [PSCustomObject[]]$AuditEvents = @() ) $resource = if ($Repo) { "$Owner/$Repo" } else { "org/$Owner" } $results = [System.Collections.Generic.List[PSCustomObject]]::new() $ownerContext = Get-FylgyrOwnerContext -Owner $Owner -Token $Token if ($ownerContext.Type -eq 'User' -and -not $Repo) { $results.Add((Format-FylgyrResult ` -CheckName 'RecentSecretChange' ` -Status 'Info' ` -Severity 'Info' ` -Resource $resource ` -Detail "Owner '$Owner' is a personal account. Organization secret drift is not applicable." ` -Remediation 'No action needed.' ` -Target $resource ` -Mode 'Drift')) return $results.ToArray() } $events = @($AuditEvents) if ($events.Count -eq 0) { try { $events = @(Get-OrgAuditLog -Owner $Owner -Token $Token -SinceHours $SinceHours) } catch { $results.Add((Format-FylgyrResult ` -CheckName 'RecentSecretChange' ` -Status 'Error' ` -Severity 'Medium' ` -Resource $resource ` -Detail "Secret-change drift requires org audit log access: $($_.Exception.Message)" ` -Remediation 'Grant admin:org for audit-log access, then rerun drift mode.' ` -Target $resource ` -Mode 'Drift')) return $results.ToArray() } } $secretEvents = @($events | Where-Object { $_.action -match '^(org|repo)\.secret\.(create|update|delete|remove)$' }) if ($Repo) { $secretEvents = @($secretEvents | Where-Object { $_.repo -eq "$Owner/$Repo" }) } if ($secretEvents.Count -eq 0) { $results.Add((Format-FylgyrResult ` -CheckName 'RecentSecretChange' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $resource ` -Detail "No secret create/update/delete events detected in the last $SinceHours hour(s)." ` -Remediation 'No action needed.' ` -Target $resource ` -Evidence @{ Source = 'audit-log' EventCount = 0 } ` -Mode 'Drift')) return $results.ToArray() } foreach ($secretRecord in $secretEvents) { $results.Add((Format-FylgyrResult ` -CheckName 'RecentSecretChange' ` -Status 'Drift' ` -Severity 'Medium' ` -Resource $resource ` -Detail "Secret metadata drift detected: action '$($secretRecord.action)'." ` -Remediation 'Validate secret lifecycle activity, confirm authorized actor intent, and review related workflow executions for abuse.' ` -AttackMapping @('committed-credentials-exposure', 'github-app-token-theft') ` -Target $resource ` -Evidence @{ Source = 'audit-log' ChangedAt = $secretRecord.created_at ChangedBy = if ($secretRecord.actor) { $secretRecord.actor } else { $null } Action = $secretRecord.action Repo = $secretRecord.repo } ` -Mode 'Drift')) } return $results.ToArray() } |