fix(start.ps1): TryParse HERMES_WEBUI_PORT + exit AFTER try/finally cleanup

Two hardening fixes for start.ps1 — both flagged by Copilot's review on
PR #2806, both touching lines that exist in #2783's scope rather than
#2806's docs change, so split into this focused follow-up PR per the
"keep diffs focused" guidance.

## Fix 1: TryParse on HERMES_WEBUI_PORT

Before:
  $PortFinal = if ($Port) { $Port }
               elseif ($env:HERMES_WEBUI_PORT) { [int]$env:HERMES_WEBUI_PORT }
               else { 8787 }

`[int]$env:HERMES_WEBUI_PORT` throws InvalidCastException with no
actionable context when the env var contains a non-integer value (typo,
accidental shell expansion, etc.). The cast also doesn't range-check —
HERMES_WEBUI_PORT=999999 would pass the cast and then fail at the
network layer with a confusing port-out-of-range error from Python's
socket.bind().

After: explicit TryParse + range guard (1-65535), each failure mode
gets a targeted Write-Error naming the offending value and the remedy:

  HERMES_WEBUI_PORT='abc' is not a valid integer port. Unset the
  variable to use the default (8787), or set it to a number 1-65535.

  HERMES_WEBUI_PORT=99999 is out of TCP-port range. Must be 1-65535.

## Fix 2: exit AFTER try/finally cleanup

Before:
  Push-Location $RepoRoot
  try {
      & $Python $serverPath @args
      exit $LASTEXITCODE
  } finally {
      Pop-Location
  }

`exit` inside the `try` block can prevent the `finally` from running
in some termination paths (notably when the script is dot-sourced or
invoked from an interactive PowerShell session). The result is a
caller stuck at $RepoRoot instead of their original working directory.

After: capture the exit code into a scoped variable, let the finally
run cleanup, exit after the try/finally completes:

  $script:serverExitCode = 0
  Push-Location $RepoRoot
  try {
      & $Python $serverPath @args
      $script:serverExitCode = $LASTEXITCODE
  } finally {
      Pop-Location
  }
  exit $script:serverExitCode

## Smoke test

Verified all three port-error paths against the modified script on a
Windows 11 Pro foundry-side workstation (PowerShell 7.5.4):

  Test A: HERMES_WEBUI_PORT='abc'    -> exit 1 + 'not a valid integer port'  PASS
  Test B: HERMES_WEBUI_PORT='99999'  -> exit 1 + 'out of TCP-port range'     PASS
  Test C: HERMES_WEBUI_PORT='0'      -> exit 1 + 'out of TCP-port range'     PASS

Test environment used HERMES_WEBUI_AGENT_DIR override to bypass agent-
discovery (this branch is based on #2783's 2-path candidate list which
doesn't include the LOCALAPPDATA install path that #2805 adds; the
hardening fix itself is orthogonal to discovery).

Stacks on top of #2783. Once #2783 lands, rebases cleanly onto master.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dustin
2026-05-23 18:00:20 -05:00
parent bba516060f
commit f53b93087f
+28 -2
View File
@@ -119,7 +119,26 @@ if (Test-Path $agentVenvPython) {
# === Resolve bind + state defaults =====================================
$BindHostFinal = if ($BindHost) { $BindHost } elseif ($env:HERMES_WEBUI_HOST) { $env:HERMES_WEBUI_HOST } else { '127.0.0.1' }
$PortFinal = if ($Port) { $Port } elseif ($env:HERMES_WEBUI_PORT) { [int]$env:HERMES_WEBUI_PORT } else { 8787 }
$PortFinal = if ($Port) {
$Port
} elseif ($env:HERMES_WEBUI_PORT) {
# TryParse + range guard on the env var. A plain [int] cast on the
# env var throws InvalidCastException with no actionable context when
# the env var is set to a non-integer (typo, accidental shell
# expansion, etc.) — surface a targeted error message instead.
$parsedPort = 0
if (-not [int]::TryParse($env:HERMES_WEBUI_PORT, [ref]$parsedPort)) {
Write-Error "HERMES_WEBUI_PORT='$($env:HERMES_WEBUI_PORT)' is not a valid integer port. Unset the variable to use the default (8787), or set it to a number 1-65535."
exit 1
}
if ($parsedPort -lt 1 -or $parsedPort -gt 65535) {
Write-Error "HERMES_WEBUI_PORT=$parsedPort is out of TCP-port range. Must be 1-65535."
exit 1
}
$parsedPort
} else {
8787
}
$env:HERMES_WEBUI_HOST = $BindHostFinal
$env:HERMES_WEBUI_PORT = "$PortFinal"
if (-not $env:HERMES_WEBUI_STATE_DIR) {
@@ -147,10 +166,17 @@ if (-not (Test-Path $serverPath)) {
exit 1
}
# Capture exit code, let finally{} run Pop-Location, exit AFTER the try.
# Plain `exit $LASTEXITCODE` inside the try block can prevent the finally
# from running in some termination paths (especially when dot-sourced or
# in interactive sessions), leaving the caller's working directory stuck
# at $RepoRoot.
$script:serverExitCode = 0
Push-Location $RepoRoot
try {
& $Python $serverPath @args
exit $LASTEXITCODE
$script:serverExitCode = $LASTEXITCODE
} finally {
Pop-Location
}
exit $script:serverExitCode