param([Parameter(Mandatory = $true)][string]$PackageRoot) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' $root = [IO.Path]::GetFullPath($PackageRoot) if (-not (Test-Path -LiteralPath $root -PathType Container)) { throw "Package directory not found: $root" } $required = @( 'sense.exe', 'start-sense.bat', 'stop-sense.bat', 'check-sense.bat', 'migrate-sense.bat', 'backup-sense.bat', 'restore-sense.bat', 'initialize-admin.bat', 'README-WINDOWS.md', 'config\sense.env', 'config\sense.env.example', 'config\sense.demo.env', 'config\mediamtx.yml', 'config\db.sql', 'config\pg.sql', 'web\index.html', 'scripts\runtime\sense-common.ps1' ) foreach ($relative in $required) { if (-not (Test-Path -LiteralPath (Join-Path $root $relative))) { throw "Package is missing required path: $relative" } } & (Join-Path $PSScriptRoot 'assert-web-assets.ps1') -WebRoot (Join-Path $root 'web') $forbiddenDirectories = Get-ChildItem -LiteralPath $root -Recurse -Directory | Where-Object { $_.Name -in @('node_modules', '.git', 'dist', '.cache') } if ($forbiddenDirectories) { throw "Package contains forbidden build directory: $($forbiddenDirectories[0].FullName)" } $forbiddenFiles = Get-ChildItem -LiteralPath $root -Recurse -File | Where-Object { $_.Extension -in @('.db', '.sqlite', '.sqlite3', '.dump', '.bak') } if ($forbiddenFiles) { throw "Package contains database or backup data: $($forbiddenFiles[0].FullName)" } $configFiles = @((Join-Path $root 'config\sense.env'), (Join-Path $root 'config\sense.demo.env')) foreach ($configFile in $configFiles) { $content = Get-Content -LiteralPath $configFile -Raw foreach ($secret in @('SENSE_DATABASE_URL', 'SENSE_DEMO_DATABASE_URL', 'SENSE_JWT_SECRET', 'SENSE_BOOTSTRAP_TOKEN', 'SENSE_CREDENTIAL_KEY')) { if ($content -match "(?m)^$secret[ \t]*=[ \t]*[^ \t\r\n]") { throw "Package contains a non-empty secret field: $secret" } } } $textExtensions = @('.md', '.txt', '.env', '.example', '.ps1', '.bat', '.yml', '.yaml', '.json', '.html', '.js', '.css', '.sql') foreach ($file in Get-ChildItem -LiteralPath $root -Recurse -File | Where-Object { $textExtensions -contains $_.Extension.ToLowerInvariant() }) { $content = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction SilentlyContinue if ($content -match '(?i)(admin123|password123|BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY)') { throw "Package contains a forbidden default credential or private key marker: $($file.FullName)" } } Write-Host "Sense package audit passed: $root"