254 lines
8.7 KiB
PowerShell
254 lines
8.7 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[switch]$CheckOnly
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$RepoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
|
|
$AllowedPowerShellFiles = @(
|
|
"scripts/audit-windows-smoke.ps1",
|
|
"scripts/check-runtime-powershell-boundary.ps1",
|
|
"scripts/prepare-release.ps1",
|
|
"scripts/update-component-bundle.ps1"
|
|
)
|
|
$ExpectedNsisFlags = @(
|
|
"--nsis-uninstall-managed",
|
|
"--nsis-verify-upgrade"
|
|
)
|
|
$IgnoredPathPattern = '^(?:\.git|node_modules|dist|releases|src-tauri/target)(?:/|$)'
|
|
|
|
function Get-RelativeRepoPath {
|
|
param([string]$Path)
|
|
|
|
$rootUri = [Uri]($RepoRoot.TrimEnd("\", "/") + [IO.Path]::DirectorySeparatorChar)
|
|
$pathUri = [Uri][IO.Path]::GetFullPath($Path)
|
|
[Uri]::UnescapeDataString($rootUri.MakeRelativeUri($pathUri).ToString()).Replace("\", "/")
|
|
}
|
|
|
|
function New-Violation {
|
|
param(
|
|
[string]$Rule,
|
|
[string]$Path,
|
|
[string]$Message,
|
|
[int]$Line = 0
|
|
)
|
|
|
|
[ordered]@{
|
|
rule = $Rule
|
|
path = $Path
|
|
line = $Line
|
|
message = $Message
|
|
}
|
|
}
|
|
|
|
function Get-ProductionLines {
|
|
param([string]$Path)
|
|
|
|
$lines = @(Get-Content -LiteralPath $Path)
|
|
for ($index = 0; $index -lt $lines.Count; $index++) {
|
|
if ($lines[$index] -match '^\s*#\s*\[\s*cfg\s*\(\s*test\s*\)\s*\]') {
|
|
if ($index -eq 0) { return @() }
|
|
return @($lines[0..($index - 1)])
|
|
}
|
|
}
|
|
return $lines
|
|
}
|
|
|
|
function Write-Result {
|
|
param(
|
|
[bool]$Success,
|
|
[string]$Message,
|
|
[object[]]$Violations,
|
|
[int]$PowerShellFileCount,
|
|
[int]$ProductionFileCount,
|
|
[string[]]$ObservedNsisFlags
|
|
)
|
|
|
|
[ordered]@{
|
|
success = $Success
|
|
action = "runtime-powershell-boundary.check"
|
|
changed = $false
|
|
message = $Message
|
|
details = [ordered]@{
|
|
allowlistedPowerShellFiles = $AllowedPowerShellFiles
|
|
scannedPowerShellFileCount = $PowerShellFileCount
|
|
scannedProductionFileCount = $ProductionFileCount
|
|
expectedNsisFlags = $ExpectedNsisFlags
|
|
observedNsisFlags = $ObservedNsisFlags
|
|
violations = $Violations
|
|
}
|
|
} | ConvertTo-Json -Depth 8
|
|
}
|
|
|
|
$violations = New-Object System.Collections.Generic.List[object]
|
|
$powerShellFileCount = 0
|
|
$productionFileCount = 0
|
|
$observedNsisFlags = @()
|
|
|
|
try {
|
|
if (-not $CheckOnly) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "check-only-required" `
|
|
-Path "scripts/check-runtime-powershell-boundary.ps1" `
|
|
-Message "Invoke this read-only boundary as -CheckOnly."))
|
|
}
|
|
|
|
$powerShellFiles = @(
|
|
Get-ChildItem -LiteralPath $RepoRoot -Recurse -File |
|
|
Where-Object { $_.Extension -in @(".ps1", ".psm1", ".psd1") } |
|
|
ForEach-Object {
|
|
[ordered]@{
|
|
fullPath = $_.FullName
|
|
relativePath = Get-RelativeRepoPath -Path $_.FullName
|
|
}
|
|
} |
|
|
Where-Object { $_.relativePath -notmatch $IgnoredPathPattern } |
|
|
Sort-Object relativePath
|
|
)
|
|
$powerShellFileCount = $powerShellFiles.Count
|
|
|
|
foreach ($file in $powerShellFiles) {
|
|
if ($file.relativePath -notin $AllowedPowerShellFiles) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "unexpected-powershell-file" `
|
|
-Path $file.relativePath `
|
|
-Message "PowerShell is allowed only for the exact build/release/QA allowlist."))
|
|
}
|
|
}
|
|
foreach ($allowedPath in $AllowedPowerShellFiles) {
|
|
if ($allowedPath -notin $powerShellFiles.relativePath) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "missing-allowlisted-tool" `
|
|
-Path $allowedPath `
|
|
-Message "Required build/release/QA tool is missing."))
|
|
}
|
|
}
|
|
|
|
$forbiddenRuntimeFiles = @(
|
|
"src-tauri/src/elevated_scripts.rs",
|
|
"src-tauri/src/helper.rs",
|
|
"src-tauri/src/powershell.rs",
|
|
"src-tauri/src/proxifyre_scripts.rs",
|
|
"src-tauri/bundled/cleanup/uninstall-managed-components.ps1"
|
|
)
|
|
foreach ($relativePath in $forbiddenRuntimeFiles) {
|
|
if (Test-Path -LiteralPath (Join-Path $RepoRoot $relativePath.Replace("/", "\"))) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "legacy-runtime-file" `
|
|
-Path $relativePath `
|
|
-Message "Legacy runtime PowerShell owner must be deleted after the native cutover."))
|
|
}
|
|
}
|
|
|
|
$tauriConfigPath = Join-Path $RepoRoot "src-tauri\tauri.conf.json"
|
|
if ((Get-Content -LiteralPath $tauriConfigPath -Raw) -match '(?i)bundled[\\/]cleanup') {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "bundled-cleanup-resource" `
|
|
-Path "src-tauri/tauri.conf.json" `
|
|
-Message "The installer must not package the displaced PowerShell cleanup resource."))
|
|
}
|
|
|
|
$productionFiles = @(
|
|
Get-ChildItem -LiteralPath (Join-Path $RepoRoot "src-tauri\src") -Recurse -File -Filter "*.rs"
|
|
Get-ChildItem -LiteralPath (Join-Path $RepoRoot "src-tauri\bundled\installer-hooks") -Recurse -File | Where-Object { $_.Extension -in @(".nsh", ".nsi") }
|
|
)
|
|
$productionFileCount = $productionFiles.Count
|
|
$rules = @(
|
|
[ordered]@{ name = "powershell-process"; pattern = '(?i)(?:command_no_window|Command::new).*\b(?:powershell|pwsh)(?:\.exe)?\b' },
|
|
[ordered]@{ name = "powershell-command-line"; pattern = '(?i)\b(?:powershell|pwsh)(?:\.exe)?\b\s+-[A-Za-z]' },
|
|
[ordered]@{ name = "powershell-policy-bypass"; pattern = '(?i)-ExecutionPolicy\b' },
|
|
[ordered]@{ name = "powershell-script-path"; pattern = '(?i)\.ps1\b' },
|
|
[ordered]@{ name = "powershell-runtime-helper"; pattern = '(?i)\b(?:run|write)_powershell_(?:command|file|script)\b' },
|
|
[ordered]@{ name = "legacy-module-declaration"; pattern = '(?i)\b(?:pub\s+)?mod\s+(?:elevated_scripts|helper|powershell|proxifyre_scripts)\s*;' },
|
|
[ordered]@{ name = "legacy-module-reexport"; pattern = '(?i)\bpub\s+use\s+crate::(?:elevated_scripts|helper|powershell|proxifyre_scripts)\b' }
|
|
)
|
|
|
|
$productionTextParts = New-Object System.Collections.Generic.List[string]
|
|
$rustTextParts = New-Object System.Collections.Generic.List[string]
|
|
foreach ($file in $productionFiles) {
|
|
$relativePath = Get-RelativeRepoPath -Path $file.FullName
|
|
$lines = @(Get-ProductionLines -Path $file.FullName)
|
|
for ($index = 0; $index -lt $lines.Count; $index++) {
|
|
$line = [string]$lines[$index]
|
|
[void]$productionTextParts.Add($line)
|
|
if ($file.Extension -ieq ".rs") {
|
|
[void]$rustTextParts.Add($line)
|
|
}
|
|
foreach ($rule in $rules) {
|
|
if ($line -match $rule.pattern) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule $rule.name `
|
|
-Path $relativePath `
|
|
-Line ($index + 1) `
|
|
-Message "Production code still contains a PowerShell runtime boundary."))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$productionText = $productionTextParts -join "`n"
|
|
$rustText = $rustTextParts -join "`n"
|
|
$observedNsisFlags = @(
|
|
[regex]::Matches($productionText, '--nsis-[a-z0-9-]+', [Text.RegularExpressions.RegexOptions]::IgnoreCase) |
|
|
ForEach-Object { $_.Value.ToLowerInvariant() } |
|
|
Sort-Object -Unique
|
|
)
|
|
foreach ($flag in $ExpectedNsisFlags) {
|
|
if (-not $rustText.Contains($flag)) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "missing-nsis-runtime-mode" `
|
|
-Path "src-tauri/src" `
|
|
-Message "Rust early-mode parser is missing fixed NSIS mode: $flag"))
|
|
}
|
|
}
|
|
foreach ($flag in $observedNsisFlags) {
|
|
if ($flag -notin $ExpectedNsisFlags) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "unexpected-nsis-mode" `
|
|
-Path "src-tauri" `
|
|
-Message "Unexpected reserved NSIS early mode: $flag"))
|
|
}
|
|
}
|
|
|
|
$hookPath = Join-Path $RepoRoot "src-tauri\bundled\installer-hooks\proxywarden-hooks.nsh"
|
|
$hookText = Get-Content -LiteralPath $hookPath -Raw
|
|
foreach ($flag in $ExpectedNsisFlags) {
|
|
if (-not $hookText.Contains($flag)) {
|
|
[void]$violations.Add((New-Violation `
|
|
-Rule "missing-nsis-hook-mode" `
|
|
-Path "src-tauri/bundled/installer-hooks/proxywarden-hooks.nsh" `
|
|
-Message "Installer hook does not call fixed early mode: $flag"))
|
|
}
|
|
}
|
|
|
|
$success = $violations.Count -eq 0
|
|
$message = if ($success) {
|
|
"Runtime PowerShell boundary is clean."
|
|
} else {
|
|
"Runtime PowerShell boundary has $($violations.Count) violation(s)."
|
|
}
|
|
Write-Result `
|
|
-Success $success `
|
|
-Message $message `
|
|
-Violations $violations.ToArray() `
|
|
-PowerShellFileCount $powerShellFileCount `
|
|
-ProductionFileCount $productionFileCount `
|
|
-ObservedNsisFlags $observedNsisFlags
|
|
if (-not $success) { exit 1 }
|
|
} catch {
|
|
$failure = New-Violation `
|
|
-Rule "checker-error" `
|
|
-Path "scripts/check-runtime-powershell-boundary.ps1" `
|
|
-Message $_.Exception.Message
|
|
Write-Result `
|
|
-Success $false `
|
|
-Message "Runtime PowerShell boundary check could not complete." `
|
|
-Violations @($failure) `
|
|
-PowerShellFileCount $powerShellFileCount `
|
|
-ProductionFileCount $productionFileCount `
|
|
-ObservedNsisFlags $observedNsisFlags
|
|
exit 1
|
|
}
|