Merge main into fix/http-client-timeout

Resolves findings.json conflict — F086 (from #60, merged into main) and
F087 (this branch) both append to the trailing findings array. Kept both
entries, in numeric order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-05-20 18:17:20 -05:00
7 changed files with 338 additions and 11 deletions
+34 -3
View File
@@ -2816,6 +2816,37 @@
"verified_by": "dotnet build + dotnet test (382 passed)"
}
},
{
"id": "F086",
"title": "New-PveVm -DiskSize and New-PveContainer -RootFsSize pass unit suffix verbatim, LVM rejects 'NG'",
"category": "api_contract",
"severity": "high",
"status": "resolved",
"first_detected": "2026-05-20",
"github_issue": 58,
"files": [
"src/PSProxmoxVE/Cmdlets/Vms/NewPveVmCmdlet.cs",
"src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerCmdlet.cs"
],
"description": "Disk and rootfs sizes are interpolated directly into the disk spec as '<storage>:<size>'. The parameter docstring advertises 'e.g. 32G' but on LVM/LVM-thin storages PVE parses the value after the colon as a volume name unless it is a bare integer, failing with 'unable to parse lvm volume name \"32G\"'. File-backed storages (NFS, directory) accept either form, masking the bug in mixed environments.",
"scan_history": [
{
"scan_date": "2026-05-20",
"local_id": null,
"status": "new"
},
{
"scan_date": "2026-05-20",
"local_id": null,
"status": "fixed"
}
],
"resolution": {
"scan_date": "2026-05-20",
"evidence": "Added SizeParser.NormalizeToGibibytes() which strips G/GB/T/TB suffixes, rejects sub-GB units with a clear error, and converts TB overflows to ArgumentException. New-PveVm and New-PveContainer normalize -DiskSize and -RootFsSize before ShouldProcess so typos are caught with -WhatIf, regardless of whether the matching -DiskStorage/-RootFsStorage was supplied.",
"verified_by": "dotnet build + dotnet test (577 passed, 32 SizeParserTests) + Pester (39 passed, new DiskSize/RootFsSize validation contexts)"
}
},
{
"id": "F087",
"title": "HttpClient uses 100s default timeout; Send-PveFile and other long-running calls fail on large payloads",
@@ -2832,7 +2863,7 @@
"src/PSProxmoxVE/Cmdlets/Storage/InvokePveStorageDownloadCmdlet.cs",
"src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs"
],
"description": "PveHttpClient constructs HttpClient without setting Timeout, so .NET's 100s default applies to every request. Send-PveFile, Invoke-PveStorageDownload, and Connect-PveServer expose no way to override it, so multi-GB ISO uploads on a real LAN reliably trip the 100s timeout with TaskCanceledException.",
"description": "PveHttpClient constructs HttpClient without setting Timeout, so .NET's 100s default applies to every request. Send-PveFile, Invoke-PveStorageDownload, and Connect-PveServer expose no way to override it, so multi-GB ISO uploads on a real LAN reliably trip the 100s timeout with TaskCanceledException. PveHttpClient.SendAsync also failed to surface the timeout as a PveApiException, leaking the raw TaskCanceledException to callers.",
"scan_history": [
{
"scan_date": "2026-05-20",
@@ -2847,8 +2878,8 @@
],
"resolution": {
"scan_date": "2026-05-20",
"evidence": "Added PveSession.Timeout (default 100s) and a TimeSpan? override on PveHttpClient. Connect-PveServer exposes -TimeoutSeconds to set the session-default; Send-PveFile and Invoke-PveStorageDownload expose -TimeoutSeconds with a 30-minute implicit default so large uploads/downloads do not trip the 100s HttpClient default. -TimeoutSeconds 0 means Timeout.InfiniteTimeSpan.",
"verified_by": "dotnet build + dotnet test (550 passed, 5 new timeout tests)"
"evidence": "Added PveSession.Timeout (default 100s) and a TimeSpan? override on PveHttpClient. Connect-PveServer exposes -TimeoutSeconds to set the session-default; Send-PveFile and Invoke-PveStorageDownload expose -TimeoutSeconds with a 30-minute implicit default so large uploads/downloads do not trip the 100s HttpClient default. -TimeoutSeconds 0 means Timeout.InfiniteTimeSpan. PveHttpClient.SendAsync now catches the TimeoutException-wrapped TaskCanceledException and rethrows it as PveApiException(RequestTimeout) with the resource path.",
"verified_by": "dotnet build + dotnet test (passed including new SendAsync_TimeoutFires xUnit test) + Pester (-TimeoutSeconds coverage on all three cmdlets)"
}
}
]