$ErrorActionPreference = "Stop" $installer = Join-Path (Split-Path $PSScriptRoot -Parent) "install.ps1" $tokens = $null $parseErrors = $null [System.Management.Automation.Language.Parser]::ParseFile( $installer, [ref]$tokens, [ref]$parseErrors ) | Out-Null if ($parseErrors.Count -gt 0) { throw "install.ps1 parser errors: $($parseErrors.Message -join '; ')" } $source = Get-Content -LiteralPath $installer -Raw function Assert-SourceContains { param([Parameter(Mandatory)][string]$Pattern, [Parameter(Mandatory)][string]$Message) if ($source -notmatch $Pattern) { throw $Message } } $runtimeMatch = [regex]::Match( $source, '(?s)\$RuntimeFiles\s*=\s*@\((?.*?)\)' ) if (!$runtimeMatch.Success) { throw "Runtime file allowlist was not found." } $runtimeFiles = $runtimeMatch.Groups["files"].Value foreach ($forbidden in @("agent-config.json", "agent-state.json", "logs", "backups")) { if ($runtimeFiles -match [regex]::Escape($forbidden)) { throw "$forbidden must not be a managed deployment target." } } $preflight = $source.IndexOf('# Validate the complete source') $firstTargetChange = $source.IndexOf('New-Item -ItemType Directory') if ($preflight -lt 0 -or $preflight -gt $firstTargetChange) { throw "Source preflight must happen before target changes." } Assert-SourceContains '\$existingTask\s*=\s*Get-MinerOpsScheduledTask' ` "Existing task preservation guard is missing." Assert-SourceContains 'Preserving existing Scheduled Task' ` "Existing task preservation is not explicit." Assert-SourceContains 'ExtraArguments\s*=\s*"-Limit 0 -TimeoutMs 8000"' ` "Fresh enrichment task arguments are missing." Assert-SourceContains 'Stop-MinerOpsScheduledTaskForUpgrade' ` "Bounded main Agent stop behavior is missing." Assert-SourceContains '\$mainTaskWasRunning' ` "Main Agent prior running state is not represented." Assert-SourceContains 'Copy-Item[^\r\n]*\(Join-Path \$Backup \$name\)' ` "Rollback does not restore managed files from backup." Assert-SourceContains 'Unregister-ScheduledTask' ` "Rollback does not remove tasks created by a failed install." Assert-SourceContains 'could not restart restored MinerOpsAgent' ` "Rollback restart failure is not surfaced." Assert-SourceContains 'Assert-MinerOpsInstallerTrustedAncestors' ` "Installer does not validate ancestor namespace protection." foreach ($functionName in @( "Assert-MinerOpsInstallerNoReparsePath", "Assert-MinerOpsInstallerTrustedAncestors" )) { $functionSource = [regex]::Match( $source, "(?s)function $functionName \{.*?\n\}" ).Value if ([string]::IsNullOrWhiteSpace($functionSource)) { throw "$functionName source was not found." } if ($functionSource -notmatch '\$current\s*=\s*\$root') { throw "$functionName does not preserve the filesystem root." } if ($functionSource -notmatch '\$fullPath\.Equals\(\s*\$root') { throw "$functionName does not protect a root-valued full path from trimming." } if ($functionSource -match '\$root\.TrimEnd\(') { throw "$functionName converts a root-qualified path to drive-relative form." } if ($functionSource -match 'GetFullPath\(\$Path\)\.TrimEnd\(') { throw "$functionName trims the filesystem root before identifying it." } if ($functionName -eq "Assert-MinerOpsInstallerTrustedAncestors" -and $functionSource -notmatch '\$parent\.Equals\(\s*\$root') { throw "$functionName does not protect a root-valued parent from trimming." } } Assert-SourceContains 'Open-MinerOpsInstallerDirectoryLock' ` "Installer does not lock an insecure target against replacement." Assert-SourceContains 'minerops-filesystem-trust\.blocked-' ` "Installer directory repair lacks an external durable provenance blocker." Assert-SourceContains 'ApproveFilesystemTrustResetAfterReboot' ` "Installer lacks an explicit post-reboot trust-reset gate." Assert-SourceContains 'DirectoryInfo\(\$staging\)' ` "Installer does not stage a protected new directory before publication." Assert-SourceContains 'Assert-MinerOpsInstallerDirectory' ` "Installer does not read back the final directory security invariant." Assert-SourceContains 'Protect-MinerOpsInstallerExistingConfig' ` "Installer does not contain the existing active-config ACL." Assert-SourceContains 'GetNumberOfLinks' ` "Installer does not reject hard-linked runtime, config, or backup files." Assert-SourceContains 'Assert-MinerOpsInstallerTrustedRuntimeFile' ` "Installer does not verify existing runtime rollback provenance." Assert-SourceContains 'Protect-MinerOpsInstallerRuntimeFile' ` "Installer does not secure and verify published runtime and rollback copies." Assert-SourceContains 'Pre-reset runtime is evidence, never rollback authority' ` "Installer trust reset can adopt pre-reset runtime as rollback authority." Assert-SourceContains 'parent-owned filesystem trust blocker remains' ` "Installer does not explicitly retain the parent-owned trust blocker." Assert-SourceContains 'windows-full-boot-event-v1:' ` "Installer blockers do not use canonical full-kernel-boot records." if ($source -match 'Remove-MinerOpsInstallerTrustBlocker|Remove-Item[^\r\n]*TrustBlocker') { throw "Installer must have no filesystem trust-blocker deletion path." } if ($source -match 'LastBootUpTime') { throw "Installer must not create wall-clock boot-epoch records." } $createStreamFunction = [regex]::Match( $source, '(?s)function New-MinerOpsInstallerProtectedFileCreateNewStream \{.*?\n\}' ).Value foreach ($requiredCreateContract in @( '\[IO\.FileStream\]::new', 'FileMode\]::CreateNew', 'FileSystemRights\]::WriteData', 'FileSystemRights\]::ReadPermissions', 'FileSystemRights\]::Synchronize', 'FileShare\]::None', '\$Acl' )) { if ($createStreamFunction -notmatch $requiredCreateContract) { throw "Installer protected CreateNew stream contract is incomplete." } } if ($createStreamFunction -match 'FullControl|SetAccessControl|Set-Acl') { throw "Installer protected creation requests excess rights or repairs by path." } $trustBlockerFunction = [regex]::Match( $source, '(?s)function New-MinerOpsInstallerTrustBlocker \{.*?\n\}' ).Value $trustCreateIndex = $trustBlockerFunction.IndexOf( 'New-MinerOpsInstallerProtectedFileCreateNewStream' ) $trustVerifyIndex = $trustBlockerFunction.IndexOf( 'Test-MinerOpsInstallerProtectedFileAcl' ) $trustContentIndex = $trustBlockerFunction.IndexOf('$record =') if ( $trustCreateIndex -lt 0 -or $trustVerifyIndex -le $trustCreateIndex -or $trustContentIndex -le $trustVerifyIndex ) { throw "Installer trust blockers must be protected and verified before content." } $configRepairFunction = [regex]::Match( $source, '(?s)function Protect-MinerOpsInstallerExistingConfig \{.*?\n\}' ).Value foreach ($requiredRepairRight in @( 'ReadPermissions', 'ChangePermissions', 'TakeOwnership', 'Synchronize' )) { if ($configRepairFunction -notmatch $requiredRepairRight) { throw "Installer existing-config repair is missing $requiredRepairRight." } } if ($configRepairFunction -match 'Set-Acl|FileAccess') { throw "Installer existing-config repair must remain handle-bound." } $repairFunction = [regex]::Match( $source, '(?s)function Initialize-MinerOpsInstallerDirectory \{.*?\n\}' ).Value $repairIndex = $repairFunction.IndexOf('Set-Acl -LiteralPath $fullPath') $abortIndex = $repairFunction.IndexOf('this invocation will not back up') if ($repairIndex -lt 0 -or $abortIndex -le $repairIndex) { throw "Insecure existing-directory containment must abort before publication." } if ($source -match 'Register-ScheduledTask[^\r\n]*-Force') { throw "Installer must not force-overwrite Scheduled Tasks." } if ($source -match '(?i)agent_secret|api3_password|api3_account') { throw "Installer must not inspect or print credentials." } Write-Host "MinerOps installer source safety tests passed."