fix(launcher): harden Campaign LA11 gate evidence
This commit is contained in:
parent
134edabed2
commit
accd01a008
16 changed files with 1820 additions and 210 deletions
109
tools/capture-campaign-la-session-process.ps1
Normal file
109
tools/capture-campaign-la-session-process.ps1
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Captures one launcher child PID by its unique isolated session-config path.
|
||||
|
||||
.DESCRIPTION
|
||||
Writes a sanitized gate-only sidecar. It never reads the session-config
|
||||
contents and records no command line, account, character, or credential.
|
||||
#>
|
||||
[CmdletBinding(DefaultParameterSetName = 'Path')]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, ParameterSetName = 'Path')]
|
||||
[string]$SessionConfigPath,
|
||||
[Parameter(Mandatory = $true, ParameterSetName = 'Directory')]
|
||||
[string]$SessionsDirectory,
|
||||
[Parameter(ParameterSetName = 'Directory')]
|
||||
[DateTimeOffset]$CreatedAfterUtc = [DateTimeOffset]::MinValue,
|
||||
[Parameter(Mandatory = $true)][string]$ReportPath,
|
||||
[ValidateRange(1, 60)][int]$WaitSeconds = 10
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ($PSVersionTable.PSVersion.Major -lt 7) {
|
||||
throw 'Campaign LA PID capture requires PowerShell 7 or newer.'
|
||||
}
|
||||
. (Join-Path $PSScriptRoot 'CampaignLaProcessCorrelation.ps1')
|
||||
|
||||
if ($PSCmdlet.ParameterSetName -eq 'Path') {
|
||||
if (-not [IO.Path]::IsPathFullyQualified($SessionConfigPath)) {
|
||||
throw '-SessionConfigPath must be absolute.'
|
||||
}
|
||||
$SessionConfigPath = [IO.Path]::GetFullPath($SessionConfigPath)
|
||||
if (-not (Test-Path -LiteralPath $SessionConfigPath -PathType Leaf)) {
|
||||
throw "Session config does not exist: $SessionConfigPath"
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (-not [IO.Path]::IsPathFullyQualified($SessionsDirectory)) {
|
||||
throw '-SessionsDirectory must be absolute.'
|
||||
}
|
||||
$SessionsDirectory = [IO.Path]::TrimEndingDirectorySeparator(
|
||||
[IO.Path]::GetFullPath($SessionsDirectory))
|
||||
if (-not (Test-Path -LiteralPath $SessionsDirectory -PathType Container)) {
|
||||
throw "Sessions directory does not exist: $SessionsDirectory"
|
||||
}
|
||||
}
|
||||
if (-not [IO.Path]::IsPathFullyQualified($ReportPath)) {
|
||||
throw '-ReportPath must be absolute.'
|
||||
}
|
||||
$ReportPath = [IO.Path]::GetFullPath($ReportPath)
|
||||
if (Test-Path -LiteralPath $ReportPath) {
|
||||
throw '-ReportPath must be fresh.'
|
||||
}
|
||||
|
||||
$deadline = [DateTime]::UtcNow.AddSeconds($WaitSeconds)
|
||||
do {
|
||||
if ($PSCmdlet.ParameterSetName -eq 'Path') {
|
||||
$correlations = @(Get-CampaignLaSessionProcessCorrelations |
|
||||
Where-Object {
|
||||
$comparison = if ($IsWindows) {
|
||||
[StringComparison]::OrdinalIgnoreCase
|
||||
} else { [StringComparison]::Ordinal }
|
||||
[string]::Equals(
|
||||
$_.SessionConfigPath,
|
||||
$SessionConfigPath,
|
||||
$comparison)
|
||||
})
|
||||
}
|
||||
else {
|
||||
$comparison = if ($IsWindows) {
|
||||
[StringComparison]::OrdinalIgnoreCase
|
||||
} else { [StringComparison]::Ordinal }
|
||||
$prefix = $SessionsDirectory + [IO.Path]::DirectorySeparatorChar
|
||||
$correlations = @(Get-CampaignLaSessionProcessCorrelations |
|
||||
Where-Object {
|
||||
$_.SessionConfigPath.StartsWith($prefix, $comparison) -and
|
||||
[IO.Path]::GetFileName($_.SessionConfigPath) -ceq 'session.json' -and
|
||||
(Test-Path -LiteralPath $_.SessionConfigPath -PathType Leaf) -and
|
||||
(Get-Item -LiteralPath $_.SessionConfigPath).LastWriteTimeUtc -ge
|
||||
$CreatedAfterUtc.UtcDateTime
|
||||
})
|
||||
}
|
||||
if ($correlations.Count -eq 1) { break }
|
||||
if ($correlations.Count -gt 1) {
|
||||
throw "More than one process uses the isolated session config."
|
||||
}
|
||||
Start-Sleep -Milliseconds 100
|
||||
} while ([DateTime]::UtcNow -lt $deadline)
|
||||
if ($correlations.Count -ne 1) {
|
||||
throw 'No live process uses the isolated session config.'
|
||||
}
|
||||
$SessionConfigPath = [IO.Path]::GetFullPath($correlations[0].SessionConfigPath)
|
||||
|
||||
$directory = Split-Path -Parent $ReportPath
|
||||
if (-not [string]::IsNullOrEmpty($directory)) {
|
||||
$null = New-Item -ItemType Directory -Force -Path $directory
|
||||
}
|
||||
$report = [ordered]@{
|
||||
schemaVersion = 1
|
||||
kind = 'campaign-la-session-process-capture'
|
||||
processId = [int]$correlations[0].ProcessId
|
||||
sessionId = [IO.Path]::GetFileName(
|
||||
[IO.Path]::GetDirectoryName($SessionConfigPath))
|
||||
sessionConfigFile = [IO.Path]::GetFileName($SessionConfigPath)
|
||||
capturedUtc = [DateTime]::UtcNow.ToString('O')
|
||||
}
|
||||
$report | ConvertTo-Json -Depth 3 |
|
||||
Set-Content -LiteralPath $ReportPath -Encoding utf8NoBOM
|
||||
Write-Host "Campaign LA process capture: $ReportPath"
|
||||
Loading…
Add table
Add a link
Reference in a new issue