param( [string]$AgentRoot = "C:\Wooo\Agent99", [string]$Prefix = "http://+:8787/agent99/sre-alert/", [string]$TokenEnv = "AGENT99_SRE_RELAY_TOKEN", [string[]]$AllowedRemotePrefixes = @("127.", "::1", "192.168.0."), [int]$MaxBodyBytes = 262144, [switch]$RunOnce ) $ErrorActionPreference = "Stop" $EvidenceDir = Join-Path $AgentRoot "evidence" $AlertRoot = Join-Path $AgentRoot "alerts" $IncomingDir = Join-Path $AlertRoot "incoming" $LogDir = Join-Path $AgentRoot "logs" $BinDir = Join-Path $AgentRoot "bin" $SreAlertInboxScript = Join-Path $BinDir "agent99-sre-alert-inbox.ps1" New-Item -ItemType Directory -Force -Path $EvidenceDir, $IncomingDir, $LogDir | Out-Null function Convert-AgentSafeFileToken { param([string]$Value) $safe = [regex]::Replace($Value, "[^A-Za-z0-9_.-]", "_") if (-not $safe) { return "relay-alert" } if ($safe.Length -gt 96) { return $safe.Substring(0, 96) } return $safe } function Get-AgentField { param( [object]$Object, [string]$Name, [object]$Default = $null ) if ($Object -and $Object.PSObject.Properties[$Name]) { return $Object.PSObject.Properties[$Name].Value } return $Default } function Get-AgentEnvValue { param([string]$Name) foreach ($target in @("Process", "User", "Machine")) { $value = [Environment]::GetEnvironmentVariable($Name, $target) if ($value) { return $value } } return "" } function Write-AgentRelayEvent { param([hashtable]$Event) $path = Join-Path $LogDir "agent99-sre-alert-relay.jsonl" $Event["timestamp"] = (Get-Date).ToString("o") ($Event | ConvertTo-Json -Compress -Depth 8) | Add-Content -Encoding UTF8 $path } function Send-AgentRelayResponse { param( [System.Net.HttpListenerContext]$Context, [int]$StatusCode, [hashtable]$Body ) $json = $Body | ConvertTo-Json -Compress -Depth 8 $bytes = [System.Text.Encoding]::UTF8.GetBytes($json) $Context.Response.StatusCode = $StatusCode $Context.Response.ContentType = "application/json; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) $Context.Response.Close() } function Test-AgentRelayRemoteAllowed { param([System.Net.HttpListenerContext]$Context) $remote = [string]$Context.Request.RemoteEndPoint.Address foreach ($prefix in $AllowedRemotePrefixes) { if ($remote.StartsWith($prefix)) { return $true } } return $false } function Start-AgentSreAlertInbox { if (-not (Test-Path $SreAlertInboxScript)) { return [pscustomobject]@{ started = $false; reason = "sre_alert_inbox_script_missing" } } $ps = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe" $stamp = Get-Date -Format "yyyyMMdd-HHmmss" $stdout = Join-Path $LogDir "agent99-sre-alert-relay-trigger-$stamp.out" $stderr = Join-Path $LogDir "agent99-sre-alert-relay-trigger-$stamp.err" $args = @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $SreAlertInboxScript, "-AgentRoot", $AgentRoot, "-RunNow" ) $proc = Start-Process -FilePath $ps -ArgumentList $args -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr return [pscustomobject]@{ started = $true processId = $proc.Id stdout = $stdout stderr = $stderr } } if (-not $Prefix.EndsWith("/")) { $Prefix = "$Prefix/" } $listener = [System.Net.HttpListener]::new() $listener.Prefixes.Add($Prefix) $startupStamp = Get-Date -Format "yyyyMMdd-HHmmss" $startupEvidence = Join-Path $EvidenceDir "agent99-SreAlertRelay-start-$startupStamp.json" @{ ok = $true prefix = $Prefix tokenEnv = $TokenEnv tokenConfigured = [bool](Get-AgentEnvValue $TokenEnv) allowedRemotePrefixes = $AllowedRemotePrefixes maxBodyBytes = $MaxBodyBytes runOnce = [bool]$RunOnce startedAt = (Get-Date).ToString("o") } | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 $startupEvidence try { $listener.Start() Write-AgentRelayEvent @{ event = "relay_started" ok = $true prefix = $Prefix tokenConfigured = [bool](Get-AgentEnvValue $TokenEnv) evidence = $startupEvidence } do { $context = $listener.GetContext() $remote = [string]$context.Request.RemoteEndPoint.Address try { if ($context.Request.HttpMethod -ne "POST") { Send-AgentRelayResponse $context 405 @{ ok = $false; error = "method_not_allowed" } Write-AgentRelayEvent @{ event = "relay_rejected"; ok = $false; remote = $remote; reason = "method_not_allowed" } continue } if (-not (Test-AgentRelayRemoteAllowed $context)) { Send-AgentRelayResponse $context 403 @{ ok = $false; error = "remote_not_allowed" } Write-AgentRelayEvent @{ event = "relay_rejected"; ok = $false; remote = $remote; reason = "remote_not_allowed" } continue } $expectedToken = Get-AgentEnvValue $TokenEnv if ($expectedToken) { $actualToken = [string]$context.Request.Headers["X-Agent99-Relay-Token"] if ($actualToken -ne $expectedToken) { Send-AgentRelayResponse $context 401 @{ ok = $false; error = "invalid_relay_token" } Write-AgentRelayEvent @{ event = "relay_rejected"; ok = $false; remote = $remote; reason = "invalid_relay_token" } continue } } if ($context.Request.ContentLength64 -gt $MaxBodyBytes) { Send-AgentRelayResponse $context 413 @{ ok = $false; error = "payload_too_large" } Write-AgentRelayEvent @{ event = "relay_rejected"; ok = $false; remote = $remote; reason = "payload_too_large" } continue } $reader = [System.IO.StreamReader]::new($context.Request.InputStream, $context.Request.ContentEncoding) $body = $reader.ReadToEnd() if ($body.Length -gt $MaxBodyBytes) { Send-AgentRelayResponse $context 413 @{ ok = $false; error = "payload_too_large" } Write-AgentRelayEvent @{ event = "relay_rejected"; ok = $false; remote = $remote; reason = "payload_too_large_readback" } continue } $alert = $body | ConvertFrom-Json $alertId = [string](Get-AgentField $alert "id" ("relay-" + [guid]::NewGuid().ToString("N"))) $safeId = Convert-AgentSafeFileToken $alertId $fileStamp = Get-Date -Format "yyyyMMdd-HHmmss" $tmpPath = Join-Path $IncomingDir ".$safeId-$fileStamp-$([guid]::NewGuid().ToString("N")).tmp" $finalPath = Join-Path $IncomingDir "$safeId-$fileStamp.json" $alert | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 $tmpPath Move-Item -Force $tmpPath $finalPath $trigger = Start-AgentSreAlertInbox Send-AgentRelayResponse $context 202 @{ ok = $true alertId = $alertId incomingFile = $finalPath inboxTriggered = [bool]$trigger.started inboxProcessId = $trigger.processId } Write-AgentRelayEvent @{ event = "relay_accepted" ok = $true remote = $remote alertId = $alertId incomingFile = $finalPath inboxTriggered = [bool]$trigger.started inboxProcessId = $trigger.processId } } catch { try { Send-AgentRelayResponse $context 500 @{ ok = $false; error = "relay_exception"; errorType = $_.Exception.GetType().Name } } catch { } Write-AgentRelayEvent @{ event = "relay_exception" ok = $false remote = $remote errorType = $_.Exception.GetType().Name error = $_.Exception.Message } } } while (-not $RunOnce) } finally { if ($listener.IsListening) { $listener.Stop() } $listener.Close() }