Files
ProxyWarden/scripts/check-runtime-powershell-boundary.ps1
dokril efda8eb98f
CI / Windows baseline (push) Canceled after 0s
Release v2.0.0
2026-09-10 20:59:52 +03:00

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
}