Public/Test-ArtifactPoisoning.ps1
|
function Test-ArtifactPoisoning { [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory)] [PSCustomObject[]]$WorkflowFiles ) $results = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($wf in $WorkflowFiles) { $sanitizedLines = @(($wf.Content -split "`n") | Where-Object { $_ -notmatch '^\s*#' }) $sanitizedContent = $sanitizedLines -join "`n" $hasDownloadArtifact = $sanitizedContent -match '(?im)^\s*-\s*uses\s*:\s*actions/download-artifact@' if (-not $hasDownloadArtifact) { $results.Add((Format-FylgyrResult ` -CheckName 'ArtifactPoisoning' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $wf.Path ` -Detail "Workflow '$($wf.Name)' does not download artifacts." ` -Remediation 'No action needed.')) continue } $hasWorkflowRunTrigger = $sanitizedContent -match '(?im)(^|\s)workflow_run(\s|:|$)' $artifactExecPatterns = @( '(?im)\bbash\s+[^`n#]*(artifact|download|dist|out|tmp|\./)' '(?im)\bsh\s+[^`n#]*(artifact|download|dist|out|tmp|\./)' '(?im)\bsource\s+[^`n#]*(artifact|download|dist|out|tmp|\./)' '(?im)\bpython\s+[^`n#]*(artifact|download|dist|out|tmp|\./)' '(?im)\b(pwsh|powershell)\s+[^`n#]*(artifact|download|dist|out|tmp|\./)' '(?im)\./[^`n#]*(artifact|download|dist|out|tmp)' ) $runBlocks = @(Get-RunBlock -Content $sanitizedContent) $hasArtifactExecution = $false foreach ($block in $runBlocks) { foreach ($pattern in $artifactExecPatterns) { if ($block.Content -match $pattern) { $hasArtifactExecution = $true break } } if ($hasArtifactExecution) { break } } if ($hasArtifactExecution) { $severity = if ($hasWorkflowRunTrigger) { 'Critical' } else { 'High' } $detail = "Workflow '$($wf.Name)' downloads artifacts and appears to execute artifact content without integrity verification." if ($hasWorkflowRunTrigger) { $detail += ' It is also triggered by workflow_run, which can turn cross-workflow artifact consumption into an elevated artifact-poisoning path.' } $results.Add((Format-FylgyrResult ` -CheckName 'ArtifactPoisoning' ` -Status 'Fail' ` -Severity $severity ` -Resource $wf.Path ` -Detail $detail ` -Remediation 'Do not execute downloaded artifacts directly. Verify checksums/signatures and isolate untrusted artifacts. For workflow_run pipelines, enforce strict producer trust boundaries and integrity validation before execution.' ` -AttackMapping @('artifact-poisoning-workflow-run'))) continue } $results.Add((Format-FylgyrResult ` -CheckName 'ArtifactPoisoning' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $wf.Path ` -Detail "Workflow '$($wf.Name)' downloads artifacts but no direct artifact-execution pattern was detected." ` -Remediation 'No action needed. Keep integrity validation in place for any future artifact execution.')) } return $results.ToArray() } |