Skip to content

[Robustness] Servy.psm1 — stderr ArrayList capture is unbounded while stdout has a 1 MB cap (asymmetric) #765

@Christophe-Rogiers

Description

@Christophe-Rogiers

Severity: Info
File: src/Servy.CLI/Servy.psm1 around lines 297-306 (error event registration) and 324 (stdout cap)

Description:

The module starts the Servy CLI process and captures stderr via a Register-ObjectEvent handler that appends every error line into a global ArrayList:

New-Variable -Name $errorVarName -Value (New-Object System.Collections.ArrayList) -Scope Global

$errorEvent = Register-ObjectEvent -InputObject $process `
  -EventName "ErrorDataReceived" `
  -Action ([ScriptBlock]::Create(@"
        if (`$EventArgs.Data) {
            [void]`$global:$errorVarName.Add(`$EventArgs.Data)
        }
"@))

Stdout, by contrast, is explicitly bounded a few lines below:

# Define a reasonable cap for CLI output (e.g., 1MB limit)
$maxStdoutChars = 1048576
$stdoutBuilder = New-Object System.Text.StringBuilder
$isTruncated = $false

The asymmetry means a pathological stderr stream — CLI hung in a stack-trace dump loop, or a crash that repeatedly re-emits the same message, or a verbose -Debug flag accidentally left on — grows the stderr ArrayList without limit. This is all in the PowerShell session's process, so the host (often the admin's interactive shell) can be pushed into paging / OOM before the CLI ever returns.

Suggested fix:

Mirror the stdout cap in the stderr handler. Track accumulated character count and flip a truncated flag once the budget is exhausted. Example:

$script:stderrMaxChars = 1048576
$script:stderrBytes = 0

$errorEvent = Register-ObjectEvent -InputObject $process `
  -EventName "ErrorDataReceived" `
  -Action ([ScriptBlock]::Create(@"
        if (`$EventArgs.Data) {
            if (`$script:stderrBytes -lt $script:stderrMaxChars) {
                [void]`$global:$errorVarName.Add(`$EventArgs.Data)
                `$script:stderrBytes += `$EventArgs.Data.Length
            }
        }
"@))

Also worth considering: the capture variables live in $global: scope. If the finally-block cleanup is skipped (Ctrl-C during wait, module removed mid-run), these $global:ServyError_<GUID> variables persist for the life of the PowerShell session. Not a leak in the strict sense — new runs generate new GUIDs — but they accumulate across interactive troubleshooting. A $script:-scoped hashtable keyed by process PID would be cleaner and self-scoped.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions