-
Notifications
You must be signed in to change notification settings - Fork 16
[WIP] feat(security): add checksum validation infrastructure #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,86 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <# | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .SYNOPSIS | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Downloads and verifies artifacts using SHA256 checksums. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .DESCRIPTION | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Securely downloads files from URLs and verifies their integrity using | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SHA256 checksums before saving or extracting. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PARAMETER Url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| URL to download from. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PARAMETER ExpectedSHA256 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Expected SHA256 checksum of the file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PARAMETER OutputPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Path where the downloaded file will be saved. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PARAMETER Extract | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Extract the archive after verification. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PARAMETER ExtractPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Destination directory for extraction. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .EXAMPLE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Get-VerifiedDownload -Url "https://example.com/tool.tar.gz" -ExpectedSHA256 "abc123..." -OutputPath "./tool.tar.gz" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .EXAMPLE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Get-VerifiedDownload -Url "https://example.com/tool.tar.gz" -ExpectedSHA256 "abc123..." -OutputPath "./tool.tar.gz" -Extract -ExtractPath "./tools" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [CmdletBinding()] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Parameter(Mandatory = $true)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [string]$Url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Parameter(Mandatory = $true)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [string]$ExpectedSHA256, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Parameter(Mandatory = $true)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [string]$OutputPath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Parameter(Mandatory = $false)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [switch]$Extract, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Parameter(Mandatory = $false)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [string]$ExtractPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $ErrorActionPreference = 'Stop' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $tempFile = [System.IO.Path]::GetTempFileName() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Write-Host "Downloading: $Url" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Invoke-WebRequest -Uri $Url -OutFile $tempFile -UseBasicParsing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Write-Host "Verifying SHA256: $ExpectedSHA256" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $actualHash = (Get-FileHash -Path $tempFile -Algorithm SHA256).Hash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($actualHash -ne $ExpectedSHA256.ToUpper()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Write-Error "Checksum verification failed!`nExpected: $ExpectedSHA256`nActual: $actualHash" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($Extract -and $ExtractPath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Write-Host "Extracting to: $ExtractPath" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (-not (Test-Path $ExtractPath)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| New-Item -ItemType Directory -Path $ExtractPath -Force | Out-Null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Expand-Archive -Path $tempFile -DestinationPath $ExtractPath -Force | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+71
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Expand-Archive -Path $tempFile -DestinationPath $ExtractPath -Force | |
| } | |
| $fileExt = [System.IO.Path]::GetExtension($tempFile).ToLowerInvariant() | |
| $fileName = [System.IO.Path]::GetFileName($tempFile).ToLowerInvariant() | |
| if ($fileExt -eq ".zip") { | |
| Expand-Archive -Path $tempFile -DestinationPath $ExtractPath -Force | |
| } | |
| elseif ($fileName -match "\.tar\.gz$" -or $fileExt -eq ".tgz") { | |
| # Use tar for .tar.gz and .tgz | |
| Write-Host "Extracting tar.gz/tgz archive using tar" | |
| $tarCmd = "tar" | |
| $tarArgs = "xf `"$tempFile`" -C `"$ExtractPath`"" | |
| $process = Start-Process -FilePath $tarCmd -ArgumentList $tarArgs -NoNewWindow -Wait -PassThru | |
| if ($process.ExitCode -ne 0) { | |
| Write-Error "Extraction failed for tar.gz/tgz archive." | |
| exit 1 | |
| } | |
| } | |
| elseif ($fileExt -eq ".tar") { | |
| # Use tar for .tar | |
| Write-Host "Extracting tar archive using tar" | |
| $tarCmd = "tar" | |
| $tarArgs = "xf `"$tempFile`" -C `"$ExtractPath`"" | |
| $process = Start-Process -FilePath $tarCmd -ArgumentList $tarArgs -NoNewWindow -Wait -PassThru | |
| if ($process.ExitCode -ne 0) { | |
| Write-Error "Extraction failed for tar archive." | |
| exit 1 | |
| } | |
| } | |
| else { | |
| Write-Error "Unsupported archive format: $fileExt. Only .zip, .tar.gz, .tgz, and .tar are supported." | |
| exit 1 | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -143,7 +143,7 @@ $DependencyPatterns = @{ | |||||||||||||
| RemediationUrl = 'https://registry.npmjs.org/{0}/{1}' | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| 'pip' = @{ | ||||||||||||||
| 'pip' = @{ | ||||||||||||||
| FilePatterns = @('**/requirements*.txt', '**/Pipfile', '**/pyproject.toml', '**/setup.py') | ||||||||||||||
| VersionPatterns = @( | ||||||||||||||
| @{ | ||||||||||||||
|
|
@@ -155,6 +155,13 @@ $DependencyPatterns = @{ | |||||||||||||
| SHAPattern = '^[a-fA-F0-9]{40}$' | ||||||||||||||
| RemediationUrl = 'https://pypi.org/pypi/{0}/{1}/json' | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| 'shell-downloads' = @{ | ||||||||||||||
| FilePatterns = @('*.sh') | ||||||||||||||
| SearchPaths = @('.devcontainer/scripts', 'scripts') | ||||||||||||||
|
Comment on lines
+160
to
+161
|
||||||||||||||
| FilePatterns = @('*.sh') | |
| SearchPaths = @('.devcontainer/scripts', 'scripts') | |
| FilePatterns = @('**/.devcontainer/scripts/*.sh', '**/scripts/*.sh') |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The shell-downloads ecosystem is defined with a ValidationFunc property, but this function is never invoked in the main execution flow. The main script only calls Get-DependencyViolation which uses VersionPatterns to scan files. Since shell-downloads doesn't define VersionPatterns, this ecosystem will not be checked. Either integrate Test-ShellDownloadSecurity into the scanning logic or remove this unused configuration.
| 'shell-downloads' = @{ | |
| FilePatterns = @('*.sh') | |
| SearchPaths = @('.devcontainer/scripts', 'scripts') | |
| ValidationFunc = 'Test-ShellDownloadSecurity' | |
| Description = 'Shell script downloads must include checksum verification' | |
| } |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The download pattern '(curl|wget)\s+.*https?://[^\s]+' will match lines with curl or wget commands but may produce false positives. It doesn't account for:
- Commented-out commands (lines starting with
#) - Commands in strings or heredocs
curl/wgetappearing in URLs or variable names
Consider adding a check to exclude commented lines (e.g., if ($line -match '^\s*#')) and potentially using more precise regex patterns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script uses
exit 1which will terminate the entire PowerShell session when run as a script. This is problematic if the script is dot-sourced or used as a function. Consider usingthrowinstead to allow proper error handling by the caller, or usereturnwith a proper error status if this is intended to be a standalone script.