Files
Cloud-20Engineering/Powershell/Tools/vscodepluginscan.ps1
Jurjen Ladenius d14e068914 - Add copilot usage check script #124960
- Add VSCode Plugin Scan poc script #124961
2025-10-17 15:04:40 +02:00

353 lines
14 KiB
PowerShell
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
Scans VS Code extensions for security vulnerabilities using npm audit and Snyk CLI.
.DESCRIPTION
This script lists installed VS Code extensions, downloads their NPM packages,
and runs security audits using npm audit and/or Snyk CLI to identify vulnerabilities.
.PARAMETER OutputPath
Directory where extension packages will be downloaded. Defaults to ./vscode-extensions-audit
.PARAMETER UseSnyk
Switch to enable Snyk CLI scanning in addition to npm audit
.EXAMPLE
.\vscodepluginscan.ps1
.EXAMPLE
.\vscodepluginscan.ps1 -OutputPath "C:\temp\extensions" -UseSnyk
#>
[CmdletBinding()]
param(
[string]$OutputPath = "./vscode-extensions-audit",
[switch]$UseSnyk
)
# Check if VS Code is installed
if (-not (Get-Command code -ErrorAction SilentlyContinue)) {
Write-Error "VS Code CLI 'code' not found. Please ensure VS Code is installed and added to PATH."
exit 1
}
# Check if npm is installed
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
Write-Error "npm not found. Please ensure Node.js and npm are installed."
exit 1
}
# Check if Snyk is installed when requested
if ($UseSnyk -and -not (Get-Command snyk -ErrorAction SilentlyContinue)) {
Write-Warning "Snyk CLI not found. Install with: npm install -g snyk"
$UseSnyk = $false
}
# Create output directory
if (-not (Test-Path $OutputPath)) {
New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
}
Write-Host "Starting VS Code extension security scan..." -ForegroundColor Green
Write-Host "Output directory: $OutputPath" -ForegroundColor Cyan
# Get list of installed extensions
Write-Host "`nGetting list of installed VS Code extensions..." -ForegroundColor Yellow
$extensions = & code --list-extensions --show-versions
if ($extensions.Count -eq 0) {
Write-Warning "No extensions found."
exit 0
}
Write-Host "Found $($extensions.Count) extensions" -ForegroundColor Green
$auditResults = @()
foreach ($extension in $extensions) {
$extensionParts = $extension -split '@'
$extensionName = $extensionParts[0]
$extensionVersion = $extensionParts[1]
Write-Host "`nProcessing: $extensionName@$extensionVersion" -ForegroundColor Cyan
# Get extension installation path
$vsCodeExtensionsPath = ""
if ($env:USERPROFILE) {
$vsCodeExtensionsPath = Join-Path $env:USERPROFILE ".vscode\extensions"
}
# Find the actual extension directory
$extensionInstallDir = Get-ChildItem -Path $vsCodeExtensionsPath -Directory | Where-Object { $_.Name -like "$extensionName-*" } | Select-Object -First 1
if (-not $extensionInstallDir) {
Write-Warning " Extension directory not found for $extensionName. Skipping."
continue
}
Write-Host " Found extension at: $($extensionInstallDir.FullName)" -ForegroundColor Gray
# Check if extension has package.json with dependencies
$extensionPackageJson = Join-Path $extensionInstallDir.FullName "package.json"
if (-not (Test-Path $extensionPackageJson)) {
Write-Host " No package.json found for $extensionName. Skipping audit." -ForegroundColor Yellow
continue
}
# Create extension-specific directory for audit
$extensionDir = Join-Path $OutputPath $extensionName.Replace('.', '-')
if (-not (Test-Path $extensionDir)) {
New-Item -ItemType Directory -Path $extensionDir -Force | Out-Null
}
# Copy package.json if it doesn't exist or is different version
$targetPackageJson = Join-Path $extensionDir "package.json"
$shouldCopyPackageJson = $true
if (Test-Path $targetPackageJson) {
try {
$sourceContent = Get-Content $extensionPackageJson | ConvertFrom-Json
$targetContent = Get-Content $targetPackageJson | ConvertFrom-Json
if ($sourceContent.version -eq $targetContent.version -and $sourceContent.name -eq $targetContent.name) {
$shouldCopyPackageJson = $false
Write-Host " Package.json already exists with same version. Skipping copy." -ForegroundColor Gray
}
} catch {
# If we can't compare, copy anyway
Write-Host " Could not compare package.json versions. Copying anyway." -ForegroundColor Yellow
}
}
if ($shouldCopyPackageJson) {
Copy-Item $extensionPackageJson $extensionDir -Force
}
# Copy package-lock.json if it exists and package.json was copied
$extensionPackageLock = Join-Path $extensionInstallDir.FullName "package-lock.json"
if ($shouldCopyPackageJson -and (Test-Path $extensionPackageLock)) {
Copy-Item $extensionPackageLock $extensionDir -Force
}
# Copy node_modules if it exists and package.json was copied (some extensions have pre-installed dependencies)
$extensionNodeModules = Join-Path $extensionInstallDir.FullName "node_modules"
if ($shouldCopyPackageJson -and (Test-Path $extensionNodeModules)) {
Write-Host " Copying existing node_modules..." -ForegroundColor Gray
Copy-Item $extensionNodeModules $extensionDir -Recurse -Force
}
Push-Location $extensionDir
try {
# Check if there are any dependencies to audit
$packageContent = Get-Content "package.json" | ConvertFrom-Json
$hasDependencies = $packageContent.dependencies -or $packageContent.devDependencies
if (-not $hasDependencies) {
Write-Host " No dependencies found in $extensionName. Skipping audit." -ForegroundColor Yellow
Pop-Location
continue
}
# Install dependencies if node_modules doesn't exist
if (-not (Test-Path "node_modules")) {
Write-Host " Installing dependencies..." -ForegroundColor Gray
$installOutput = & npm install --production 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Warning " Failed to install dependencies for $extensionName. Attempting audit anyway."
}
}
# Run npm audit
Write-Host " Running npm audit..." -ForegroundColor Gray
$auditOutput = & npm audit --json 2>&1
$auditResult = @{
Extension = $extension
NpmAudit = $auditOutput
SnykAudit = $null
}
# Parse npm audit results
try {
$auditJson = $auditOutput | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($auditJson.vulnerabilities) {
$vulnCount = ($auditJson.vulnerabilities.PSObject.Properties | Measure-Object).Count
if ($vulnCount -gt 0) {
Write-Host " ⚠️ Found $vulnCount vulnerabilities in $extensionName" -ForegroundColor Red
} else {
Write-Host " ✅ No vulnerabilities found in $extensionName" -ForegroundColor Green
}
}
} catch {
Write-Host " Audit completed for $extensionName" -ForegroundColor Blue
}
# Run Snyk if requested and available
if ($UseSnyk) {
Write-Host " Running Snyk scan..." -ForegroundColor Gray
$snykOutput = & snyk test --json 2>&1
$auditResult.SnykAudit = $snykOutput
try {
$snykJson = $snykOutput | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($snykJson.vulnerabilities) {
$snykVulnCount = $snykJson.vulnerabilities.Count
if ($snykVulnCount -gt 0) {
Write-Host " ⚠️ Snyk found $snykVulnCount vulnerabilities in $extensionName" -ForegroundColor Red
} else {
Write-Host " ✅ Snyk found no vulnerabilities in $extensionName" -ForegroundColor Green
}
} else {
Write-Host " ✅ Snyk found no vulnerabilities in $extensionName" -ForegroundColor Green
}
} catch {
Write-Host " Snyk scan completed for $extensionName" -ForegroundColor Blue
}
}
$auditResults += $auditResult
} catch {
Write-Error " Error processing $extensionName`: $_"
} finally {
Pop-Location
}
}
# Generate summary report
Write-Host "`n" + "="*60 -ForegroundColor Green
Write-Host "AUDIT SUMMARY REPORT" -ForegroundColor Green
Write-Host "="*60 -ForegroundColor Green
$reportPath = Join-Path $OutputPath "audit-report.json"
$auditResults | ConvertTo-Json -Depth 5 | Out-File -FilePath $reportPath -Encoding UTF8
# Generate HTML report
$htmlReportPath = Join-Path $OutputPath "audit-report.html"
$htmlContent = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VS Code Extensions Security Audit Report</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 20px; background-color: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; }
h2 { color: #34495e; margin-top: 30px; }
.summary { background-color: #ecf0f1; padding: 15px; border-radius: 5px; margin: 20px 0; }
.extension { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
.extension-header { font-weight: bold; font-size: 1.1em; margin-bottom: 10px; }
.vulnerability { margin: 10px 0; padding: 10px; border-left: 4px solid #e74c3c; background-color: #fdf2f2; }
.no-vulns { color: #27ae60; font-weight: bold; }
.has-vulns { color: #e74c3c; font-weight: bold; }
.audit-source { font-size: 0.9em; color: #7f8c8d; margin-top: 5px; }
.timestamp { color: #95a5a6; font-size: 0.9em; }
.stats { display: flex; gap: 20px; margin: 20px 0; }
.stat-item { background-color: #3498db; color: white; padding: 10px 15px; border-radius: 5px; text-align: center; }
.stat-value { font-size: 1.5em; font-weight: bold; }
.stat-label { font-size: 0.9em; }
</style>
</head>
<body>
<div class="container">
<h1>VS Code Extensions Security Audit Report</h1>
<div class="timestamp">Generated on: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")</div>
<div class="summary">
<h2>Summary</h2>
<div class="stats">
<div class="stat-item">
<div class="stat-value">$($auditResults.Count)</div>
<div class="stat-label">Extensions Scanned</div>
</div>
<div class="stat-item">
<div class="stat-value">$extensionsWithVulns</div>
<div class="stat-label">With Vulnerabilities</div>
</div>
</div>
</div>
<h2>Extension Details</h2>
"@
foreach ($result in $auditResults) {
$extensionName = $result.Extension
$hasVulnerabilities = $false
$vulnerabilityDetails = ""
# Process npm audit results
try {
$npmAudit = $result.NpmAudit | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($npmAudit.vulnerabilities) {
$vulnCount = ($npmAudit.vulnerabilities.PSObject.Properties | Measure-Object).Count
if ($vulnCount -gt 0) {
$hasVulnerabilities = $true
$vulnerabilityDetails += "<div class='audit-source'>npm audit found $vulnCount vulnerabilities:</div>"
foreach ($vulnProperty in $npmAudit.vulnerabilities.PSObject.Properties) {
$vuln = $vulnProperty.Value
$vulnerabilityDetails += "<div class='vulnerability'><strong>$($vulnProperty.Name)</strong> - Severity: $($vuln.severity)</div>"
}
}
}
} catch {
# Continue processing
}
# Process Snyk results if available
if ($result.SnykAudit) {
try {
$snykAudit = $result.SnykAudit | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($snykAudit.vulnerabilities -and $snykAudit.vulnerabilities.Count -gt 0) {
$hasVulnerabilities = $true
$vulnerabilityDetails += "<div class='audit-source'>Snyk found $($snykAudit.vulnerabilities.Count) vulnerabilities:</div>"
foreach ($vuln in $snykAudit.vulnerabilities) {
$vulnerabilityDetails += "<div class='vulnerability'><strong>$($vuln.title)</strong> - Severity: $($vuln.severity)</div>"
}
}
} catch {
# Continue processing
}
}
$statusClass = if ($hasVulnerabilities) { "has-vulns" } else { "no-vulns" }
$statusText = if ($hasVulnerabilities) { "⚠️ Has Vulnerabilities" } else { "✅ No Vulnerabilities" }
$htmlContent += @"
<div class="extension">
<div class="extension-header">$extensionName</div>
<div class="$statusClass">$statusText</div>
$vulnerabilityDetails
</div>
"@
}
$htmlContent += @"
</div>
</body>
</html>
"@
$htmlContent | Out-File -FilePath $htmlReportPath -Encoding UTF8
Write-Host "HTML report saved to: $htmlReportPath" -ForegroundColor Cyan
Write-Host "Extensions scanned: $($auditResults.Count)" -ForegroundColor Cyan
Write-Host "Detailed results saved to: $reportPath" -ForegroundColor Cyan
# Summary of findings
$extensionsWithVulns = 0
foreach ($result in $auditResults) {
try {
$npmAudit = $result.NpmAudit | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($npmAudit.vulnerabilities -and ($npmAudit.vulnerabilities.PSObject.Properties | Measure-Object).Count -gt 0) {
$extensionsWithVulns++
}
} catch {
# Continue processing
}
}
if ($extensionsWithVulns -gt 0) {
Write-Host "⚠️ $extensionsWithVulns extension(s) have vulnerabilities" -ForegroundColor Red
} else {
Write-Host "✅ No vulnerabilities found in scanned extensions" -ForegroundColor Green
}
Write-Host "`nScan completed!" -ForegroundColor Green