Public/Test-BinaryArtifact.ps1
|
function Test-BinaryArtifact { [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory)] [ValidatePattern('^[a-zA-Z0-9._-]+$')] [string]$Owner, [Parameter(Mandatory)] [ValidatePattern('^[a-zA-Z0-9._-]+$')] [string]$Repo, [Parameter(Mandatory)] [string]$Token ) $target = "$Owner/$Repo" $results = [System.Collections.Generic.List[PSCustomObject]]::new() $resource = $target try { $treeResult = Get-RepoTree -Owner $Owner -Repo $Repo -Token $Token } catch { $msg = $_.Exception.Message if ($msg -match '403') { $results.Add((Format-FylgyrResult ` -CheckName 'BinaryArtifact' ` -Status 'Error' ` -Severity 'Low' ` -Resource $resource ` -Detail 'Insufficient permissions to read repository tree.' ` -Remediation 'Use a fine-grained token with Contents:read permission, or a classic token with repo scope.' ` -Target $target)) return $results.ToArray() } $results.Add((Format-FylgyrResult ` -CheckName 'BinaryArtifact' ` -Status 'Error' ` -Severity 'Low' ` -Resource $resource ` -Detail "Unexpected error reading repository tree: $($_.Exception.Message)" ` -Remediation 'Re-run with a valid token and verify network access to api.github.com.' ` -Target $target)) return $results.ToArray() } if ($treeResult.PSObject.Properties['empty'] -and $treeResult.empty) { $results.Add((Format-FylgyrResult ` -CheckName 'BinaryArtifact' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $resource ` -Detail 'Repository is empty or has no committed files.' ` -Remediation 'No action needed.' ` -Target $target)) return $results.ToArray() } # Handle truncated tree — large repos may exceed the 100,000-entry API limit if ($treeResult.truncated -eq $true) { $results.Add((Format-FylgyrResult ` -CheckName 'BinaryArtifact' ` -Status 'Info' ` -Severity 'Low' ` -Resource $resource ` -Detail 'Repository tree was truncated by the GitHub API (exceeds 100,000 entries). Binary artifact check is incomplete — not all files could be inspected.' ` -Remediation 'Use the GitHub web UI or git CLI to manually audit for committed binaries. Run git ls-files locally and filter for .exe, .dll, .so, .dylib, .bin, .jar, .war, .a, .o, .pyc, or .class extensions.' ` -Target $target)) return $results.ToArray() } $binaryExtensions = @('.exe', '.dll', '.so', '.dylib', '.bin', '.jar', '.war', '.a', '.o', '.pyc', '.class') $treeEntries = if ($treeResult.tree) { $treeResult.tree } else { @() } $binaryFiles = [System.Collections.Generic.List[string]]::new() foreach ($entry in $treeEntries) { if ($entry.type -ne 'blob') { continue } $ext = [System.IO.Path]::GetExtension($entry.path).ToLowerInvariant() if ($binaryExtensions -contains $ext) { $binaryFiles.Add($entry.path) } } if ($binaryFiles.Count -gt 0) { $sample = ($binaryFiles | Select-Object -First 10) -join ', ' $detail = "$($binaryFiles.Count) binary file(s) found in the default branch tree. Committed binaries hide backdoors that source-level review cannot catch — this is the pattern behind the SolarWinds Orion SUNBURST attack, where injected compiled code in build artifacts went undetected for months. Files (first 10): $sample." if ($binaryFiles.Count -gt 10) { $detail = "$($binaryFiles.Count) binary file(s) found in the default branch tree. Committed binaries hide backdoors that source-level review cannot catch — this is the pattern behind the SolarWinds Orion SUNBURST attack, where injected compiled code in build artifacts went undetected for months. First 10 of $($binaryFiles.Count) files: $sample." } $results.Add((Format-FylgyrResult ` -CheckName 'BinaryArtifact' ` -Status 'Fail' ` -Severity 'Low' ` -Resource $resource ` -Detail $detail ` -Remediation 'Remove committed binaries from the repository. Store build artifacts in GitHub Releases or a package registry. Add binary extensions to .gitignore. If vendored binaries are required as test fixtures, document them and verify their integrity with checksums.' ` -AttackMapping @('solarwinds-orion') ` -Target $target)) } else { $results.Add((Format-FylgyrResult ` -CheckName 'BinaryArtifact' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $resource ` -Detail 'No binary files with known risk extensions found in the default branch tree.' ` -Remediation 'No action needed.' ` -Target $target)) } $results.ToArray() } |