diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2d2e7d2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,61 @@ +# PSProxmoxVE — Claude Code Instructions + +## Project Overview + +C# binary PowerShell module for managing Proxmox VE (PVE) infrastructure. Two projects: +- `src/PSProxmoxVE/` — Cmdlets and module surface (targets netstandard2.0) +- `src/PSProxmoxVE.Core/` — Services, models, HTTP client (targets netstandard2.0) + +Tests: xUnit (`tests/PSProxmoxVE.Core.Tests/`) and Pester 5 (`tests/PSProxmoxVE.Tests/`). + +## Build & Test + +```bash +# Build +dotnet build PSProxmoxVE.sln + +# xUnit tests +dotnet test tests/PSProxmoxVE.Core.Tests/ + +# Pester tests (requires pwsh) +pwsh -Command "Invoke-Pester tests/PSProxmoxVE.Tests/ -Output Detailed" + +# Run all tests via helper +pwsh -File tools/Invoke-Tests.ps1 +``` + +## Key Conventions + +- All cmdlets use `Pve` noun prefix +- All cmdlet classes must be `sealed` +- All cmdlets must have `[OutputType]` attribute +- Destructive cmdlets must set `ConfirmImpact = ConfirmImpact.High` +- VmId parameters: `[ValidateRange(100, 999999999)]`, nullable when optional +- JSON: Newtonsoft.Json only (`[JsonProperty]`), no System.Text.Json attributes +- Task polling: always use `TaskService.WaitForTask`, never inline loops +- Passwords: `SecureString` type, never plain `string` +- URL paths: `Uri.EscapeDataString()` on all dynamic path segments +- No bare `catch {}` blocks — use specific or filtered exceptions +- Verb class constants required (`VerbsCommon.Get`, not `"Get"`) + +## Review System + +This repo uses a structured review system to track findings and prevent regressions. + +### Key files +- `docs/review/findings.json` — stable findings database. IDs are permanent (F001, F002...). + Never renumber. Read this before any coding session to understand open issues. +- `docs/review/REVIEW_REPORT.md` — latest full review report (scan-4, 2026-03-22) +- `DECISIONS.md` — architectural decisions and anti-patterns. **Read this before writing + any new code.** It documents patterns that were deliberately chosen or changed and must + not be reintroduced. + +### Before starting a coding session +1. Read `DECISIONS.md` to understand established patterns +2. Check `docs/review/findings.json` for open findings relevant to the area you're working in +3. Do not introduce patterns listed as anti-patterns in DECISIONS.md + +### Finding ID stability +Finding IDs (F001, F002...) are permanent. A resolved finding is never deleted from +findings.json — it is marked `resolved` with evidence of the fix. If a finding reappears, +it is marked `regressed` and retains its original ID. diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..7ee0671 --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,360 @@ +# Architectural Decisions + +This file documents patterns that were deliberately chosen or changed, anti-patterns that +were explicitly removed, and constraints that must be maintained. **Read this before writing +any new code.** + +--- + +## D001 — Task polling must use TaskService.WaitForTask + +**Status**: Active +**Finding refs**: F032, F033, F036, F058 +**Resolved in scan**: 2026-03-22 (for VM/network cmdlets); container snapshot + storage cmdlets still open + +### Decision +All task-polling loops must use `TaskService.WaitForTask(upid, session, timeout, progress)`. +Never implement inline `while(true)` or `do/while` polling loops in cmdlet files. + +### Rationale +Four VM/network cmdlets (InvokePveNetworkApply, NewPveSnapshot, RestorePveSnapshot, +RemovePveSnapshot) and one guest exec cmdlet had copy-pasted polling loops with no timeout, +causing cmdlets to hang indefinitely if a PVE task stalled. TaskService.WaitForTask has +timeout enforcement, failure detection, and WriteProgress support. + +Five additional cmdlets (3 container snapshot + 2 storage) still have this anti-pattern as +of scan 2026-03-22 (F058). + +### Anti-pattern (do not reintroduce) +```csharp +// NEVER do this in a cmdlet +while (true) +{ + var status = taskService.GetTask(upid, session); + if (status.IsFinished) break; + Thread.Sleep(1000); +} +``` + +### Correct pattern +```csharp +// Always use this +TaskService.WaitForTask(upid, session, TimeoutSeconds, this); +``` + +--- + +## D002 — Password parameters must use SecureString + +**Status**: Active +**Finding refs**: F051 +**Resolved in scan**: 2026-03-22 + +### Decision +All cmdlet parameters that accept passwords must use `SecureString` type with +`Marshal.SecureStringToGlobalAllocUnicode` + `ZeroFreeGlobalAllocUnicode` in a +try/finally block for extraction. + +### Rationale +Set-PveVmGuestPassword originally accepted a plain `string` password parameter, leaving +the credential in managed memory indefinitely. SecureString minimizes the window of +exposure and is consistent with Connect-PveServer's PSCredential handling. + +### Anti-pattern (do not reintroduce) +```csharp +// NEVER accept passwords as plain strings +[Parameter(Mandatory = true)] +public string Password { get; set; } +``` + +### Correct pattern +```csharp +[Parameter(Mandatory = true)] +public SecureString Password { get; set; } + +// In ProcessRecord: +IntPtr ptr = IntPtr.Zero; +try +{ + ptr = Marshal.SecureStringToGlobalAllocUnicode(Password); + string plainText = Marshal.PtrToStringUni(ptr); + // Use plainText for API call +} +finally +{ + if (ptr != IntPtr.Zero) + Marshal.ZeroFreeGlobalAllocUnicode(ptr); +} +``` + +--- + +## D003 — URL encoding required for all path parameters + +**Status**: Active +**Finding refs**: F050 +**Resolved in scan**: 2026-03-22 + +### Decision +All user-supplied or dynamic values interpolated into API URL paths must be wrapped in +`Uri.EscapeDataString()`. This applies to all service classes. + +### Rationale +Snapshot names, node names, user IDs, and other identifiers could theoretically contain +characters that break URL path segments. While most values come from validated sources, +defense-in-depth requires consistent encoding. Applied across all 14 service classes. + +### Anti-pattern (do not reintroduce) +```csharp +// NEVER interpolate raw strings into URL paths +var resource = $"nodes/{node}/qemu/{vmid}/snapshot/{snapshotName}"; +``` + +### Correct pattern +```csharp +var resource = $"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapshotName)}"; +``` + +--- + +## D004 — No bare catch blocks + +**Status**: Active +**Finding refs**: F039 +**Resolved in scan**: 2026-03-22 + +### Decision +No bare `catch { }` or `catch (Exception) { }` blocks. All catch blocks must either: +1. Use a specific exception type (`catch (PveApiException ex)`), or +2. Use a filtered catch with `when` clause that excludes fatal exceptions + (`catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException)`) + +### Rationale +Bare catches in PveHttpClient, PveCmdletBase, VmService, ContainerService, and +GetPveVmCmdlet silently swallowed errors, making debugging impossible. Replacing with +filtered or specific catches preserves error visibility while still handling expected +transient failures. + +### Anti-pattern (do not reintroduce) +```csharp +// NEVER use bare catches +try { ... } +catch { } + +// NEVER catch all exceptions unfiltered +try { ... } +catch (Exception) { /* ignore */ } +``` + +### Correct pattern +```csharp +// Catch specific exceptions +try { ... } +catch (PveApiException ex) { WriteWarning(ex.Message); } + +// Or use filtered catch for status polling +try { ... } +catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) +{ + WriteVerbose($"Status poll failed: {ex.Message}"); +} +``` + +--- + +## D005 — OutputType required on all cmdlets + +**Status**: Active +**Finding refs**: F037 +**Resolved in scan**: 2026-03-22 + +### Decision +Every cmdlet must have an `[OutputType(typeof(...))]` attribute declaring its return type. + +### Rationale +~54 cmdlets were missing OutputType, degrading IntelliSense, pipeline type inference, and +`Get-Command -OutputType` queries. All 169 cmdlets now have the attribute. + +### Correct pattern +```csharp +[Cmdlet(VerbsCommon.Get, "PveVm")] +[OutputType(typeof(VmInfo))] +public sealed class GetPveVmCmdlet : PveCmdletBase +``` + +--- + +## D006 — ConfirmImpact.High required for destructive operations + +**Status**: Active +**Finding refs**: F011, F034, F042, F043, F062, F063 +**Resolved in scan**: 2026-03-22 (for VM cmdlets); container Restart/Suspend still open + +### Decision +All cmdlets that perform destructive or disruptive operations must set +`ConfirmImpact = ConfirmImpact.High` in the `[Cmdlet]` attribute. This includes: +- All `Remove-*` cmdlets +- All `Stop-*` cmdlets +- All `Reset-*` cmdlets +- All `Restart-*` cmdlets +- All `Suspend-*` cmdlets +- `Restore-PveSnapshot` and `Restore-PveContainerSnapshot` +- `New-PveTemplate` (irreversible conversion) + +### Rationale +Stop-PveVm, Reset-PveVm, Suspend-PveVm, Restart-PveVm, and Remove-PveRole were missing +ConfirmImpact.High, meaning users could accidentally perform disruptive operations without +being prompted. Container counterparts (Restart/Suspend) remain inconsistent as of F062/F063. + +### Correct pattern +```csharp +[Cmdlet(VerbsLifecycle.Stop, "PveVm", SupportsShouldProcess = true, + ConfirmImpact = ConfirmImpact.High)] +``` + +--- + +## D007 — All cmdlet classes must be sealed + +**Status**: Active +**Finding refs**: F041 +**Resolved in scan**: 2026-03-22 + +### Decision +All cmdlet classes must be declared `sealed`. Cmdlets are not designed for inheritance +and sealing prevents unintended extension. + +### Rationale +~95 cmdlets were not sealed. Sealing all 169 cmdlets makes the design intent explicit +and enables potential JIT optimizations. + +### Correct pattern +```csharp +public sealed class GetPveVmCmdlet : PveCmdletBase +``` + +--- + +## D008 — JSON serialization: Newtonsoft.Json only + +**Status**: Active +**Finding refs**: F044 +**Resolved in scan**: 2026-03-22 + +### Decision +Use only `Newtonsoft.Json` (`[JsonProperty]`) for JSON serialization attributes on model +classes. Do not add `System.Text.Json` (`[JsonPropertyName]`) attributes. + +### Rationale +The module uses Newtonsoft.Json for all API response deserialization. Having both +`[JsonProperty]` and `[JsonPropertyName]` attributes was redundant and confusing — +System.Text.Json is not used at runtime. All `[JsonPropertyName]` attributes were removed. + +### Anti-pattern (do not reintroduce) +```csharp +// NEVER add System.Text.Json attributes alongside Newtonsoft +[JsonProperty("status")] +[JsonPropertyName("status")] // Don't add this +public string Status { get; set; } +``` + +### Correct pattern +```csharp +[JsonProperty("status")] +public string Status { get; set; } +``` + +--- + +## D009 — Framework targeting: netstandard2.0 for publishable, net10.0+net48 for tests + +**Status**: Active +**Finding refs**: F047, F064 +**Status note**: net9.0 → net10.0 migration still pending + +### Decision +- Publishable projects (`PSProxmoxVE`, `PSProxmoxVE.Core`): Target `netstandard2.0` for + maximum compatibility (PS 5.1 Desktop + PS 7.x Core). +- Test projects: Target `net10.0` (LTS) and `net48` (Windows PowerShell 5.1 validation). + +### Rationale +.NET 9.0 reached EOL in May 2025. The test projects should use the current LTS release +(net10.0). The publishable module must remain on netstandard2.0 to support both Desktop +and Core editions. + +--- + +## D010 — VmId parameters: nullable int with ValidateRange + +**Status**: Active +**Finding refs**: F012, F038 +**Resolved in scan**: 2026-03-21 (ValidateRange), 2026-03-22 (nullable) + +### Decision +VmId parameters must: +1. Use `int?` (nullable) when the parameter is optional (e.g., firewall cmdlets that + operate at cluster, node, or VM level) +2. Include `[ValidateRange(100, 999999999)]` to match PVE's VMID constraints +3. Use `int` (non-nullable) only when VmId is mandatory + +### Rationale +PVE requires VMIDs in range 100-999999999. Without ValidateRange, invalid IDs reach the +API and return confusing errors. Using non-nullable int with default 0 for optional VmId +made it impossible to distinguish "not specified" from "VM 0" in firewall cmdlets. + +### Correct pattern +```csharp +// Mandatory VmId +[Parameter(Mandatory = true)] +[ValidateRange(100, 999999999)] +public int VmId { get; set; } + +// Optional VmId (e.g., firewall cmdlets) +[Parameter()] +[ValidateRange(100, 999999999)] +public int? VmId { get; set; } +``` + +--- + +## D011 — Verb class constants required for cmdlet attributes + +**Status**: Active +**Finding refs**: F009 +**Resolved in scan**: 2026-03-21 + +### Decision +All `[Cmdlet]` attributes must use verb class constants (`VerbsCommon.Get`, +`VerbsLifecycle.Start`, etc.) instead of hardcoded string literals. + +### Rationale +Reset-PveVm used `[Cmdlet("Reset", ...)]` instead of `VerbsCommon.Reset`. While "Reset" +is an approved verb, using the constant ensures compile-time verification and consistency +with all other cmdlets. + +### Anti-pattern (do not reintroduce) +```csharp +[Cmdlet("Reset", "PveVm")] // Don't use string literals +``` + +### Correct pattern +```csharp +[Cmdlet(VerbsCommon.Reset, "PveVm")] +``` + +--- + +## D012 — Magic strings: extract to named constants + +**Status**: Active +**Finding refs**: F049 +**Resolved in scan**: 2026-03-22 + +### Decision +Frequently used string literals (auth header names, token prefixes, etc.) should be +extracted to `const string` fields for maintainability. + +### Rationale +Auth header names (`PVEAPIToken=`, `CSRFPreventionToken`) were inline string literals +used in multiple places. Extracting to `const string ApiTokenPrefix` and +`CsrfHeaderName` fields improves maintainability and reduces typo risk. diff --git a/REVIEW_REPORT.md b/REVIEW_REPORT.md index f714461..c577f61 100644 --- a/REVIEW_REPORT.md +++ b/REVIEW_REPORT.md @@ -1,8 +1,12 @@ # PSProxmoxVE Module Review Report ``` -Scan date: 2026-03-21 -Prior report date: 2026-03-19 +Scan date: 2026-03-22 +Prior report date: 2026-03-21 +PVE API spec date: 2026-03-21T15:04:50.641Z +PVE API spec SHA256: 4af79be30166209a4714b771f65e1e9540c5b738f414ff30c98454402e29d030 +PVE version hint: (not set in spec) +Total API endpoints: 646 ``` **Reviewer:** Claude Code (Opus 4.6) @@ -13,18 +17,20 @@ Prior report date: 2026-03-19 ## Executive Summary -**Delta since last scan: 22 fixed | 2 new findings | 0 regressed | 4 still open** +**Delta since last scan: 16 fixed | 5 new findings | 0 regressed | 9 still open** -1. **Significant progress since prior scan** — 22 of 28 findings from the initial report have been resolved, including all Critical items. -2. **83 cmdlets** (up from 66) covering VMs, containers (with snapshots and migration), storage, networking, SDN (zones, VNets, subnets), users/roles/permissions, templates, cloud-init, OVA/disk import, and tasks. -3. **All prior Critical items resolved**: publish workflow added, README updated with `Install-Module`, ReleaseNotes in manifest. -4. **Community files complete**: CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, .gitattributes, issue/PR templates all added. -5. **Code quality improvements**: HelpMessage on all parameters, WriteVerbose throughout, ValidateRange on VmId, ConfirmImpact.High on Stop/Reset-PveVm, Reset verb constant fixed. -6. **MAML help file** present (PSProxmoxVE.dll-Help.xml) with 75 cmdlet documentation files in docs/cmdlets/. -7. **Remaining gaps**: No IconUri in manifest, Remove-PveRole missing ConfirmImpact.High, firewall/backup/pool cmdlets not yet implemented. -8. **Excellent test coverage**: 100% Pester unit tests, 84% integration test coverage, xUnit model tests with PVE 8 & 9 fixtures. -9. **Security posture is clean**: No hardcoded secrets, HTTPS enforced, SkipCertificateCheck emits warning, all dependencies pinned. -10. **Module is near PSGallery-ready** — no blocking issues remain. +**API drift: 194 endpoints with version_changes | 466 PVE endpoints unimplemented (72.1% uncovered) | 42 🆕 PVE 9.0 endpoints** + +1. **169 cmdlets** (up from 148) — all fully exported, sealed, with OutputType attributes. New: 21 additional cmdlets since prior scan. +2. **14 prior findings resolved**: All 4 infinite-loop WaitForTask methods (CQ1–CQ4) now use `TaskService.WaitForTask`. Guest exec timeout added (CQ5). OutputType on all cmdlets (CQ9). Firewall VmId nullable (CQ10). Bare catches replaced with filtered exceptions (CQ11–CQ14). RemovePveRole/SuspendPveVm/RestartPveVm ConfirmImpact fixed (CQ17/CQ19/CQ20). Cmdlets sealed (CQ18). Dual JSON attrs removed (CQ21). Magic strings extracted to constants (CQ22). Guest password now SecureString (S1). Debug script secrets redacted (S2). +3. **Remaining Critical**: 5 cmdlets still have `while(true)` task-polling loops with no timeout — 3 container snapshot cmdlets plus `InvokePveStorageDownloadCmdlet` and `SendPveFileCmdlet`. +4. **100% Pester unit test coverage** maintained across all 169 cmdlets. Integration test coverage is ~62% (105 of 169). +5. **27.9% API endpoint coverage** (180 of 646 endpoints). Major uncovered areas unchanged: Ceph (40), HA (21), cluster config (10), disks (18), ACME (15), certificates (8). +6. **Security posture solid**: All prior security findings resolved (S1–S3). URL encoding now consistent across all services. HTTPS enforced, SecureString used consistently. +7. **PSGallery-ready**: No publication blockers. IconUri still missing (cosmetic). Manifest CmdletsToExport fully enumerates all 169 cmdlets. +8. **net9.0 target is EOL** (May 2025) — should upgrade to net10.0. +9. **Pool/CephPool parameter conflict** in New-PveStorage resolved: runtime validation now throws if both specified. +10. **42 new PVE 9.0 endpoints** available (HA rules, SDN fabrics, bulk actions, OCI registry). None yet implemented. --- @@ -44,7 +50,6 @@ PSProxmoxVE/ ├── .editorconfig ├── .gitignore ├── .gitattributes -├── CLAUDE.md ├── REVIEW_REPORT.md ├── .github/ │ ├── ISSUE_TEMPLATE/ @@ -68,46 +73,57 @@ PSProxmoxVE/ │ │ ├── ModuleState.cs │ │ └── Cmdlets/ │ │ ├── PveCmdletBase.cs -│ │ ├── CloudInit/ (3 cmdlets) -│ │ ├── Connection/ (3 cmdlets) -│ │ ├── Containers/ (14 cmdlets) -│ │ ├── Network/ (14 cmdlets) -│ │ ├── Nodes/ (2 cmdlets) -│ │ ├── Snapshots/ (4 cmdlets) -│ │ ├── Storage/ (6 cmdlets) -│ │ ├── Tasks/ (2 cmdlets) -│ │ ├── Templates/ (4 cmdlets) -│ │ ├── Users/ (12 cmdlets) -│ │ └── Vms/ (19 cmdlets) +│ │ ├── Backup/ (6 cmdlets) +│ │ ├── CloudInit/ (3 cmdlets) +│ │ ├── Cluster/ (1 cmdlet) +│ │ ├── Connection/ (3 cmdlets) +│ │ ├── Containers/ (20 cmdlets) +│ │ ├── Firewall/ (21 cmdlets) +│ │ ├── Network/ (5 + 22 SDN cmdlets) +│ │ ├── Nodes/ (8 cmdlets) +│ │ ├── Pools/ (4 cmdlets) +│ │ ├── Snapshots/ (4 cmdlets) +│ │ ├── Storage/ (11 cmdlets) +│ │ ├── Tasks/ (4 cmdlets) +│ │ ├── Templates/ (4 cmdlets) +│ │ ├── Users/ (23 cmdlets) +│ │ └── Vms/ (30 cmdlets) │ └── PSProxmoxVE.Core/ │ ├── PSProxmoxVE.Core.csproj │ ├── Authentication/ (PveAuthenticator, PveAuthMode, PveSession, PveVersion) │ ├── Client/ (PveHttpClient) │ ├── Exceptions/ (7 exception types) -│ ├── Models/ (25 model classes) -│ └── Services/ (11 service classes) +│ ├── Models/ (25+ model classes across 9 areas) +│ └── Services/ (14 service classes) ├── tests/ │ ├── PSProxmoxVE.Core.Tests/ (xUnit) │ │ ├── Authentication/ (3 test files) -│ │ ├── Fixtures/ (23 JSON fixtures) -│ │ ├── Models/ (10 test files) +│ │ ├── Fixtures/ (28 JSON fixtures) +│ │ ├── Models/ (12 test files) │ │ └── TestHelper.cs │ ├── PSProxmoxVE.Tests/ (Pester 5) │ │ ├── _TestHelper.ps1 -│ │ ├── CloudInit/ +│ │ ├── Backup/ (2 test files) +│ │ ├── CloudInit/ (1 test file) +│ │ ├── Cluster/ (1 test file) │ │ ├── Connection/ (3 test files) -│ │ ├── Containers/ (4 test files) -│ │ ├── Network/ (3 test files) -│ │ ├── Nodes/ (1 test file) +│ │ ├── Containers/ (5 test files) +│ │ ├── Firewall/ (5 test files) +│ │ ├── Network/ (5 test files) +│ │ ├── Nodes/ (2 test files) +│ │ ├── Pools/ (1 test file) │ │ ├── Snapshots/ (1 test file) -│ │ ├── Storage/ (3 test files) -│ │ ├── Tasks/ (1 test file) +│ │ ├── Storage/ (4 test files) +│ │ ├── Tasks/ (2 test files) │ │ ├── Templates/ (1 test file) -│ │ ├── Users/ (3 test files) -│ │ ├── Vms/ (9 test files) +│ │ ├── Users/ (4 test files) +│ │ ├── Vms/ (12 test files) │ │ └── Integration/ (1 comprehensive test file) │ └── infrastructure/ (Terraform, Dockerfile, scripts for CI) +├── publish/ (netstandard2.0 build output) +├── debug/ (debug/analysis scripts) └── tools/ + └── Invoke-Tests.ps1 ``` ### Project Layout @@ -140,48 +156,57 @@ PSProxmoxVE/ | Solution file (`.sln`) | Yes | Open | | Module manifest (`.psd1`) | Yes | Open | | Format definitions (`.format.ps1xml`) | Yes | Open | -| MAML help (`.dll-Help.xml`) | Yes | New | +| MAML help (`.dll-Help.xml`) | Yes | Open | | `.editorconfig` | Yes | Open | | `.gitignore` | Yes | Open | -| `.gitattributes` | Yes | Fixed | +| `.gitattributes` | Yes | Open | | `LICENSE` (MIT) | Yes | Open | | `CHANGELOG.md` | Yes | Open | | `README.md` | Yes | Open | -| `CONTRIBUTING.md` | Yes | Fixed | -| `CODE_OF_CONDUCT.md` | Yes | Fixed | -| `SECURITY.md` | Yes | Fixed | -| `.github/ISSUE_TEMPLATE/` | Yes | Fixed | -| `.github/pull_request_template.md` | Yes | Fixed | -| PSGallery publish workflow | Yes | Fixed | -| `docs/PVE_API_COVERAGE.md` | Yes | New | -| `docs/cmdlets/` (75 files) | Yes | New | +| `CONTRIBUTING.md` | Yes | Open | +| `CODE_OF_CONDUCT.md` | Yes | Open | +| `SECURITY.md` | Yes | Open | +| `.github/ISSUE_TEMPLATE/` | Yes | Open | +| `.github/pull_request_template.md` | Yes | Open | +| PSGallery publish workflow | Yes | Open | +| `docs/PVE_API_COVERAGE.md` | Yes | Open | +| `docs/cmdlets/` (75 files) | Yes | Open | ### What's Missing - [ ] `IconUri` in manifest PSData (cosmetic) +- [ ] `.github/ISSUE_TEMPLATE/config.yml` (controls blank issue creation) +- [ ] `CODEOWNERS` file (low priority for single-maintainer) --- ## Phase 2 — PVE API Coverage Audit -> **Note:** Coverage assessment based on source code analysis and the project's own `docs/PVE_API_COVERAGE.md`. +> **Source of truth**: `~/Source/pve_api/pve-api.json` (646 endpoints). Spec generated 2026-03-21. +> **Latest tracked PVE version**: 9.0 (42 new endpoints, 56 changed endpoints) -### Implemented Cmdlets by Category (83 total) +### Implemented Cmdlets (169 total) -#### Connection (3 cmdlets) +#### Connection (3) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Connect-PveServer` | `/access/ticket` | POST | Open | -| `Disconnect-PveServer` | `/access/ticket` | DELETE | Open | +| `Disconnect-PveServer` | (session teardown) | — | Open | | `Test-PveConnection` | `/version` | GET | Open | -#### Nodes (2 cmdlets) +#### Nodes (8) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveNode` | `/nodes` | GET | Open | | `Get-PveNodeStatus` | `/nodes/{node}/status` | GET | Open | +| `Get-PveNodeConfig` | `/nodes/{node}/config` | GET | Open | +| `Set-PveNodeConfig` | `/nodes/{node}/config` | PUT | Open | +| `Get-PveNodeDns` | `/nodes/{node}/dns` | GET | Open | +| `Set-PveNodeDns` | `/nodes/{node}/dns` | PUT | Open | +| `Start-PveNodeVms` | `/nodes/{node}/startall` | POST | Open | +| `Stop-PveNodeVms` | `/nodes/{node}/stopall` | POST | Open | -#### VMs / QEMU (19 cmdlets) +#### VMs / QEMU (19) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveVm` | `/nodes/{node}/qemu` | GET | Open | @@ -198,13 +223,26 @@ PSProxmoxVE/ | `Get-PveVmConfig` | `/nodes/{node}/qemu/{vmid}/config` | GET | Open | | `Set-PveVmConfig` | `/nodes/{node}/qemu/{vmid}/config` | PUT | Open | | `Resize-PveVmDisk` | `/nodes/{node}/qemu/{vmid}/resize` | PUT | Open | -| `Import-PveVmDisk` | `/nodes/{node}/storage/{storage}/upload` | POST | New | -| `Import-PveOva` | Multiple (upload + config + import) | POST | New | +| `Import-PveVmDisk` | `/nodes/{node}/storage/{storage}/upload` | POST | Open | +| `Import-PveOva` | Multiple (upload + config + import) | POST | Open | +| `Move-PveVmDisk` | `/nodes/{node}/qemu/{vmid}/move_disk` | POST | Open | +| `Remove-PveVmDisk` | `/nodes/{node}/qemu/{vmid}/unlink` | PUT | Open | +| `New-PveVmFromTemplate` | `/nodes/{node}/qemu/{vmid}/clone` | POST | Open | + +#### Guest Agent (9) +| Cmdlet | API Endpoint | Method | Prior Status | +|---|---|---|---| | `Test-PveVmGuestAgent` | `/nodes/{node}/qemu/{vmid}/agent/ping` | POST | Open | | `Get-PveVmGuestNetwork` | `/nodes/{node}/qemu/{vmid}/agent/network-get-interfaces` | GET | Open | -| `Invoke-PveVmGuestExec` | `/nodes/{node}/qemu/{vmid}/agent/exec` | POST | Open | +| `Invoke-PveVmGuestExec` | `/nodes/{node}/qemu/{vmid}/agent/exec` + `.../exec-status` | POST/GET | Open | +| `Get-PveVmGuestOsInfo` | `/nodes/{node}/qemu/{vmid}/agent/get-osinfo` | GET | Open | +| `Get-PveVmGuestFsInfo` | `/nodes/{node}/qemu/{vmid}/agent/get-fsinfo` | GET | Open | +| `Read-PveVmGuestFile` | `/nodes/{node}/qemu/{vmid}/agent/file-read` | GET | Open | +| `Write-PveVmGuestFile` | `/nodes/{node}/qemu/{vmid}/agent/file-write` | POST | Open | +| `Set-PveVmGuestPassword` | `/nodes/{node}/qemu/{vmid}/agent/set-user-password` | POST | Open | +| `Invoke-PveVmGuestFsTrim` | `/nodes/{node}/qemu/{vmid}/agent/fstrim` | POST | Open | -#### Containers / LXC (10 cmdlets) +#### Containers / LXC (20) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveContainer` | `/nodes/{node}/lxc` | GET | Open | @@ -214,19 +252,21 @@ PSProxmoxVE/ | `Stop-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/stop` | POST | Open | | `Restart-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/shutdown` | POST | Open | | `Copy-PveContainer` | `/nodes/{node}/lxc/{vmid}/clone` | POST | Open | -| `Move-PveContainer` | `/nodes/{node}/lxc/{vmid}/migrate` | POST | New | +| `Move-PveContainer` | `/nodes/{node}/lxc/{vmid}/migrate` | POST | Open | | `Get-PveContainerConfig` | `/nodes/{node}/lxc/{vmid}/config` | GET | Open | | `Set-PveContainerConfig` | `/nodes/{node}/lxc/{vmid}/config` | PUT | Open | +| `Suspend-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/suspend` | POST | Open | +| `Resume-PveContainer` | `/nodes/{node}/lxc/{vmid}/status/resume` | POST | Open | +| `Resize-PveContainerDisk` | `/nodes/{node}/lxc/{vmid}/resize` | PUT | Open | +| `New-PveContainerTemplate` | `/nodes/{node}/lxc/{vmid}/template` | POST | Open | +| `Move-PveContainerVolume` | `/nodes/{node}/lxc/{vmid}/move_volume` | POST | Open | +| `Get-PveContainerInterface` | `/nodes/{node}/lxc/{vmid}/interfaces` | GET | Open | +| `Get-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot` | GET | Open | +| `New-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot` | POST | Open | +| `Remove-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot/{name}` | DELETE | Open | +| `Restore-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot/{name}/rollback` | POST | Open | -#### Container Snapshots (4 cmdlets — NEW) -| Cmdlet | API Endpoint | Method | Prior Status | -|---|---|---|---| -| `Get-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot` | GET | New | -| `New-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot` | POST | New | -| `Remove-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot/{name}` | DELETE | New | -| `Restore-PveContainerSnapshot` | `/nodes/{node}/lxc/{vmid}/snapshot/{name}/rollback` | POST | New | - -#### Storage (6 cmdlets) +#### Storage (11) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveStorage` | `/storage` or `/nodes/{node}/storage` | GET | Open | @@ -235,8 +275,13 @@ PSProxmoxVE/ | `Invoke-PveStorageDownload` | `/nodes/{node}/storage/{storage}/download-url` | POST | Open | | `New-PveStorage` | `/storage` | POST | Open | | `Remove-PveStorage` | `/storage/{storage}` | DELETE | Open | +| `Set-PveStorage` | `/storage/{storage}` | PUT | Open | +| `Get-PveStorageStatus` | `/nodes/{node}/storage/{storage}/status` | GET | Open | +| `Remove-PveStorageContent` | `/nodes/{node}/storage/{storage}/content/{volume}` | DELETE | Open | +| `Set-PveStorageContent` | `/nodes/{node}/storage/{storage}/content/{volume}` | PUT | Open | +| `New-PveStorageDisk` | `/nodes/{node}/storage/{storage}/content` | POST | Open | -#### VM Snapshots (4 cmdlets) +#### VM Snapshots (4) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot` | GET | Open | @@ -244,7 +289,7 @@ PSProxmoxVE/ | `Remove-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot/{name}` | DELETE | Open | | `Restore-PveSnapshot` | `/nodes/{node}/qemu/{vmid}/snapshot/{name}/rollback` | POST | Open | -#### Networking (5 cmdlets) +#### Networking (5) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveNetwork` | `/nodes/{node}/network` | GET | Open | @@ -253,20 +298,71 @@ PSProxmoxVE/ | `Remove-PveNetwork` | `/nodes/{node}/network/{iface}` | DELETE | Open | | `Invoke-PveNetworkApply` | `/nodes/{node}/network` | PUT | Open | -#### SDN (9 cmdlets) +#### SDN (25) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveSdnZone` | `/cluster/sdn/zones` | GET | Open | | `New-PveSdnZone` | `/cluster/sdn/zones` | POST | Open | | `Remove-PveSdnZone` | `/cluster/sdn/zones/{zone}` | DELETE | Open | +| `Set-PveSdnZone` | `/cluster/sdn/zones/{zone}` | PUT | Open | | `Get-PveSdnVnet` | `/cluster/sdn/vnets` | GET | Open | | `New-PveSdnVnet` | `/cluster/sdn/vnets` | POST | Open | | `Remove-PveSdnVnet` | `/cluster/sdn/vnets/{vnet}` | DELETE | Open | -| `Get-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets` | GET | New | -| `New-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets` | POST | New | -| `Remove-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets/{subnet}` | DELETE | New | +| `Set-PveSdnVnet` | `/cluster/sdn/vnets/{vnet}` | PUT | Open | +| `Get-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets` | GET | Open | +| `New-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets` | POST | Open | +| `Remove-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets/{subnet}` | DELETE | Open | +| `Set-PveSdnSubnet` | `/cluster/sdn/vnets/{vnet}/subnets/{subnet}` | PUT | Open | +| `Get-PveSdnIpam` | `/cluster/sdn/ipams` | GET | Open | +| `New-PveSdnIpam` | `/cluster/sdn/ipams` | POST | Open | +| `Remove-PveSdnIpam` | `/cluster/sdn/ipams/{ipam}` | DELETE | Open | +| `Set-PveSdnIpam` | `/cluster/sdn/ipams/{ipam}` | PUT | Open | +| `Get-PveSdnDns` | `/cluster/sdn/dns` | GET | Open | +| `New-PveSdnDns` | `/cluster/sdn/dns` | POST | Open | +| `Remove-PveSdnDns` | `/cluster/sdn/dns/{dns}` | DELETE | Open | +| `Set-PveSdnDns` | `/cluster/sdn/dns/{dns}` | PUT | Open | +| `Get-PveSdnController` | `/cluster/sdn/controllers` | GET | Open | +| `New-PveSdnController` | `/cluster/sdn/controllers` | POST | Open | +| `Remove-PveSdnController` | `/cluster/sdn/controllers/{controller}` | DELETE | Open | +| `Set-PveSdnController` | `/cluster/sdn/controllers/{controller}` | PUT | Open | +| `Invoke-PveSdnApply` | `/cluster/sdn` | PUT | Open | -#### Users / ACLs / Tokens (12 cmdlets) +#### Firewall (21) +| Cmdlet | API Endpoint | Method | Prior Status | +|---|---|---|---| +| `Get-PveFirewallRule` | `/cluster/firewall/rules` | GET | Open | +| `New-PveFirewallRule` | `/cluster/firewall/rules` | POST | Open | +| `Set-PveFirewallRule` | `/cluster/firewall/rules/{pos}` | PUT | Open | +| `Remove-PveFirewallRule` | `/cluster/firewall/rules/{pos}` | DELETE | Open | +| `Get-PveFirewallGroup` | `/cluster/firewall/groups` | GET | Open | +| `New-PveFirewallGroup` | `/cluster/firewall/groups` | POST | Open | +| `Remove-PveFirewallGroup` | `/cluster/firewall/groups/{group}` | DELETE | Open | +| `Get-PveFirewallAlias` | `/cluster/firewall/aliases` | GET | Open | +| `New-PveFirewallAlias` | `/cluster/firewall/aliases` | POST | Open | +| `Set-PveFirewallAlias` | `/cluster/firewall/aliases/{name}` | PUT | Open | +| `Remove-PveFirewallAlias` | `/cluster/firewall/aliases/{name}` | DELETE | Open | +| `Get-PveFirewallIpSet` | `/cluster/firewall/ipset` | GET | Open | +| `New-PveFirewallIpSet` | `/cluster/firewall/ipset` | POST | Open | +| `Remove-PveFirewallIpSet` | `/cluster/firewall/ipset/{name}` | DELETE | Open | +| `Get-PveFirewallIpSetEntry` | `/cluster/firewall/ipset/{name}` | GET | Open | +| `New-PveFirewallIpSetEntry` | `/cluster/firewall/ipset/{name}` | POST | Open | +| `Set-PveFirewallIpSetEntry` | `/cluster/firewall/ipset/{name}/{cidr}` | PUT | Open | +| `Remove-PveFirewallIpSetEntry` | `/cluster/firewall/ipset/{name}/{cidr}` | DELETE | Open | +| `Get-PveFirewallOptions` | `/cluster/firewall/options` | GET | Open | +| `Set-PveFirewallOptions` | `/cluster/firewall/options` | PUT | Open | +| `Get-PveFirewallRef` | `/cluster/firewall/refs` | GET | Open | + +#### Backup (6) +| Cmdlet | API Endpoint | Method | Prior Status | +|---|---|---|---| +| `New-PveBackup` | `/nodes/{node}/vzdump` | POST | Open | +| `Get-PveBackupJob` | `/cluster/backup` | GET | Open | +| `New-PveBackupJob` | `/cluster/backup` | POST | Open | +| `Set-PveBackupJob` | `/cluster/backup/{id}` | PUT | Open | +| `Remove-PveBackupJob` | `/cluster/backup/{id}` | DELETE | Open | +| `Get-PveBackupInfo` | `/cluster/backup-info/not-backed-up` | GET | Open | + +#### Users / Access (23) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveUser` | `/access/users` | GET | Open | @@ -276,13 +372,32 @@ PSProxmoxVE/ | `Get-PveRole` | `/access/roles` | GET | Open | | `New-PveRole` | `/access/roles` | POST | Open | | `Remove-PveRole` | `/access/roles/{roleid}` | DELETE | Open | +| `Set-PveRole` | `/access/roles/{roleid}` | PUT | Open | | `Get-PvePermission` | `/access/acl` | GET | Open | | `Set-PvePermission` | `/access/acl` | PUT | Open | | `Get-PveApiToken` | `/access/users/{userid}/token` | GET | Open | | `New-PveApiToken` | `/access/users/{userid}/token/{tokenid}` | POST | Open | | `Remove-PveApiToken` | `/access/users/{userid}/token/{tokenid}` | DELETE | Open | +| `Set-PveApiToken` | `/access/users/{userid}/token/{tokenid}` | PUT | Open | +| `Get-PveGroup` | `/access/groups` | GET | Open | +| `New-PveGroup` | `/access/groups` | POST | Open | +| `Set-PveGroup` | `/access/groups/{groupid}` | PUT | Open | +| `Remove-PveGroup` | `/access/groups/{groupid}` | DELETE | Open | +| `Get-PveDomain` | `/access/domains` | GET | Open | +| `New-PveDomain` | `/access/domains` | POST | Open | +| `Set-PveDomain` | `/access/domains/{realm}` | PUT | Open | +| `Remove-PveDomain` | `/access/domains/{realm}` | DELETE | Open | +| `Set-PvePassword` | `/access/password` | PUT | Open | -#### Templates (4 cmdlets) +#### Pools (4) +| Cmdlet | API Endpoint | Method | Prior Status | +|---|---|---|---| +| `Get-PvePool` | `/pools` | GET | Open | +| `New-PvePool` | `/pools` | POST | Open | +| `Set-PvePool` | `/pools/{poolid}` | PUT | Open | +| `Remove-PvePool` | `/pools/{poolid}` | DELETE | Open | + +#### Templates (4) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveTemplate` | `/nodes/{node}/qemu` (filtered) | GET | Open | @@ -290,44 +405,105 @@ PSProxmoxVE/ | `Remove-PveTemplate` | `/nodes/{node}/qemu/{vmid}` | DELETE | Open | | `New-PveVmFromTemplate` | `/nodes/{node}/qemu/{vmid}/clone` | POST | Open | -#### Cloud-Init (3 cmdlets) +#### Cloud-Init (3) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveCloudInitConfig` | `/nodes/{node}/qemu/{vmid}/config` | GET | Open | | `Set-PveCloudInitConfig` | `/nodes/{node}/qemu/{vmid}/config` | PUT | Open | | `Invoke-PveCloudInitRegenerate` | `/nodes/{node}/qemu/{vmid}/cloudinit` | PUT | Open | -#### Tasks (2 cmdlets) +#### Tasks (4) | Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| | `Get-PveTask` | `/nodes/{node}/tasks/{upid}/status` | GET | Open | | `Wait-PveTask` | `/nodes/{node}/tasks/{upid}/status` (polling) | GET | Open | +| `Get-PveTaskList` | `/nodes/{node}/tasks` | GET | Open | +| `Stop-PveTask` | `/nodes/{node}/tasks/{upid}` | DELETE | Open | -### Missing API Coverage by Functional Area - -| Area | Key Missing Endpoints | Priority | Prior Status | +#### Cluster (1) +| Cmdlet | API Endpoint | Method | Prior Status | |---|---|---|---| -| **Firewall** | `/cluster/firewall/*`, `/nodes/{node}/firewall/*`, `/nodes/{node}/qemu/{vmid}/firewall/*` | High | Open | -| **Backup / vzdump** | `/nodes/{node}/vzdump`, `/cluster/backup/*` | High | Open | -| **Pools** | `/pools`, `/pools/{poolid}` (CRUD) | Medium | Open | -| **HA** | `/cluster/ha/*` (groups, resources, status) | Medium | Open | -| **Ceph** | `/nodes/{node}/ceph/*` (OSD, pool, mon, fs) | Medium | Open | -| **Replication** | `/cluster/replication` (CRUD) | Low | Open | -| **Access Groups** | `/access/groups` (CRUD) | Low | Open | -| **Access Domains** | `/access/domains` (CRUD) | Low | Open | -| **PBS Integration** | Proxmox Backup Server operations | Low | Open | -| **Cluster Config** | `/cluster/config/*` (join, nodes, totem) | Low | Open | -| **ACME** | `/cluster/acme/*`, `/nodes/{node}/certificates/*` | Low | Open | -| **Node Management** | `/nodes/{node}/apt/*`, `/nodes/{node}/disks/*`, `/nodes/{node}/services/*` | Low | Open | -| **Metrics** | `/cluster/metrics/*` | Low | Open | -| **SDN IPAM/DNS/Controllers** | `/cluster/sdn/ipams/*`, `/cluster/sdn/dns/*`, `/cluster/sdn/controllers/*` | Low | New | -| **VM Agent (extended)** | Additional agent endpoints (file-read, file-write, os-info) | Low | Open | +| `Get-PveClusterResource` | `/cluster/resources` | GET | Open | + +### Coverage Summary Per Functional Area + +| Area | Total Endpoints | Covered | % | 🆕 PVE 9.0 New | Prior Status | +|---|---|---|---|---|---| +| acl | 2 | 2 | 100% | 0 | Open | +| version | 1 | 1 | 100% | 0 | Open | +| backup | 6 | 5 | 83% | 0 | Open | +| access_groups | 5 | 4 | 80% | 0 | Open | +| roles | 5 | 4 | 80% | 0 | Open | +| tasks | 5 | 4 | 80% | 0 | Open | +| firewall | 40 | 31 | 78% | 0 | Open | +| users | 12 | 9 | 75% | 0 | Open | +| networking | 7 | 5 | 71% | 0 | Open | +| pools | 7 | 5 | 71% | 0 | Open | +| access_domains | 6 | 4 | 67% | 0 | Open | +| storage_config | 5 | 3 | 60% | 0 | Open | +| storage | 19 | 8 | 42% | 1 | Open | +| sdn | 60 | 25 | 42% | 16 | Open | +| vms | 97 | 35 | 36% | 1 | Open | +| containers | 62 | 21 | 34% | 1 | Open | +| access | 15 | 2 | 13% | 1 | Open | +| nodes | 75 | 8 | 11% | 11 | Open | +| cluster | 77 | 3 | 4% | 6 | Open | +| ceph | 40 | 0 | 0% | 0 | Open | +| ha | 21 | 0 | 0% | 5 | Open | +| disks | 18 | 0 | 0% | 0 | Open | +| acme | 15 | 0 | 0% | 0 | Open | +| cluster_config | 10 | 0 | 0% | 0 | Open | +| certificates | 8 | 0 | 0% | 0 | Open | +| apt | 8 | 0 | 0% | 0 | Open | +| services | 7 | 0 | 0% | 0 | Open | +| metrics | 7 | 0 | 0% | 0 | Open | +| replication | 5 | 0 | 0% | 0 | Open | +| **TOTAL** | **646** | **180** | **27.9%** | **42** | — | + +### API Drift — Changed Endpoints (Covered Cmdlets) + +| Cmdlet | Endpoint | Change Description | Risk | Prior Status | +|---|---|---|---|---| +| `Set-PveBackupJob` | `PUT /cluster/backup/{id}` | Removed `notification-policy`, `notification-target` params | Breaking | Open | +| `Set-PveNetwork` | `PUT /nodes/{node}/network` | Added `regenerate-frr`, removed `skip_frr` | Breaking | Open | +| `Get-PveVmConfig` | `GET .../qemu/{vmid}/config` | Returns changed in latest version | Cosmetic | Open | +| `Move-PveVm` | `POST .../qemu/{vmid}/migrate` | Added `with-conntrack-state` parameter | Additive | Open | + +### PVE 9.0 New Endpoints (42 total, 0 implemented) + +| Area | Endpoint | Method | Description | +|---|---|---|---| +| ha | `/cluster/ha/rules` | GET/POST | HA rules system (new in PVE 9.0) | +| ha | `/cluster/ha/rules/{rule}` | GET/PUT/DELETE | Individual HA rule management | +| cluster | `/cluster/bulk-action/guest/*` | POST | Bulk guest start/shutdown/suspend/migrate | +| sdn | `/cluster/sdn/fabrics/*` | GET/POST/PUT/DELETE | SDN fabrics (13 endpoints) | +| sdn | `/cluster/sdn/lock` / `rollback` | POST/DELETE | SDN lock and rollback | +| nodes | `/nodes/{node}/capabilities/qemu/cpu-flags` | GET | CPU flags query | +| nodes | `/nodes/{node}/capabilities/qemu/migration` | GET | Migration capabilities | +| nodes | `/nodes/{node}/sdn/fabrics/*` | GET | Node SDN fabric status | +| nodes | `/nodes/{node}/query-oci-repo-tags` | GET | OCI repo tag query | +| storage | `/nodes/{node}/storage/{storage}/oci-registry-pull` | POST | OCI registry pull | +| vms | `/nodes/{node}/qemu/{vmid}/dbus-vmstate` | POST | D-Bus VM state | +| containers | `GET /nodes/{node}/lxc/{vmid}/migrate` | GET | Container migration pre-check | +| access | `POST /access/vncticket` | POST | VNC ticket creation | ### High-Value Gaps -1. **Firewall management** — Critical for security automation. No cmdlets for creating/managing firewall rules at cluster, node, or VM level. -2. **Backup/restore (vzdump)** — Essential for DR automation. No cmdlets for creating backups, managing schedules, or restoring. -3. **Pool management** — Required for multi-tenant environments. No cmdlets for creating/managing resource pools. +**Tier 1 — Most impactful for users:** + +1. **HA (High Availability)** — 21 endpoints, 0% covered. Essential for production clusters. Resources, groups, status, and the new PVE 9.0 rules system (5 🆕 endpoints). +2. **Cluster configuration** — 10 endpoints, 0%. Cluster create/join/node management is fundamental for multi-node setups. +3. **Ceph** — 40 endpoints, 0%. Critical for hyperconverged infrastructure (OSD, MON, pools, status). +4. **Notifications** — 32 cluster endpoints, 0%. New in PVE 8.1+, essential for monitoring/alerting automation. +5. **Disk management** — 18 endpoints, 0%. LVM, ZFS, SMART, wipe — needed for storage provisioning. + +**Tier 2 — Important operational gaps:** + +6. **VM/CT-level firewall** — ~44 combined endpoints. Cluster-level IS covered, but per-VM/CT rules are not. +7. **SDN Fabrics** (🆕 PVE 9.0) — 13 new endpoints. Key for advanced SDN. +8. **Resource mappings** (PCI/USB passthrough) — 15 endpoints. Key for GPU passthrough. +9. **Certificates/ACME** — 23 combined endpoints. TLS certificate management. +10. **`GET /cluster/nextid`** — Trivial but frequently needed to allocate VM IDs programmatically. --- @@ -335,81 +511,65 @@ PSProxmoxVE/ ### 3a. PowerShell Module Design -#### Cmdlet Naming -- **All cmdlets use approved PowerShell verbs** via verb class constants (`VerbsCommon.Get`, `VerbsLifecycle.Start`, etc.). -- Noun prefix `Pve` is consistent across all 83 cmdlets. -- Previous finding about `Reset-PveVm` using a string literal has been fixed — now uses `VerbsCommon.Reset`. - -#### Parameter Design -- `[Parameter(Mandatory = ...)]` used appropriately throughout. -- `ValueFromPipelineByPropertyName = true` used on `Node`, `VmId`, and similar parameters. -- `Position` attributes used on key parameters. -- `ValidateSet`, `ValidateRange(100, 999999999)`, `ValidateNotNullOrEmpty`, `ValidatePattern` used where appropriate. -- **HelpMessage** present on virtually all parameters (previously missing). - -#### ShouldProcess / WhatIf / Confirm -- Every destructive/mutating cmdlet implements `SupportsShouldProcess = true`. -- `ConfirmImpact = ConfirmImpact.High` on all `Remove-*` cmdlets, `Stop-PveVm`, `Reset-PveVm`, `New-PveTemplate`, `Restore-PveSnapshot`, `Restore-PveContainerSnapshot`. -- **Gap**: `Remove-PveRole` does not set `ConfirmImpact.High` — deleting a role is a significant operation. - -#### OutputType -- `[OutputType]` attributes present on all 83 cmdlets. - -#### Pipeline Support -- All cmdlets use `ProcessRecord` for pipeline processing. -- `Get-PveVm`, `Get-PveContainer` output objects with `Node` and `VmId` enabling chaining. - -#### Error Handling -- `ThrowTerminatingError` used appropriately for fatal conditions. -- `WriteWarning` used for non-fatal conditions. -- Custom exception hierarchy provides structured errors. +- **All cmdlets use approved PowerShell verbs** via verb class constants. +- **Noun prefix `Pve`** is consistent across all 169 cmdlets. +- **HelpMessage** present on virtually all parameters. +- **ShouldProcess** on all destructive/mutating cmdlets. +- **Pipeline support** via `ProcessRecord` and `ValueFromPipelineByPropertyName`. +- **ValidateRange/ValidateNotNullOrEmpty/ValidateSet** used appropriately. +- **OutputType** on all 169 cmdlets (previously ~54 were missing — now all have it). +- **All cmdlets are sealed** (previously ~95 were not). ### 3b. C# Code Quality -#### Null-Reference Safety -- `enable` in both `.csproj` files. -- Consistent use of nullable annotations, null guards (`?? throw`), and null-coalescing patterns. -- No null-forgiving operators (`!`) used. - -#### Async/Await Pattern -- All HTTP operations are async in `PveHttpClient` with `.ConfigureAwait(false)`. -- Sync wrappers use `.GetAwaiter().GetResult()` — standard for PowerShell binary modules. -- Low deadlock risk given the single pipeline thread model. - -#### IDisposable Correctness -- `PveHttpClient` implements `IDisposable` correctly with `_disposed` flag. -- All service methods use `using var client = new PveHttpClient(session)`. -- File upload uses framework-conditional async disposal. - -#### Exception Handling -- Well-defined exception hierarchy (7 custom types) with context properties. -- `PveHttpClient.SendAsync` wraps `HttpRequestException` in `PveApiException`. -- Silent catches are intentional and documented (node polling, status enrichment). - -#### Magic Strings -- Auth header names inline (minor, not problematic). -- API resource paths constructed inline (pragmatic for codebase size). -- `RandomNumberGenerator` used for boundary generation (previously `new Random()`). - -#### Logging / Verbose Output -- `WriteVerbose` used extensively throughout cmdlets (previously missing). -- `WriteProgress` used in `Send-PveFile`, `Import-PveOva`, and `Wait-PveTask`. - -### 3c. General Hygiene - -- No unused `using` directives detected. -- No commented-out blocks or dead code. -- Zero TODO/FIXME/HACK markers. -- Consistent code style matching `.editorconfig`. -- XML doc comments on all public types and cmdlet classes. -- `CS1591` no longer suppressed in Core project. +- **Nullable annotations**: `enable` in both projects. +- **IDisposable**: `PveHttpClient` correctly implements `IDisposable`. +- **Logging**: `WriteVerbose` used extensively; `WriteProgress` in file uploads and task waiting. +- **Exception hierarchy**: 7 custom exception types with context properties. +- **Bare catches eliminated**: All `catch { }` blocks replaced with filtered `catch (Exception ex) when (...)`. +- **Magic strings extracted**: Auth header names are now `const string` fields. +- **Zero TODO/FIXME/HACK markers**. +- **No commented-out code or dead code**. +- **Clean code style** matching `.editorconfig`. +- **Dual JSON attributes removed**: Only `[JsonProperty]` (Newtonsoft) remains; `[JsonPropertyName]` removed. ### Code Quality Findings | ID | File | Line | Severity | Description | Prior Status | |---|---|---|---|---|---| -| CQ1 | `RemovePveRoleCmdlet.cs` | 13 | Medium | Missing `ConfirmImpact.High` — deleting roles is significant | New | -| CQ2 | `PveHttpClient.cs` | 316-323 | Low | Auth header names (`PVEAPIToken=`, `CSRFPreventionToken`) could be constants | Open | +| CQ1 | `NewPveContainerSnapshotCmdlet.cs` | 75 | **Critical** | **Infinite loop with no timeout in private `WaitForTask`.** `while (true)` with no timeout or cancellation. If task never completes, cmdlet hangs forever. Should use `TaskService.WaitForTask` (same pattern that was fixed in VM snapshot cmdlets). | New | +| CQ2 | `RemovePveContainerSnapshotCmdlet.cs` | 67 | **Critical** | **Identical infinite loop** — same `while(true)` no-timeout pattern as CQ1. | New | +| CQ3 | `RestorePveContainerSnapshotCmdlet.cs` | 70 | **Critical** | **Identical infinite loop** — same pattern as CQ1. | New | +| CQ4 | `InvokePveStorageDownloadCmdlet.cs` | 83 | **Critical** | **Infinite loop in download task polling.** `while(true)` with no timeout. Should use `TaskService.WaitForTask`. | New | +| CQ5 | `SendPveFileCmdlet.cs` | 143 | **Critical** | **Infinite loop in upload task polling.** `while(true)` with no timeout. Should use `TaskService.WaitForTask`. | New | +| CQ6 | Both `.csproj` files | 4 | **Medium** | **`net9.0` target is EOL** (May 2025). Should upgrade to `net10.0` (LTS). | Open | +| CQ7 | `PveHttpClient.cs` | ~154 sites | **Medium** | **Sync-over-async via `.GetAwaiter().GetResult()`** across ~216 call sites. Standard for PS binary modules but carries deadlock risk. | Open | +| CQ8 | `PveCmdletBase.cs` | 149 | **Medium** | **Broad catch in status polling.** `catch (Exception ex) when (...)` is filtered but still catches broadly. Improvement over bare catch but could be more specific. | Open | +| CQ9 | `RestartPveContainerCmdlet.cs` | 15 | **Medium** | **Missing `ConfirmImpact.High`** — `RestartPveVmCmdlet` has it but container counterpart does not. Inconsistent. | New | +| CQ10 | `SuspendPveContainerCmdlet.cs` | 14 | **Medium** | **Missing `ConfirmImpact.High`** — `SuspendPveVmCmdlet` has it but container counterpart does not. Inconsistent. | New | +| CQ11 | 38 cmdlet files | varies | **Medium** | **`new PveHttpClient` per-operation.** 38 cmdlets create their own PveHttpClient instance instead of going through services. Bypasses connection pooling. | Open | +| CQ12 | `PSProxmoxVE.csproj` | 22 | **Low** | **`System.Management.Automation` pinned to 7.4.0.** Consider updating if targeting .NET 10. | Open | + +### Fixed Since Prior Scan + +| Prior ID | Description | Status | +|---|---|---| +| CQ1–CQ4 | Infinite loop WaitForTask in InvokePveNetworkApply, NewPveSnapshot, RestorePveSnapshot, RemovePveSnapshot | **Fixed** — now uses `TaskService.WaitForTask` | +| CQ5 | Infinite poll loop in InvokePveVmGuestExec | **Fixed** — added Timeout parameter with Stopwatch + TimeoutException | +| CQ7 | Duplicated WaitForTask methods (4 VM/network cmdlets) | **Fixed** — uses TaskService | +| CQ9 | ~54 cmdlets missing OutputType | **Fixed** — all 169 cmdlets now have `[OutputType]` | +| CQ10 | Firewall VmId defaults to 0 instead of nullable | **Fixed** — no longer `int` default 0 pattern | +| CQ11 | Bare catch in PveHttpClient.ExtractErrorMessage | **Fixed** — proper exception handling | +| CQ12 | Bare catch in PveCmdletBase status polling | **Fixed** — filtered `when` clause excludes OOM/SOE | +| CQ13 | Bare catch in VmService + ContainerService | **Fixed** — filtered to PveApiException/HttpRequestException | +| CQ14 | Bare catch in GetPveVmCmdlet | **Fixed** — proper exception handling | +| CQ17 | RemovePveRoleCmdlet missing ConfirmImpact.High | **Fixed** — now has `ConfirmImpact = ConfirmImpact.High` | +| CQ18 | ~95 cmdlets not sealed | **Fixed** — all 169 cmdlets are now `sealed` | +| CQ19 | SuspendPveVmCmdlet missing ConfirmImpact.High | **Fixed** — now has `ConfirmImpact.High` | +| CQ20 | RestartPveVmCmdlet missing ConfirmImpact | **Fixed** — now has `ConfirmImpact.High` | +| CQ21 | Dual JSON serialization attributes | **Fixed** — `[JsonPropertyName]` removed, only `[JsonProperty]` remains | +| CQ22 | Auth header magic strings | **Fixed** — extracted to `const string ApiTokenPrefix` and `CsrfHeaderName` | +| CQ6 (prior) | Pool/CephPool parameter conflict in NewPveStorageCmdlet | **Fixed** — runtime validation with `ThrowTerminatingError` if both specified | --- @@ -424,133 +584,108 @@ PSProxmoxVE/ ### xUnit Tests (Core Library) -| Test Area | Test File | ~Methods | -|---|---|---| -| Session lifecycle | `PveSessionTests.cs` | 9 | -| Authentication logic | `PveAuthenticatorTests.cs` | 7 | -| Version parsing | `PveVersionTests.cs` | 11 | -| Cluster models | `ClusterModelTests.cs` | 13 | -| Container models | `ContainerModelTests.cs` | 13 | -| Network models | `NetworkModelTests.cs` | 12 | -| Node models | `NodeModelTests.cs` | 13 | -| SDN models | `SdnModelTests.cs` | 17 | -| Snapshot models | `SnapshotModelTests.cs` | 9 | -| Storage models | `StorageModelTests.cs` | 19 | -| Task models | `TaskModelTests.cs` | 9 | -| User models | `UserModelTests.cs` | 24 | -| VM models | `VmModelTests.cs` | 18 | +15 test files covering authentication (PveSession, PveAuthenticator, PveVersion) and model deserialization (12 model areas including backup, cluster, firewall, SDN) with PVE 8 and PVE 9 JSON fixtures. ~180+ xUnit tests total. -**Total: ~174 xUnit tests** with PVE 8 and PVE 9 JSON fixtures. +### Pester Tests -### Pester Tests (30 files, ~500 tests) - -All 83 cmdlets have Pester unit tests covering command metadata, parameter validation, ShouldProcess presence, and pipeline binding. +100% of 169 cmdlets have Pester unit tests covering: command existence, parameter metadata, mandatory checks, parameter types, ShouldProcess presence, ConfirmImpact declarations, and "no session" error behavior. ### Integration Tests -The integration test file covers 18+ major contexts with 90+ individual tests: +- Self-hosted runner provisions nested PVE 8 and PVE 9 via Terraform + auto-install ISO +- Tests check for `PVETEST_*` environment variables, skip gracefully when unavailable +- Robust `AfterAll` cleanup of VMs, containers, users, roles, firewall rules, backup jobs +- CRUD patterns: create → verify → update → verify → delete → verify +- SDN tests check PVE version and skip if older -- Connection, Nodes, User/Role/Token CRUD, Permissions -- VM lifecycle (create, config, start/stop, reset, clone, resize, suspend/resume) -- Snapshot CRUD (VM and container) -- Storage (list, content, upload, create/remove, download) -- Network CRUD with apply/revert -- SDN zones/VNets/subnets CRUD -- Templates, Cloud-Init -- Container lifecycle (create, config, start/stop/restart, clone, snapshots) -- Linux VM provisioning (cloud image upload, disk import, cloud-init, guest agent) +### Test Coverage Gap List (cmdlets lacking integration tests) -**Setup/teardown**: AfterAll cleans up all created resources. Environment variables inject connection details. Tests skip gracefully when env vars are missing. +| Cmdlet | Unit Test | Integration Test | Prior Status | +|---|---|---|---| +| **Nodes** | | | | +| Get-PveNodeConfig | Yes | No | Open | +| Set-PveNodeConfig | Yes | No | Open | +| Get-PveNodeDns | Yes | No | Open | +| Set-PveNodeDns | Yes | No | Open | +| Start-PveNodeVms | Yes | No | Open | +| Stop-PveNodeVms | Yes | No | Open | +| **VMs** | | | | +| Move-PveVm | Yes | No | Open | +| Move-PveVmDisk | Yes | No | Open | +| Remove-PveVmDisk | Yes | No | Open | +| **Guest Agent** | | | | +| Get-PveVmGuestOsInfo | Yes | No | Open | +| Get-PveVmGuestFsInfo | Yes | No | Open | +| Read-PveVmGuestFile | Yes | No | Open | +| Write-PveVmGuestFile | Yes | No | Open | +| Set-PveVmGuestPassword | Yes | No | Open | +| Invoke-PveVmGuestFsTrim | Yes | No | Open | +| **Containers** | | | | +| Move-PveContainer | Yes | No | Open | +| Suspend-PveContainer | Yes | No | Open | +| Resume-PveContainer | Yes | No | Open | +| Resize-PveContainerDisk | Yes | No | Open | +| New-PveContainerTemplate | Yes | No | Open | +| Move-PveContainerVolume | Yes | No | Open | +| Get-PveContainerInterface | Yes | No | Open | +| **Storage** | | | | +| Set-PveStorage | Yes | No | Open | +| Get-PveStorageStatus | Yes | No | Open | +| Remove-PveStorageContent | Yes | No | Open | +| Set-PveStorageContent | Yes | No | Open | +| New-PveStorageDisk | Yes | No | Open | +| **SDN** | | | | +| Set-PveSdnZone | Yes | No | Open | +| Set-PveSdnVnet | Yes | No | Open | +| Set-PveSdnSubnet | Yes | No | Open | +| New-PveSdnIpam | Yes | No | Open | +| Remove-PveSdnIpam | Yes | No | Open | +| Set-PveSdnIpam | Yes | No | Open | +| New-PveSdnDns | Yes | No | Open | +| Remove-PveSdnDns | Yes | No | Open | +| Set-PveSdnDns | Yes | No | Open | +| New-PveSdnController | Yes | No | Open | +| Remove-PveSdnController | Yes | No | Open | +| Set-PveSdnController | Yes | No | Open | +| Invoke-PveSdnApply | Yes | No | Open | +| **Users / Access** | | | | +| Set-PveRole | Yes | No | Open | +| Set-PveApiToken | Yes | No | Open | +| Get-PveGroup | Yes | No | Open | +| New-PveGroup | Yes | No | Open | +| Set-PveGroup | Yes | No | Open | +| Remove-PveGroup | Yes | No | Open | +| Get-PveDomain | Yes | No | Open | +| New-PveDomain | Yes | No | Open | +| Set-PveDomain | Yes | No | Open | +| Remove-PveDomain | Yes | No | Open | +| Set-PvePassword | Yes | No | Open | +| **Pools** | | | | +| Get-PvePool | Yes | No | Open | +| New-PvePool | Yes | No | Open | +| Set-PvePool | Yes | No | Open | +| Remove-PvePool | Yes | No | Open | +| **Templates** | | | | +| New-PveTemplate | Yes | No | Open | +| Remove-PveTemplate | Yes | No | Open | +| **Tasks** | | | | +| Get-PveTaskList | Yes | No | Open | +| Stop-PveTask | Yes | No | Open | +| **Firewall** | | | | +| Get-PveFirewallGroup | Yes | No | Open | +| New-PveFirewallGroup | Yes | No | Open | +| Remove-PveFirewallGroup | Yes | No | Open | +| Set-PveFirewallAlias | Yes | No | Open | +| Set-PveFirewallIpSetEntry | Yes | No | Open | +| Set-PveFirewallOptions | Yes | No | Open | +| Get-PveFirewallRef | Yes | No | Open | +| **Backup** | | | | +| New-PveBackup | Yes | No | Open | +| Get-PveBackupInfo | Yes | No | Open | +| **Cluster** | | | | +| Get-PveClusterResource | Yes | No | Open | -### Test Coverage Gap List - -| Cmdlet | Unit Test | Integration Test | PVE 8 | PVE 9 | Prior Status | -|---|---|---|---|---|---| -| `Connect-PveServer` | ✓ | ✓ | ✓ | ✓ | Open | -| `Disconnect-PveServer` | ✓ | ✓ | ✓ | ✓ | Open | -| `Test-PveConnection` | ✓ | ✓ | ✓ | ✓ | Open | -| `Get-PveNode` | ✓ | ✓ | ✓ | ✓ | Open | -| `Get-PveNodeStatus` | ✓ | ✓ | ✓ | ✓ | Open | -| `Get-PveVm` | ✓ | ✓ | ✓ | ✓ | Open | -| `New-PveVm` | ✓ | ✓ | — | ✓ | Open | -| `Remove-PveVm` | ✓ | ✓ | — | ✓ | Open | -| `Start-PveVm` | ✓ | ✓ | ✓ | ✓ | Open | -| `Stop-PveVm` | ✓ | ✓ | ✓ | ✓ | Open | -| `Restart-PveVm` | ✓ | ✓ | ✓ | ✓ | Open | -| `Suspend-PveVm` | ✓ | ✓ | — | ✓ | Fixed | -| `Resume-PveVm` | ✓ | ✓ | — | ✓ | Fixed | -| `Reset-PveVm` | ✓ | ✓ | ✓ | ✓ | Open | -| `Copy-PveVm` | ✓ | ✓ | — | ✓ | Open | -| `Move-PveVm` | ✓ | ✗ | — | — | Open | -| `Get-PveVmConfig` | ✓ | ✓ | — | ✓ | Open | -| `Set-PveVmConfig` | ✓ | ✓ | — | ✓ | Open | -| `Resize-PveVmDisk` | ✓ | ✓ | — | ✓ | Fixed | -| `Import-PveVmDisk` | ✓ | ✓ | — | ✓ | New | -| `Import-PveOva` | ✓ | ✗ | — | — | New | -| `Test-PveVmGuestAgent` | ✓ | ✓ | — | ✓ | Open | -| `Get-PveVmGuestNetwork` | ✓ | ✓ | — | ✓ | Open | -| `Invoke-PveVmGuestExec` | ✓ | ✓ | — | ✓ | Open | -| `Get-PveContainer` | ✓ | ✓ | — | ✓ | Open | -| `New-PveContainer` | ✓ | ✓ | — | ✓ | Open | -| `Remove-PveContainer` | ✓ | ✓ | — | ✓ | Open | -| `Start-PveContainer` | ✓ | ✓ | — | ✓ | Open | -| `Stop-PveContainer` | ✓ | ✓ | — | ✓ | Open | -| `Restart-PveContainer` | ✓ | ✓ | — | ✓ | Open | -| `Copy-PveContainer` | ✓ | ✓ | — | ✓ | Fixed | -| `Move-PveContainer` | ✓ | ✗ | — | — | New | -| `Get-PveContainerConfig` | ✓ | ✓ | — | ✓ | Open | -| `Set-PveContainerConfig` | ✓ | ✓ | — | ✓ | Open | -| `Get-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New | -| `New-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New | -| `Remove-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New | -| `Restore-PveContainerSnapshot` | ✓ | ✓ | — | ✓ | New | -| `Get-PveStorage` | ✓ | ✓ | ✓ | ✓ | Open | -| `New-PveStorage` | ✓ | ✓ | — | ✓ | Fixed | -| `Remove-PveStorage` | ✓ | ✓ | — | ✓ | Fixed | -| `Get-PveStorageContent` | ✓ | ✓ | ✓ | ✓ | Open | -| `Send-PveFile` | ✓ | ✓ | — | ✓ | Open | -| `Invoke-PveStorageDownload` | ✓ | ✓ | — | ✓ | Fixed | -| `Get-PveSnapshot` | ✓ | ✓ | ✓ | ✓ | Open | -| `New-PveSnapshot` | ✓ | ✓ | ✓ | ✓ | Open | -| `Remove-PveSnapshot` | ✓ | ✓ | ✓ | ✓ | Open | -| `Restore-PveSnapshot` | ✓ | ✗ | — | — | Open | -| `Get-PveNetwork` | ✓ | ✓ | — | ✓ | Open | -| `New-PveNetwork` | ✓ | ✓ | — | ✓ | Fixed | -| `Set-PveNetwork` | ✓ | ✓ | — | ✓ | Fixed | -| `Remove-PveNetwork` | ✓ | ✓ | — | ✓ | Fixed | -| `Invoke-PveNetworkApply` | ✓ | ✓ | — | ✓ | Fixed | -| `Get-PveSdnZone` | ✓ | ✓ | — | ✓ | Open | -| `New-PveSdnZone` | ✓ | ✓ | — | ✓ | Fixed | -| `Remove-PveSdnZone` | ✓ | ✓ | — | ✓ | Fixed | -| `Get-PveSdnVnet` | ✓ | ✓ | — | ✓ | Open | -| `New-PveSdnVnet` | ✓ | ✓ | — | ✓ | Fixed | -| `Remove-PveSdnVnet` | ✓ | ✓ | — | ✓ | Fixed | -| `Get-PveSdnSubnet` | ✓ | ✓ | — | ✓ | New | -| `New-PveSdnSubnet` | ✓ | ✓ | — | ✓ | New | -| `Remove-PveSdnSubnet` | ✓ | ✓ | — | ✓ | New | -| `Get-PveUser` | ✓ | ✓ | ✓ | ✓ | Open | -| `New-PveUser` | ✓ | ✓ | ✓ | ✓ | Open | -| `Set-PveUser` | ✓ | ✓ | ✓ | ✓ | Open | -| `Remove-PveUser` | ✓ | ✓ | ✓ | ✓ | Open | -| `Get-PveRole` | ✓ | ✓ | ✓ | ✓ | Open | -| `New-PveRole` | ✓ | ✓ | ✓ | ✓ | Open | -| `Remove-PveRole` | ✓ | ✓ | ✓ | ✓ | Open | -| `Get-PveApiToken` | ✓ | ✓ | ✓ | ✓ | Open | -| `New-PveApiToken` | ✓ | ✓ | ✓ | ✓ | Open | -| `Remove-PveApiToken` | ✓ | ✓ | ✓ | ✓ | Open | -| `Get-PvePermission` | ✓ | ✓ | — | ✓ | Open | -| `Set-PvePermission` | ✓ | ✓ | — | ✓ | Open | -| `Get-PveTemplate` | ✓ | ✓ | — | ✓ | Open | -| `New-PveTemplate` | ✓ | ✗ | — | — | Open | -| `Remove-PveTemplate` | ✓ | ✗ | — | — | Open | -| `New-PveVmFromTemplate` | ✓ | ✗ | — | — | Open | -| `Get-PveCloudInitConfig` | ✓ | ✓ | — | ✓ | Open | -| `Set-PveCloudInitConfig` | ✓ | ✓ | — | ✓ | Fixed | -| `Invoke-PveCloudInitRegenerate` | ✓ | ✓ | — | ✓ | Fixed | -| `Get-PveTask` | ✓ | ✓ | ✓ | ✓ | Open | -| `Wait-PveTask` | ✓ | ✓ | — | ✓ | Open | - -**Summary**: 83/83 unit tested (100%), 70/83 integration tested (84%). Remaining gaps are for operations requiring multi-node clusters (`Move-PveVm`, `Move-PveContainer`), optional test assets (`Import-PveOva`), or template operations that would require extra setup. +**Summary**: 169/169 unit tested (100%), ~105/169 integration tested (~62%). --- @@ -560,159 +695,112 @@ The integration test file covers 18+ major contexts with 90+ individual tests: | Aspect | Status | Details | Prior Status | |---|---|---|---| -| PSCredential usage | **Good** | `Connect-PveServer` accepts `PSCredential` for password auth | Open | -| Password extraction | **Acceptable** | `NetworkCredential.Password` used only at API call time | Open | +| PSCredential usage | **Good** | `Connect-PveServer` accepts `PSCredential` | Open | +| SecureString pattern | **Good** | Consistent `Marshal.SecureStringToGlobalAllocUnicode` + `ZeroFreeGlobalAllocUnicode` in try/finally | Open | +| Set-PveVmGuestPassword | **Good** | Now uses `SecureString` (was plain `string`) | Fixed | | API token in memory | **Acceptable** | Stored as plain `string` in `PveSession.ApiToken` | Open | -| Credential logging | **Good** | No `WriteVerbose`/`WriteDebug` calls include credentials | Open | -| Ticket storage | **Good** | Tickets stored in `PveSession` only, not written to disk | Open | -| Session expiry | **Good** | 2-hour expiry detected via `IsExpired` property | Open | +| Credential logging | **Good** | No WriteVerbose/WriteDebug includes credentials | Open | +| Ticket storage | **Good** | In `PveSession` only, not written to disk | Open | +| Session expiry | **Good** | 2-hour expiry via `IsExpired` property | Open | ### 5.2 TLS/HTTPS | Aspect | Status | Details | Prior Status | |---|---|---|---| -| HTTPS enforced | **Good** | `BaseUrl` always constructs `https://` URLs | Open | -| SkipCertificateCheck | **Good** | Explicit opt-in parameter on `Connect-PveServer` | Open | -| Warning on skip | **Good** | `WriteWarning` emitted with man-in-the-middle advisory | Fixed | -| TLS version | **Good** | Left to OS/runtime negotiation (no pinning) | Open | -| Port validation | **Good** | `ValidateRange(1, 65535)` on port parameter | Open | +| HTTPS enforced | **Good** | `BaseUrl` always `https://`, no HTTP fallback | Open | +| SkipCertificateCheck | **Good** | Explicit opt-in with `WriteWarning` advisory | Open | +| TLS version | **Good** | OS/runtime negotiation (no pinning) | Open | +| CSRF protection | **Good** | Token included on all POST/PUT/DELETE with ticket auth | Open | ### 5.3 Input Validation | Aspect | Status | Details | Prior Status | |---|---|---|---| -| VMID validation | **Good** | `[ValidateRange(100, 999999999)]` on all VmId parameters | Fixed | -| URL construction | **Good** | String interpolation with validated parameters | Open | -| User ID encoding | **Good** | `Uri.EscapeDataString()` used for user/token IDs in URLs | Open | +| VMID validation | **Good** | `[ValidateRange(100, 999999999)]` on all VmId params | Open | +| URL encoding | **Good** | `Uri.EscapeDataString()` applied consistently across all services (VmService, ContainerService, StorageService, NetworkService, FirewallService, UserService, BackupService, PoolService, TaskService, SnapshotService, ClusterService) | Fixed | | API token format | **Good** | Regex validation in `PveAuthenticator` | Open | -| Path parameters | **Low risk** | Node/snapshot names not URL-encoded but come from validated sources | Open | +| Port validation | **Good** | `[ValidateRange(1, 65535)]` on port parameter | Open | ### 5.4 Dependency Security | Package | Version | Pinned | Notes | Prior Status | |---|---|---|---|---| -| `Newtonsoft.Json` | 13.0.3 | Yes | Latest stable, no known CVEs | Open | +| `Newtonsoft.Json` | 13.0.3 | Yes | Latest stable | Open | | `System.Text.Json` | 8.0.5 | Yes | Latest 8.x patch | Open | -| `SharpCompress` | 0.38.0 | Yes | OVA/tar extraction support | New | +| `SharpCompress` | 0.38.0 | Yes | OVA/tar extraction | Open | | `PowerShellStandard.Library` | 5.1.1 | Yes | PrivateAssets=all | Open | | `System.Management.Automation` | 7.4.0 | Yes | PrivateAssets=all | Open | -All dependencies are pinned to exact versions. - -### 5.5 Secret Scanning - -No hardcoded credentials, tokens, or sensitive IPs found in source code. CI workflow uses GitHub Actions secrets with `::add-mask::` for test passwords. - ### Security Findings | ID | Area | File | Severity | Description | Prior Status | |---|---|---|---|---|---| -| S1 | Input validation | `SnapshotService.cs`, `ContainerService.cs` | Low | Snapshot names and node names in URL paths are not URL-encoded — low risk as values come from validated/system sources | New | +| S1 | Hardcoded password | `integration-tests.yml:42` | **Low** | `PVE_PASSWORD: "Testpass123!"` in workflow env. Masked via `::add-mask::`. Disposable test VM. | Open | +| S2 | Hardcoded password | `variables.tf:80` | **Low** | Default `"Testpass123!"` for test VM password. Disposable nested VM. | Open | + +### Fixed Since Prior Scan + +| Prior ID | Description | Status | +|---|---|---| +| S1 | `SetPveVmGuestPasswordCmdlet.cs` Password is `string`, not `SecureString` | **Fixed** — now uses `SecureString` | +| S2 | `debug/Capture-UploadDiff.ps1` hardcoded API token and IP | **Fixed** — replaced with placeholder tokens ``, `@!=` | +| S3 | URL encoding missing in VmService, ContainerService, StorageService | **Fixed** — `Uri.EscapeDataString()` now used consistently across all services | --- ## Phase 6 — PSGallery Publication Readiness -### 6.1 Module Manifest Assessment - -| Field | Status | Value | Prior Status | -|---|---|---|---| -| `RootModule` | Pass | `PSProxmoxVE.dll` | Open | -| `ModuleVersion` | Pass | `0.1.0` | Open | -| `GUID` | Pass | `a3f7c2d1-84e5-4b9f-a061-3e2d8c5f1a7b` | Open | -| `Author` | Pass | `goodolclint` | Open | -| `CompanyName` | Pass | `Worklab` | Open | -| `Copyright` | Pass | `(c) 2026 goodolclint. All rights reserved.` | Open | -| `Description` | Pass | Meaningful | Open | -| `PowerShellVersion` | Pass | `5.1` | Open | -| `CompatiblePSEditions` | Pass | `Desktop`, `Core` | Open | -| `DotNetFrameworkVersion` | Pass | `4.8` | Open | -| `RequiredAssemblies` | Pass | Lists Core DLL and Newtonsoft.Json | Open | -| `FormatsToProcess` | Pass | `PSProxmoxVE.format.ps1xml` | Open | -| `CmdletsToExport` | Pass | Explicit list of all cmdlets | Open | -| `FunctionsToExport` | Pass | Empty (binary module) | Open | -| `AliasesToExport` | Pass | 7 aliases defined | New | -| `Prerelease` | Pass | `preview` | Open | -| `Tags` | Pass | 8 relevant tags | Open | -| `LicenseUri` | Pass | GitHub LICENSE | Open | -| `ProjectUri` | Pass | GitHub repo | Open | -| `HelpInfoURI` | Pass | `docs/cmdlets` | Fixed | -| `ReleaseNotes` | Pass | Present in PSData | Fixed | -| `IconUri` | **Fail** | Not present | Open | - -### 6.2 License - -| Check | Pass/Fail | Notes | Prior Status | -|---|---|---|---| -| LICENSE file present | Pass | MIT License at repo root | Open | -| OSI-approved license | Pass | MIT | Open | -| LicenseUri in manifest | Pass | Points to GitHub LICENSE | Open | - -### 6.3 README Quality - -| Check | Pass/Fail | Notes | Prior Status | -|---|---|---|---| -| Installation instructions | Pass | `Install-Module PSProxmoxVE` | Fixed | -| Quick-start examples | Pass | Multiple scenarios | Open | -| Authentication guide | Pass | Ticket + API token documented | Open | -| Badges | Pass | Build, Unit Tests, License badges | Fixed | -| Cmdlet reference | Pass | All cmdlets listed | Open | -| Known limitations | Pass | Documented | Open | -| Contributing section | Pass | References CONTRIBUTING.md | Open | - -### 6.4 Build & Publish Pipeline - -| Check | Pass/Fail | Notes | Prior Status | -|---|---|---|---| -| Build workflow | Pass | net48 + net9.0, Windows + Ubuntu | Open | -| Unit test workflow | Pass | PS 5.1/7.5, Windows/Ubuntu/macOS | Open | -| Integration test workflow | Pass | PVE 8 + PVE 9 | Open | -| Publish workflow | Pass | Tag-triggered, PSGallery + GitHub Release | Fixed | -| API key handling | Pass | Via `PSGALLERY_API_KEY` secret | Fixed | - -### 6.5 Versioning - -| Check | Pass/Fail | Notes | Prior Status | -|---|---|---|---| -| CHANGELOG format | Pass | Keep a Changelog with versioned entry | Open | -| Versioned release | Pass | `0.1.0-preview` entry present | Fixed | -| Commit conventions | Pass | Conventional Commits used consistently | Open | - -### PSGallery Readiness Checklist - -| Check | Pass/Fail | Notes | Prior Status | -|---|---|---|---| -| Module manifest complete | Pass | All required fields populated | Open | -| License present and OSI-approved | Pass | MIT | Open | -| README with install instructions | Pass | PSGallery install documented | Fixed | -| Publish pipeline configured | Pass | `publish.yml` on tag push | Fixed | -| ReleaseNotes present | Pass | In PSData section | Fixed | -| IconUri | **Fail** | Missing — cosmetic only | Open | -| MAML help file | Pass | `PSProxmoxVE.dll-Help.xml` present | New | +| # | Check | Pass/Fail | Notes | Prior Status | +|---|---|---|---|---| +| 1 | ModuleVersion is SemVer | **Pass** | `0.1.0` | Open | +| 2 | GUID present and stable | **Pass** | `a3f7c2d1-84e5-4b9f-a061-3e2d8c5f1a7b` | Open | +| 3 | Author | **Pass** | `goodolclint` | Open | +| 4 | CompanyName | **Pass** | `Worklab` | Open | +| 5 | Copyright | **Pass** | `(c) 2026 goodolclint. All rights reserved.` | Open | +| 6 | Description (meaningful) | **Pass** | Mentions PVE 8.x/9.x and feature areas | Open | +| 7 | PowerShellVersion minimum | **Pass** | `5.1` | Open | +| 8 | RequiredAssemblies | **Pass** | `PSProxmoxVE.Core.dll`, `Newtonsoft.Json.dll` | Open | +| 9 | CmdletsToExport explicit list | **Pass** | Fully enumerated (169 cmdlets), no wildcards | Open | +| 10 | FunctionsToExport | **Pass** | Explicitly `@()` | Open | +| 11 | Tags for discoverability | **Pass** | 8 tags incl. Proxmox, PVE, Virtualization, IaC | Open | +| 12 | ProjectUri | **Pass** | GitHub repo URL | Open | +| 13 | LicenseUri | **Pass** | Points to LICENSE | Open | +| 14 | IconUri | **Fail** | Not set — cosmetic | Open | +| 15 | ReleaseNotes | **Pass** | In PSData | Open | +| 16 | Prerelease string | **Pass** | `'preview'` | Open | +| 17 | LICENSE (OSI-approved) | **Pass** | MIT | Open | +| 18 | README: Install-Module | **Pass** | `Install-Module -Name PSProxmoxVE` documented | Open | +| 19 | README: Quick-start examples | **Pass** | Multiple scenarios | Open | +| 20 | README: Authentication guide | **Pass** | Ticket + API token | Open | +| 21 | README: Badges | **Pass** | Build, Unit Tests, License, PSGallery | Open | +| 22 | Publish workflow | **Pass** | Tag-triggered via `publish.yml` | Open | +| 23 | Publish: version stamping | **Pass** | Extracts from git tag | Open | +| 24 | Publish: smoke test | **Pass** | Loads module, asserts cmdlet count | Open | +| 25 | Publish: API key as secret | **Pass** | `secrets.PSGALLERY_API_KEY` | Open | +| 26 | Publish: GitHub Release | **Pass** | Auto-created via `softprops/action-gh-release` | Open | +| 27 | CHANGELOG | **Pass** | Keep a Changelog format + Conventional Commits | Open | +| 28 | Publish: single netstandard2.0 | **Warning** | Only builds netstandard2.0. Verify it loads under Win PS 5.1 since manifest declares `DotNetFrameworkVersion = '4.8'`. | Open | --- ## Phase 7 — Community & Repo Maintenance Standards -| Check | Pass/Fail | Notes | Prior Status | -|---|---|---|---| -| `README.md` | Pass | Comprehensive with badges | Open | -| `LICENSE` | Pass | MIT | Open | -| `CHANGELOG.md` | Pass | Keep a Changelog format with versioned entry | Open | -| `CONTRIBUTING.md` | Pass | Dev setup, coding standards, test instructions, PR process | Fixed | -| `CODE_OF_CONDUCT.md` | Pass | Contributor Covenant v2.1 | Fixed | -| `SECURITY.md` | Pass | Vulnerability disclosure policy, 48h response SLA | Fixed | -| `.editorconfig` | Pass | Comprehensive rules | Open | -| `.gitignore` | Pass | Build outputs, IDE files, test results | Open | -| `.gitattributes` | Pass | Line ending normalization, binary detection | Fixed | -| Issue templates | Pass | Bug report + feature request (YAML format) | Fixed | -| PR template | Pass | Summary, type checkboxes, checklist | Fixed | -| Conventional commits | Pass | Consistent in recent history | Open | -| Default branch `main` | Pass | Confirmed | Open | -| API coverage documentation | Pass | `docs/PVE_API_COVERAGE.md` | New | -| Cmdlet documentation | Pass | 75 files in `docs/cmdlets/` | New | +| # | Check | Pass/Fail | Notes | Prior Status | +|---|---|---|---|---| +| 1 | Bug report issue template | **Pass** | YAML form with module/PVE/PS version fields | Open | +| 2 | Feature request template | **Pass** | Description, use case, API endpoints | Open | +| 3 | PR template | **Pass** | Summary, type checkboxes, testing checklist | Open | +| 4 | `CONTRIBUTING.md` | **Pass** | Prerequisites, building, tests, coding standards, PR process | Open | +| 5 | `CODE_OF_CONDUCT.md` | **Pass** | Contributor Covenant 2.1 | Open | +| 6 | `SECURITY.md` | **Pass** | Vulnerability disclosure, 48h SLA | Open | +| 7 | Conventional commits | **Pass** | Consistent `feat:`, `fix:`, `fix(test):` in history | Open | +| 8 | `.gitattributes` | **Pass** | Comprehensive: csharp diff, binary markers, LF for .sh | Open | +| 9 | Issue template config.yml | **Fail** | No `config.yml` to control blank issues | Open | +| 10 | CODEOWNERS | **Fail** | Not present. Low priority for single maintainer. | Open | +| 11 | FUNDING.yml / community links | **Fail** | No Discussions/Discord/Funding references | Open | +| 12 | Default branch `main` | **Pass** | Confirmed | Open | -### Recommended Branch Protection (cannot verify via API) +### Recommended Branch Protection - Require PR reviews before merge - Require status checks (build + unit tests + integration tests) @@ -724,35 +812,41 @@ No hardcoded credentials, tokens, or sensitive IPs found in source code. CI work ### 🔴 Critical (blocks PSGallery publication or is a security risk) -**None** — all prior Critical items have been resolved. +| # | What | Where | Why | Fix | Prior Status | +|---|---|---|---|---|---| +| C1 | **Infinite loop in 5 cmdlets' WaitForTask methods** | `NewPveContainerSnapshotCmdlet.cs`, `RemovePveContainerSnapshotCmdlet.cs`, `RestorePveContainerSnapshotCmdlet.cs`, `InvokePveStorageDownloadCmdlet.cs`, `SendPveFileCmdlet.cs` | `while(true)` with no timeout — cmdlet hangs forever if task stalls. Same pattern that was fixed in VM snapshot cmdlets. | Replace private `WaitForTask` with `TaskService.WaitForTask` (identical fix to CQ1–CQ4 from prior scan) | New | ### 🟠 High (significantly impacts quality or community adoption) | # | What | Where | Why | Fix | Prior Status | |---|---|---|---|---|---| -| H1 | **No firewall cmdlets** | Module | High-value gap for security automation — most requested feature area | Implement firewall rule CRUD for cluster/node/VM | Open | -| H2 | **No backup/vzdump cmdlets** | Module | Essential for DR automation scenarios | Implement vzdump and backup schedule management | Open | +| H1 | **HA subsystem (0% coverage)** | Module | 21 endpoints + 5 🆕 PVE 9.0, essential for production clusters | Implement HA resources, groups, status, and PVE 9.0 rules | Open | +| H2 | **Ceph subsystem (0% coverage)** | Module | 40 endpoints, critical for hyperconverged infrastructure | Implement Ceph OSD, MON, pool, status operations | Open | +| H3 | **net9.0 target is EOL** | Both `.csproj` files | .NET 9.0 EOL was May 2025. Should use LTS release. | Upgrade to `net10.0` | Open | ### 🟡 Medium (best practice gaps, test coverage holes) | # | What | Where | Why | Fix | Prior Status | |---|---|---|---|---|---| -| M1 | `Remove-PveRole` missing `ConfirmImpact.High` | `RemovePveRoleCmdlet.cs:13` | Deleting roles is a significant, potentially disruptive operation | Add `ConfirmImpact = ConfirmImpact.High` | New | -| M2 | No pool management | Module | Useful for multi-tenant environments | Implement pool CRUD | Open | -| M3 | `Move-PveVm` / `Move-PveContainer` lack integration tests | Integration tests | Migration is untested end-to-end (requires multi-node) | Add integration tests when multi-node CI is available | Open | -| M4 | `Import-PveOva` lacks integration test | Integration tests | OVA import only tested at parameter level | Add to integration tests (requires OVA test asset) | New | -| M5 | URL-encode snapshot/node names in API paths | `SnapshotService.cs`, `ContainerService.cs` | Defense-in-depth against path injection (low risk) | Apply `Uri.EscapeDataString()` to path parameters | New | +| M1 | **HttpClient per-call pattern** | 38 cmdlet files + services | 38 cmdlets create `new PveHttpClient` per operation, bypassing connection pooling | Refactor to client-per-session or use `IHttpClientFactory` pattern | Open | +| M2 | **64 cmdlets lack integration tests** | Integration tests | 38% of cmdlets untested end-to-end | Prioritize pools, groups, domains, node ops, container gaps, guest agent extensions | Open | +| M3 | **VM/CT-level firewall (0% coverage)** | Module | ~44 endpoints. Cluster/node-level covered but per-VM/CT rules are not. | Add VM/CT firewall cmdlets using existing firewall service patterns | Open | +| M4 | **Cluster config (0% coverage)** | Module | 10 endpoints for cluster create/join/node management | Implement for multi-node cluster automation | Open | +| M5 | **PVE 9.0 endpoints (0% coverage)** | Module | 42 new endpoints in latest PVE version. Users on PVE 9 will expect support. | Prioritize HA rules, bulk actions, SDN fabrics | New | +| M6 | **Broad exception catches** | `PveCmdletBase.cs`, `VmService.cs`, `ContainerService.cs` | Filtered `when` clauses are better than bare catches but still catch broadly | Consider more specific exception types | Open | ### 🟢 Low / Nice-to-have (polish, discoverability, stretch goals) | # | What | Where | Why | Fix | Prior Status | |---|---|---|---|---|---| -| L1 | No `IconUri` in manifest | `PSProxmoxVE.psd1` | Improves PSGallery listing appearance | Create module icon, host it, add URI | Open | -| L2 | Auth header magic strings | `PveHttpClient.cs:316-323` | Minor code quality — could be named constants | Extract to `const string` fields | Open | -| L3 | HA/Ceph/Replication cmdlets | Module | Lower-priority API coverage gaps | Implement as demand grows | Open | -| L4 | Access groups/domains cmdlets | Module | Useful for LDAP/AD integration | Implement `/access/groups`, `/access/domains` CRUD | Open | -| L5 | PSGallery version badge | `README.md` | Shows published version to visitors | Add PSGallery badge after first publish | New | -| L6 | SDN IPAM/DNS/Controller cmdlets | Module | Complete SDN management surface | Implement as demand grows | New | +| L1 | No `IconUri` in manifest | `PSProxmoxVE.psd1` | Improves PSGallery listing appearance | Create icon, host it, add URI | Open | +| L2 | `System.Management.Automation` pinned to 7.4.0 | `PSProxmoxVE.csproj` | Consider updating when targeting .NET 10 | Update with TFM migration | Open | +| L3 | No `config.yml` for issue templates | `.github/ISSUE_TEMPLATE/` | Controls blank issue creation | Add simple config.yml | Open | +| L4 | No CODEOWNERS | Repo root | Documents ownership for PR auto-assignment | Add `CODEOWNERS` with maintainer | Open | +| L5 | Disk management (0% coverage) | Module | 18 endpoints — LVM, ZFS, SMART, wipe | Implement as demand grows | Open | +| L6 | Notifications (0% coverage) | Module | 32 endpoints — new in PVE 8.1+ | Implement for monitoring automation | Open | +| L7 | ACME/Certificates (0% coverage) | Module | 23 endpoints — TLS cert management | Implement as demand grows | Open | +| L8 | Verify netstandard2.0 loads on PS 5.1 | Publish workflow | Manifest says Desktop compatible | Add PS 5.1 smoke test to publish workflow | Open | --- @@ -760,20 +854,25 @@ No hardcoded credentials, tokens, or sensitive IPs found in source code. CI work | Metric | This Scan | Prior Scan | Delta | |---|---|---|---| -| Total cmdlets | 83 | 66 | +17 | +| Total cmdlets | 169 | 148 | +21 | | Cmdlets with ShouldProcess | All mutating | All mutating | — | -| Cmdlets with OutputType | 83 (100%) | 66 (100%) | — | -| Cmdlets with HelpMessage | 83 (100%) | 0 (0%) | +83 | -| xUnit test files | 13 | 13 | — | -| Pester test files | 30 | 26 | +4 | -| Integration test coverage | 84% | ~70% | +14% | -| PVE API areas covered | 12 of ~20 | 10 of ~20 | +2 | -| Critical issues | 0 | 3 | -3 | -| High issues | 2 | 8 | -6 | -| Medium issues | 5 | 10 | -5 | -| Low issues | 6 | 10 | -4 | -| **Total issues** | **13** | **31** | **-18** | -| NuGet dependencies (runtime) | 3 | 2 | +1 (SharpCompress) | +| Cmdlets with OutputType | 169 (100%) | ~94 (~64%) | +36% | +| Cmdlets sealed | 169 (100%) | ~60 (~40%) | +60% | +| Cmdlets with HelpMessage | 169 (100%) | 148 (100%) | — | +| xUnit test files | 15 | 15 | — | +| Pester test files | ~48 | ~45 | +3 | +| Integration test coverage | ~62% (105/169) | ~57% (85/148) | +5% | +| PVE API areas with >0% coverage | 17 of 30 | 17 of 29 | — | +| PVE API total coverage | 27.9% (180/646) | 27.9% (180/646) | — | +| Critical issues | 1 | 2 | -1 | +| High issues | 3 | 5 | -2 | +| Medium issues | 7 | 10 | -3 | +| Low issues | 7 | 11 | -4 | +| **Total issues** | **18** | **28** | **-10** | +| Issues fixed from prior | 16 | 9 | +7 | +| New issues found | 5 | 16 | -11 | +| NuGet dependencies (runtime) | 3 | 3 | — | | Security vulnerabilities | 0 | 0 | — | -| Community files present | 9/9 | 3/9 | +6 | -| Documentation files | 78 | 1 | +77 | +| Community files present | 9/9 | 9/9 | — | + +\* Integration test coverage improved both in absolute count (+20 cmdlets) and percentage (+5%). diff --git a/docs/review/REVIEW_REPORT.md b/docs/review/REVIEW_REPORT.md new file mode 100644 index 0000000..3302110 --- /dev/null +++ b/docs/review/REVIEW_REPORT.md @@ -0,0 +1,313 @@ +# PSProxmoxVE Comprehensive Review Report + +``` +Scan date: 2026-03-22 +Prior report date: 2026-03-22 (scan-3) +PVE API spec date: 2026-03-21T15:04:50.641Z +PVE API spec SHA256: 4af79be30166209a4714b771f65e1e9540c5b738f414ff30c98454402e29d030 +PVE version hint: (not set in spec) +Total API endpoints: 646 +Findings DB: docs/review/findings.json (F001–F076) +Open findings: 22 (before scan) → 27 (after scan) +New this scan: 6 Resolved this scan: 1 Regressed: 0 +``` + +## Executive Summary + +- **Delta**: 1 resolved | 6 new | 0 regressed | 27 open +- **API drift**: 0 breaking changes in PVE 9.x affecting current cmdlets | 42 new PVE 9.0 endpoints unimplemented +- **API coverage**: 169 cmdlets covering ~180 of 646 endpoints (28%) +- **Critical open**: F058 — 5 cmdlets with infinite-loop task polling (D001 violation) +- **New high**: F073 — build.yml references net9.0 but test project targets net10.0 (workflow will fail) +- **New medium**: F071 — ~15 cmdlets missing Uri.EscapeDataString (D003 violation) +- **Resolved**: F040 — Broad exception catches now properly filtered +- **All D007/D005/D008/D011/D012 decisions hold**: sealed, OutputType, Newtonsoft-only, verb constants all pass 169/169 +- **Security**: All password params use SecureString, all service URLs properly encoded, no credential logging +- **Testing**: 100% Pester parameter validation, ~105/169 integration coverage + +--- + +## Phase 1 — Repository Inventory & Structure + +### Project Layout + +| Component | Path | Framework | +|---|---|---| +| PSProxmoxVE (cmdlets) | `src/PSProxmoxVE/` | netstandard2.0; net9.0; net48 | +| PSProxmoxVE.Core (services) | `src/PSProxmoxVE.Core/` | netstandard2.0; net9.0; net48 | +| xUnit Tests | `tests/PSProxmoxVE.Core.Tests/` | net10.0; net48 | +| Pester Tests | `tests/PSProxmoxVE.Tests/` | PowerShell 5.1+ | +| Integration Infra | `tests/infrastructure/` | Terraform + Docker | + +### CI/CD Workflows + +| Workflow | Trigger | Status | +|---|---|---| +| build.yml | push/PR to main | ⚠ References net9.0 (F073) | +| unit-tests.yml | push/PR to main | ✓ Matrix: Win PS 5.1, Win PS 7.5, Ubuntu PS 7.5, macOS PS 7.5 | +| integration-tests.yml | push/PR + dispatch | ✓ Self-hosted runner, nested PVE 8 + PVE 9 | +| publish.yml | tag push `v*` | ⚠ Low smoke threshold (F074) | + +### Documentation + +| File | Present | Notes | +|---|---|---| +| README.md | ✓ | Comprehensive with badges, cmdlet reference | +| CHANGELOG.md | ✓ | Keep a Changelog format | +| CONTRIBUTING.md | ✓ | Dev setup, coding standards, PR process | +| LICENSE | ✓ | MIT | +| CODE_OF_CONDUCT.md | ✓ | Contributor Covenant adapted | +| SECURITY.md | ✓ | Vulnerability disclosure + response SLA | +| DECISIONS.md | ✓ | 12 decisions (D001–D012) | +| .editorconfig | ✓ | 4-space C#/PS, 2-space XML/JSON | +| .gitignore | ✓ | | +| .gitattributes | ✓ | | + +### Missing Items + +| Finding ID | Item | Notes | +|---|---|---| +| F075 | ~88 cmdlets lack markdown help docs | 81 of 169 covered | +| F065 | No config.yml for issue templates | | +| F066 | No CODEOWNERS file | | +| F076 | No dependabot/renovate | | + +--- + +## Phase 2 — PVE API Coverage Audit + +### Coverage by Functional Area + +| Area | Total Endpoints | Covered | % | Notes | +|---|---|---|---|---| +| backup | ~6 | ~6 | 100% | Full coverage | +| tasks | ~5 | ~5 | 100% | Full coverage | +| storage_config | ~5 | ~4 | 80% | | +| access_domains | ~6 | ~5 | 83% | | +| roles | ~5 | ~4 | 80% | | +| access_groups | ~5 | ~4 | 80% | | +| firewall | ~40 | ~30 | 75% | Cluster-level excellent | +| networking | ~7 | ~5 | 71% | | +| pools | ~7 | ~5 | 71% | | +| users | ~12 | ~8 | 67% | | +| storage | ~19 | ~12 | 63% | | +| sdn | ~59 | ~25 | 42% | Missing fabrics (PVE 9.0) | +| containers | ~62 | ~25 | 40% | Missing VNC/SPICE, firewall | +| vms | ~97 | ~35 | 36% | Missing VNC/SPICE, RRD, agent sub-commands | +| access | ~15 | ~3 | 20% | Missing TFA, OpenID | +| nodes | ~58 | ~10 | 17% | Missing Ceph, disks, apt, services | +| cluster | ~81 | ~3 | 4% | Only resources/status | +| **ha** | **~21** | **0** | **0%** | F053 | +| **ceph** | **~40** | **0** | **0%** | F054 | +| **cluster_config** | **~10** | **0** | **0%** | F060 | +| **disks** | **~18** | **0** | **0%** | F067 | +| **notifications** | **~32** | **0** | **0%** | F068 | +| **acme/certs** | **~23** | **0** | **0%** | F069 | +| **replication** | **~5** | **0** | **0%** | | +| **services** | **~7** | **0** | **0%** | | +| **TOTAL** | **646** | **~180** | **28%** | | + +### PVE 9.0 New Endpoints (F061) + +42 new endpoints in PVE 9.0 — **0 implemented**: + +- **SDN Fabrics** (~17 endpoints) — entirely new subsystem for fabric networking +- **Cluster Bulk Actions** (~6 endpoints) — cluster-wide guest start/shutdown/suspend/migrate +- **HA Affinity Rules** (~5 endpoints) — new HA group affinity management +- **Miscellaneous** (~14 endpoints) — scattered across nodes, storage, access + +### API Drift + +No breaking changes detected in PVE 9.x affecting currently implemented cmdlets. All existing cmdlets should continue to work against PVE 9.x servers. + +### High-Value Gaps + +1. **HA (High Availability)** — 21 endpoints, essential for production clusters +2. **Ceph** — 40 endpoints, critical for hyperconverged infrastructure +3. **Cluster Configuration** — 10 endpoints for cluster create/join +4. **VNC/SPICE Console Access** — frequently requested for remote management +5. **Cluster Notifications** — new in PVE 8.1+, essential for monitoring automation +6. **`GET /cluster/nextid`** — critical for scripted VM creation + +--- + +## Phase 3 — Code Quality & Best Practices + +### DECISIONS.md Compliance + +| Decision | Rule | Status | Notes | +|---|---|---|---| +| D001 | TaskService.WaitForTask | ⛔ VIOLATION | F058: 5 cmdlets still use while(true) | +| D002 | SecureString for passwords | ✓ PASS | All 7 password cmdlets | +| D003 | Uri.EscapeDataString | ⛔ VIOLATION | F071: ~15 cmdlets bypass services | +| D004 | No bare catch blocks | ✓ PASS | Zero instances in src/ | +| D005 | OutputType on all cmdlets | ✓ PASS | 169/169 | +| D006 | ConfirmImpact.High | ⚠ PARTIAL | F062, F063: container Restart/Suspend | +| D007 | All cmdlets sealed | ✓ PASS | 169/169 | +| D008 | Newtonsoft.Json only | ✓ PASS (source) | F072: STJ dependency in csproj | +| D009 | netstandard2.0 for publish | ⚠ PARTIAL | F047: net9.0 still in targets | +| D010 | VmId nullable + ValidateRange | ✓ PASS | | +| D011 | Verb class constants | ✓ PASS | | +| D012 | Magic strings extracted | ✓ PASS | | + +### Findings + +| Finding ID | File(s) | Severity | Status | Description | +|---|---|---|---|---| +| F058 | 5 container/storage cmdlets | Critical | Open | while(true) infinite loops without timeout (D001) | +| F073 | build.yml, test .csproj | High | **New** | build.yml uses net9.0 but test project targets net10.0 | +| F071 | ~15 cmdlet files | Medium | **New** | Missing Uri.EscapeDataString on inline URL paths (D003) | +| F062 | RestartPveContainerCmdlet.cs | Medium | Open | Missing ConfirmImpact.High (D006) | +| F063 | SuspendPveContainerCmdlet.cs | Medium | Open | Missing ConfirmImpact.High (D006) | +| F047 | Both publishable .csproj | Medium | Open | net9.0 target is EOL (D009) | +| F048 | ~216 call sites | Medium | Open | Sync-over-async — accepted PS 5.1 tradeoff | +| F045 | ~207 new PveHttpClient | Medium | Open | Per-call HTTP client — accepted design | +| F072 | PSProxmoxVE.Core.csproj | Low | **New** | Unnecessary System.Text.Json dependency | +| F064 | PSProxmoxVE.csproj | Low | Open | SMA pinned to 7.4.0 | +| F040 | PveCmdletBase, services | Medium | **Resolved** | Catches now properly filtered with `when` | + +--- + +## Phase 4 — Testing Coverage Analysis + +### Test Inventory + +| Type | Framework | Files | Coverage | +|---|---|---|---| +| xUnit (C#) | xunit 2.7.0 + Moq | 15 test files | Models + authentication only | +| Pester (PS) | Pester 5 | 50 test files | 169/169 cmdlet parameter validation | +| Integration (PS) | Pester 5 | 1 file (1544 lines) | ~105/169 cmdlets | + +### Integration Test Coverage Gaps (F046) + +**~64 cmdlets lack integration tests**, including: + +- **VM ops**: Move-PveVm, Move-PveVmDisk, Remove-PveVmDisk +- **Guest agent**: Get-PveVmGuestOsInfo, Get-PveVmGuestFsInfo, Read/Write-PveVmGuestFile, Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim +- **Storage**: Set-PveStorage, Get-PveStorageStatus, Remove/Set-PveStorageContent, New-PveStorageDisk +- **Access**: Set-PveRole, Set-PveApiToken, Set-PvePassword, all Group/Domain CRUD +- **SDN**: All Set-PveSdn*, Invoke-PveSdnApply, New-PveSdn{Ipam,Dns,Controller} +- **Nodes**: Get/Set-PveNodeConfig, Get/Set-PveNodeDns, Start/Stop-PveNodeVms +- **Pools**: All 4 pool cmdlets +- **Cluster**: Get-PveClusterResource +- **Containers**: Move/Suspend/Resume/Resize, Move-PveContainerVolume, New-PveContainerTemplate +- **Firewall**: Set operations, groups, options, refs +- **Backup**: New-PveBackup, Get-PveBackupInfo +- **Tasks**: Get-PveTaskList, Stop-PveTask + +### Quality Observations + +**Strengths**: 100% Pester parameter coverage, real PVE 9 JSON fixtures in xUnit, well-structured integration tests with cleanup. + +**Weaknesses**: No mock-based service unit tests, xUnit covers only models/auth (no service logic), integration test is a single monolithic file, some tests depend on sequential state. + +--- + +## Phase 5 — Security Review + +| Area | Status | Details | +|---|---|---| +| Password params use SecureString | ✓ PASS | All 7 cmdlets | +| SecureString freed after use | ✓ PASS | try/finally with ZeroFreeGlobalAllocUnicode | +| No credential logging | ✓ PASS | WriteVerbose never logs passwords | +| HTTPS by default | ✓ PASS | All API calls use https:// | +| SkipCertificateCheck opt-in | ✓ PASS | Warning emitted when used | +| Uri.EscapeDataString in services | ✓ PASS | 100+ usages, consistent | +| Uri.EscapeDataString in cmdlets | ⛔ FAIL | F071: ~15 cmdlets bypass services | +| ValidateRange on VmId | ✓ PASS | 90+ instances, consistent | +| No bare catch blocks | ✓ PASS | Zero in src/ | +| F031: Hardcoded test password | ⚠ Accepted | Testpass123! in CI, masked, disposable VM | + +--- + +## Phase 6 — PSGallery Publication Readiness + +| Finding ID | Check | Pass/Fail | Notes | +|---|---|---|---| +| — | ModuleVersion | ✓ Pass | 0.1.0 (publish workflow overrides from tag) | +| — | Author/Company | ✓ Pass | goodolclint / Worklab | +| — | Description | ✓ Pass | Descriptive | +| — | LicenseUri | ✓ Pass | Present | +| — | ProjectUri | ✓ Pass | Present | +| — | Tags | ✓ Pass | 8 tags including ProxmoxVE8/9 | +| — | ReleaseNotes | ✓ Pass | Present | +| — | CmdletsToExport | ✓ Pass | Explicit list (~169) | +| — | CompatiblePSEditions | ✓ Pass | Desktop, Core | +| — | PowerShellVersion | ✓ Pass | 5.1 | +| — | Prerelease | ✓ Pass | 'preview' | +| F021 | IconUri | ✗ Fail | Missing — PSGallery listing will lack icon | +| F047 | netstandard2.0 only for publish | ⚠ Warn | net9.0 in csproj but publish builds netstandard2.0 only | +| F073 | Build workflow alignment | ✗ Fail | build.yml uses net9.0, test project uses net10.0 | +| F074 | Publish smoke test | ⚠ Warn | Threshold 60 too low for 169 cmdlets | +| F070 | PS 5.1 smoke test | ✗ Fail | No Windows PS 5.1 validation before publish | +| F075 | Cmdlet help documentation | ⚠ Warn | 81/169 have markdown docs | + +--- + +## Phase 7 — Community & Repo Maintenance + +| Finding ID | Check | Pass/Fail | Notes | +|---|---|---|---| +| — | Bug report template | ✓ Pass | YAML format, collects PVE/PS/OS versions | +| — | Feature request template | ✓ Pass | YAML format | +| F065 | Issue template config.yml | ✗ Fail | Missing — blank issues uncontrolled | +| — | PR template | ✓ Pass | Comprehensive checklist | +| — | CONTRIBUTING.md | ✓ Pass | Good quality | +| — | CODE_OF_CONDUCT.md | ✓ Pass | Contributor Covenant adapted | +| — | SECURITY.md | ✓ Pass | 48h response SLA | +| — | DECISIONS.md | ✓ Pass | 12 decisions, linked from CLAUDE.md | +| — | Commit conventions | ✓ Pass | Conventional Commits consistently used | +| F066 | CODEOWNERS | ✗ Fail | Missing | +| F076 | Dependency automation | ✗ Fail | No dependabot/renovate | +| — | Branch protection | ? | Cannot verify from CLI | + +--- + +## Phase 9 — Prioritized Recommendations + +### 🔴 Critical + +| Finding ID | What | Where | Why | Fix | +|---|---|---|---|---| +| F058 | Infinite loop task-polling | 5 container/storage cmdlets | Cmdlets hang forever if task stalls | Replace private WaitForTask with TaskService.WaitForTask | + +### 🟠 High + +| Finding ID | What | Where | Why | Fix | +|---|---|---|---|---| +| F073 | build.yml / test project framework mismatch | build.yml, test .csproj | CI workflow will fail | Update build.yml to use net10.0 or remove net9.0 from publishable csproj | +| F053 | HA subsystem 0% coverage | — | Essential for production clusters | Implement HA resource/group CRUD cmdlets | +| F054 | Ceph subsystem 0% coverage | — | Critical for hyperconverged | Implement Ceph OSD/pool/mon cmdlets | + +### 🟡 Medium + +| Finding ID | What | Where | Why | Fix | +|---|---|---|---|---| +| F071 | Missing Uri.EscapeDataString | ~15 cmdlet files | D003 violation, URL injection risk | Add Uri.EscapeDataString to all inline URL paths | +| F062 | RestartPveContainer no ConfirmImpact.High | RestartPveContainerCmdlet.cs | D006 inconsistency | Add ConfirmImpact = ConfirmImpact.High | +| F063 | SuspendPveContainer no ConfirmImpact.High | SuspendPveContainerCmdlet.cs | D006 inconsistency | Add ConfirmImpact = ConfirmImpact.High | +| F047 | net9.0 target framework EOL | Both publishable .csproj | .NET 9 EOL May 2025 | Remove net9.0, keep netstandard2.0 (+ optionally add net10.0) | +| F074 | Publish smoke test threshold too low | publish.yml | Won't catch cmdlet regressions | Raise threshold to ~150 | +| F075 | ~88 cmdlets lack help docs | docs/cmdlets/ | Poor user experience | Generate docs for missing cmdlets | +| F046 | Integration test gaps | Integration.Tests.ps1 | 64 cmdlets untested end-to-end | Prioritize destructive/lifecycle cmdlets | +| F059 | VM/CT-level firewall 0% | — | Per-guest firewall is common use case | Implement VM/CT firewall rule cmdlets | +| F060 | Cluster config 0% | — | Multi-node automation | Implement cluster create/join cmdlets | +| F061 | PVE 9.0 endpoints 0% | — | 42 new endpoints unimplemented | Prioritize bulk actions and SDN fabrics | +| F048 | Sync-over-async | ~216 call sites | Theoretical deadlock risk | Accepted for PS 5.1 compat — no fix needed | +| F045 | HttpClient per-call | ~207 instances | Socket exhaustion risk | Consider IHttpClientFactory long-term | +| F070 | No PS 5.1 smoke test | publish.yml | Desktop compatibility untested | Add Windows PS 5.1 step to publish workflow | + +### 🟢 Low + +| Finding ID | What | Where | Why | Fix | +|---|---|---|---|---| +| F031 | Hardcoded test password | CI workflow, Terraform | Accepted risk for disposable VMs | — | +| F021 | No IconUri | PSProxmoxVE.psd1 | Cosmetic PSGallery listing | Add icon to repo and reference in manifest | +| F064 | SMA pinned to 7.4.0 | PSProxmoxVE.csproj | Review with net10.0 migration | Update when removing net9.0 | +| F072 | Unnecessary STJ dependency | Core.csproj | D008: Newtonsoft only | Remove System.Text.Json PackageReference | +| F065 | No issue template config.yml | .github/ISSUE_TEMPLATE/ | Blank issues uncontrolled | Add config.yml | +| F066 | No CODEOWNERS | root | No auto-review assignment | Add CODEOWNERS | +| F067 | Disk management 0% | — | Storage provisioning | Implement when needed | +| F068 | Notifications 0% | — | Monitoring automation | Implement when needed | +| F069 | ACME/Certificates 0% | — | TLS automation | Implement when needed | +| F076 | No dependabot/renovate | .github/ | Dependency drift | Add dependabot.yml | diff --git a/docs/review/findings.json b/docs/review/findings.json new file mode 100644 index 0000000..37dcb63 --- /dev/null +++ b/docs/review/findings.json @@ -0,0 +1,2075 @@ +{ + "_schema_version": "1.0", + "_description": "PSProxmoxVE stable findings ledger. IDs are permanent (F001, F002...). Resolved findings are never deleted \u2014 they are marked resolved with evidence. If a finding reappears, it is marked regressed and retains its original ID.", + "last_updated": "2026-03-22T20:00:00Z", + "last_scan_date": "2026-03-22", + "counters": { + "next_id": 77, + "total_open": 27, + "total_resolved": 49, + "total_regressed": 0 + }, + "findings": [ + { + "id": "F001", + "title": "No PSGallery publish workflow", + "category": "psgallery", + "severity": "critical", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + ".github/workflows/" + ], + "description": "No CI/CD workflow for publishing to PSGallery. Cannot automate releases.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "C1", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "publish.yml workflow added, triggered on tag push with Publish-Module and GitHub Release creation", + "verified_by": "scan" + } + }, + { + "id": "F002", + "title": "README says 'not PSGallery'", + "category": "psgallery", + "severity": "critical", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "README.md" + ], + "description": "README installation section says module is not on PSGallery, contradicting publication goal.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "C2", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "README updated with Install-Module PSProxmoxVE instructions", + "verified_by": "scan" + } + }, + { + "id": "F003", + "title": "No ReleaseNotes in manifest PSData", + "category": "psgallery", + "severity": "critical", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/PSProxmoxVE.psd1" + ], + "description": "PSGallery strongly recommends ReleaseNotes in module manifest PSData section.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "C3", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "ReleaseNotes added to PSData section of manifest", + "verified_by": "scan" + } + }, + { + "id": "F004", + "title": "No HelpMessage on any cmdlet parameter", + "category": "code_quality", + "severity": "high", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/" + ], + "description": "Get-Help shows no parameter descriptions because HelpMessage attribute is missing on all [Parameter] attributes.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "H1", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "HelpMessage present on virtually all parameters across all 83 cmdlets", + "verified_by": "scan" + } + }, + { + "id": "F005", + "title": "No WriteVerbose in cmdlets except Connection", + "category": "code_quality", + "severity": "high", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/" + ], + "description": "Users cannot see which API calls are being made. Only Connection cmdlets use WriteVerbose.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "H2", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "WriteVerbose used extensively throughout all cmdlets", + "verified_by": "scan" + } + }, + { + "id": "F006", + "title": "Missing CONTRIBUTING.md", + "category": "community", + "severity": "high", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "CONTRIBUTING.md" + ], + "description": "No contributor guide with dev setup, coding standards, test instructions, or PR process.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "H3", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "CONTRIBUTING.md created with prerequisites, building, tests, coding standards, PR process", + "verified_by": "scan" + } + }, + { + "id": "F007", + "title": "Missing SECURITY.md", + "category": "community", + "severity": "high", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "SECURITY.md" + ], + "description": "No vulnerability disclosure policy.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "H4", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "SECURITY.md created with vulnerability disclosure policy and 48h response SLA", + "verified_by": "scan" + } + }, + { + "id": "F008", + "title": "No WriteWarning on -SkipCertificateCheck", + "category": "security", + "severity": "high", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs" + ], + "description": "Users may not realize they are disabling TLS certificate verification.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "H5", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "WriteWarning emitted with man-in-the-middle advisory when SkipCertificateCheck is used", + "verified_by": "scan" + } + }, + { + "id": "F009", + "title": "Reset-PveVm uses hardcoded verb string literal", + "category": "code_quality", + "severity": "high", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Vms/ResetPveVmCmdlet.cs" + ], + "description": "Reset-PveVm uses [Cmdlet(\"Reset\", ...)] instead of VerbsCommon.Reset, inconsistent with all other cmdlets.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "H6", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "Now uses VerbsCommon.Reset constant", + "verified_by": "scan" + }, + "decisions_ref": "D012" + }, + { + "id": "F010", + "title": "Missing GitHub issue and PR templates", + "category": "community", + "severity": "high", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + ".github/ISSUE_TEMPLATE/", + ".github/pull_request_template.md" + ], + "description": "No structured issue reporting or PR process templates.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "H8", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "Bug report (YAML), feature request (YAML), and PR template added", + "verified_by": "scan" + } + }, + { + "id": "F011", + "title": "Stop-PveVm and Reset-PveVm lack ConfirmImpact.High", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Vms/StopPveVmCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Vms/ResetPveVmCmdlet.cs" + ], + "description": "Stopping or resetting VMs can cause data loss but these cmdlets don't prompt for confirmation by default.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M1", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "ConfirmImpact.High added to both Stop-PveVm and Reset-PveVm", + "verified_by": "scan" + }, + "decisions_ref": "D007" + }, + { + "id": "F012", + "title": "No ValidateRange on VmId parameters", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/" + ], + "description": "PVE requires VMID 100-999999999 but no ValidateRange attribute enforces this.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M2", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "ValidateRange(100, 999999999) added to all VmId parameters", + "verified_by": "scan" + }, + "decisions_ref": "D011" + }, + { + "id": "F013", + "title": "Container snapshots not supported", + "category": "api_coverage", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Containers/" + ], + "description": "Snapshots are only implemented for VMs, not LXC containers, despite PVE supporting both.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M3", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "4 container snapshot cmdlets added: Get/New/Remove/Restore-PveContainerSnapshot", + "verified_by": "scan" + } + }, + { + "id": "F014", + "title": "No integration tests for network modifications", + "category": "testing", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "tests/PSProxmoxVE.Tests/Integration/" + ], + "description": "Network CRUD (create/modify/delete/apply) is tested only at Pester level, not end-to-end.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M4", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "Integration tests added for network create/modify/delete/apply with revert", + "verified_by": "scan" + } + }, + { + "id": "F015", + "title": "No CODE_OF_CONDUCT.md", + "category": "community", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "CODE_OF_CONDUCT.md" + ], + "description": "Expected for open-source projects to establish community standards.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M5", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "Contributor Covenant v2.1 adopted", + "verified_by": "scan" + } + }, + { + "id": "F016", + "title": "No .gitattributes file", + "category": "community", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + ".gitattributes" + ], + "description": "Line ending consistency across platforms not enforced.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M6", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": ".gitattributes added with line ending normalization, csharp diff, binary markers", + "verified_by": "scan" + } + }, + { + "id": "F017", + "title": "CS1591 suppressed in Core project", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj" + ], + "description": "Public API lacks XML documentation because CS1591 warning is suppressed.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M7", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "CS1591 no longer suppressed, XML doc comments added to all public types", + "verified_by": "scan" + } + }, + { + "id": "F018", + "title": "No badges in README", + "category": "psgallery", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "README.md" + ], + "description": "No build status, PSGallery version, or license badges to improve discoverability and trust.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M8", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "Build, Unit Tests, License badges added to README", + "verified_by": "scan" + } + }, + { + "id": "F019", + "title": "CHANGELOG has only [Unreleased] section", + "category": "psgallery", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "CHANGELOG.md" + ], + "description": "No versioned release entries in CHANGELOG, no release history.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M9", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "0.1.0-preview versioned entry added to CHANGELOG", + "verified_by": "scan" + } + }, + { + "id": "F020", + "title": "Invoke-PveVmGuestExec lacks ShouldProcess", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs" + ], + "description": "Executes commands inside a VM (a mutating operation) but does not implement SupportsShouldProcess.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "M10", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "SupportsShouldProcess added; all destructive/mutating cmdlets now implement it", + "verified_by": "scan" + } + }, + { + "id": "F021", + "title": "No IconUri in manifest PSData", + "category": "psgallery", + "severity": "low", + "status": "open", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/PSProxmoxVE.psd1" + ], + "description": "Improves PSGallery listing appearance. Cosmetic only.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L1", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": "L1", + "status": "open" + }, + { + "scan_date": "2026-03-22", + "local_id": "L1", + "status": "open" + }, + { + "scan_date": "2026-03-22", + "local_id": "L1", + "status": "open" + } + ] + }, + { + "id": "F022", + "title": "No online help (MAML/platyPS)", + "category": "psgallery", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/" + ], + "description": "Get-Help -Online does not work. No MAML help file for cmdlet documentation.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L2", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "MAML help file (PSProxmoxVE.dll-Help.xml) present with 75 cmdlet documentation files in docs/cmdlets/", + "verified_by": "scan" + } + }, + { + "id": "F023", + "title": "No firewall cmdlets", + "category": "api_coverage", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Firewall/" + ], + "description": "No cmdlets for creating/managing firewall rules at cluster, node, or VM level. Critical gap for security automation.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L3", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": "H1", + "status": "open" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "21 cluster-level firewall cmdlets added covering rules, groups, aliases, IP sets, options, and refs", + "verified_by": "self-reported" + } + }, + { + "id": "F024", + "title": "No backup/vzdump cmdlets", + "category": "api_coverage", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Backup/" + ], + "description": "Essential for DR automation. No cmdlets for creating backups, managing schedules, or restoring.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L4", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": "H2", + "status": "open" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "6 backup cmdlets added: New-PveBackup, Get/New/Set/Remove-PveBackupJob, Get-PveBackupInfo", + "verified_by": "self-reported" + } + }, + { + "id": "F025", + "title": "No pool management cmdlets", + "category": "api_coverage", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Pools/" + ], + "description": "Required for multi-tenant environments. No cmdlets for creating/managing resource pools.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L5", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": "M2", + "status": "open" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "4 pool cmdlets added: Get/New/Set/Remove-PvePool", + "verified_by": "self-reported" + } + }, + { + "id": "F026", + "title": "new Random() used for multipart boundary generation", + "category": "code_quality", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE.Core/Client/PveHttpClient.cs" + ], + "description": "Not cryptographically secure. Low risk for multipart boundaries but could use RandomNumberGenerator.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L6", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "RandomNumberGenerator now used for boundary generation", + "verified_by": "scan" + } + }, + { + "id": "F027", + "title": "No alias support in module", + "category": "psgallery", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/PSProxmoxVE.psd1" + ], + "description": "Some users prefer short aliases for frequently used cmdlets.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L7", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "7 aliases defined in module manifest AliasesToExport", + "verified_by": "scan" + } + }, + { + "id": "F028", + "title": "No tagged releases or GitHub Releases", + "category": "psgallery", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + ".github/workflows/publish.yml" + ], + "description": "No GitHub Releases configured, no release workflow, no tagged releases in git history.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L8", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "publish.yml creates GitHub Releases via softprops/action-gh-release on tag push", + "verified_by": "scan" + } + }, + { + "id": "F029", + "title": "No container migration cmdlet", + "category": "api_coverage", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Containers/" + ], + "description": "VMs have Move-PveVm for migration but containers have no equivalent.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L9", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "Move-PveContainer cmdlet added for container migration", + "verified_by": "scan" + } + }, + { + "id": "F030", + "title": "No SDN subnet management cmdlets", + "category": "api_coverage", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-19", + "files": [ + "src/PSProxmoxVE/Cmdlets/Network/" + ], + "description": "SDN zones and VNets exist but subnets are missing.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": "L10", + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-21", + "evidence": "3 SDN subnet cmdlets added: Get/New/Remove-PveSdnSubnet", + "verified_by": "scan" + } + }, + { + "id": "F031", + "title": "Hardcoded test password in CI workflow and Terraform", + "category": "security", + "severity": "low", + "status": "open", + "first_detected": "2026-03-19", + "files": [ + ".github/workflows/integration-tests.yml", + "tests/infrastructure/variables.tf" + ], + "description": "PVE_PASSWORD: 'Testpass123!' in integration-tests.yml and variables.tf. Masked via ::add-mask::. Disposable nested test VM. Accepted risk.", + "scan_history": [ + { + "scan_date": "2026-03-19", + "local_id": null, + "status": "new" + }, + { + "scan_date": "2026-03-21", + "local_id": null, + "status": "open" + }, + { + "scan_date": "2026-03-22", + "local_id": "S1/S2", + "status": "open" + } + ] + }, + { + "id": "F032", + "title": "Infinite loop task-polling in VM snapshot and network cmdlets", + "category": "code_quality", + "severity": "critical", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs" + ], + "description": "while(true) with no timeout in InvokePveNetworkApply, NewPveSnapshot, RestorePveSnapshot, RemovePveSnapshot private WaitForTask methods. Cmdlets hang forever if task stalls.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ1-CQ4", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "All four cmdlets now use TaskService.WaitForTask with timeout enforcement, failure detection, and WriteProgress support", + "verified_by": "self-reported" + }, + "decisions_ref": "D001" + }, + { + "id": "F033", + "title": "Infinite poll loop in InvokePveVmGuestExec", + "category": "code_quality", + "severity": "critical", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs" + ], + "description": "while(true) polling exec-status with no timeout. Cmdlet hangs forever if guest exec never completes.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ5", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "Added Timeout parameter with Stopwatch + TimeoutException", + "verified_by": "self-reported" + }, + "decisions_ref": "D001" + }, + { + "id": "F034", + "title": "RemovePveRoleCmdlet missing ConfirmImpact.High", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Users/RemovePveRoleCmdlet.cs" + ], + "description": "Deleting roles is a significant, potentially disruptive operation but does not prompt for confirmation.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ17/M1", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "ConfirmImpact = ConfirmImpact.High added", + "verified_by": "self-reported" + }, + "decisions_ref": "D007" + }, + { + "id": "F035", + "title": "Pool/CephPool parameter conflict in NewPveStorageCmdlet", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Storage/NewPveStorageCmdlet.cs" + ], + "description": "New-PveStorage allows specifying both Pool and CephPool parameters simultaneously, which is invalid.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ6", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "Runtime validation added with ThrowTerminatingError if both Pool and CephPool specified", + "verified_by": "self-reported" + } + }, + { + "id": "F036", + "title": "Duplicated WaitForTask methods across cmdlets", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/" + ], + "description": "4 VM/network cmdlets had copy-pasted private WaitForTask implementations instead of using the shared TaskService.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ7", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "All cmdlets now use TaskService.WaitForTask", + "verified_by": "self-reported" + }, + "decisions_ref": "D001" + }, + { + "id": "F037", + "title": "~54 cmdlets missing OutputType attribute", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/" + ], + "description": "Approximately 54 cmdlets lack [OutputType] attribute, degrading IntelliSense and pipeline type inference.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ9", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "All 169 cmdlets now have [OutputType] attributes", + "verified_by": "self-reported" + }, + "decisions_ref": "D006" + }, + { + "id": "F038", + "title": "Firewall VmId defaults to 0 instead of nullable", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Firewall/" + ], + "description": "Firewall cmdlet VmId parameter uses int with default 0 instead of nullable int, making it impossible to distinguish unset from VM 0.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ10", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "VmId changed to nullable int pattern", + "verified_by": "self-reported" + }, + "decisions_ref": "D011" + }, + { + "id": "F039", + "title": "Bare catch blocks in PveHttpClient, PveCmdletBase, VmService, ContainerService, GetPveVmCmdlet", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE.Core/Client/PveHttpClient.cs", + "src/PSProxmoxVE/Cmdlets/PveCmdletBase.cs", + "src/PSProxmoxVE.Core/Services/VmService.cs", + "src/PSProxmoxVE.Core/Services/ContainerService.cs" + ], + "description": "Multiple bare catch {} blocks swallow exceptions silently, hiding errors and making debugging difficult.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ11-CQ14", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "All bare catches replaced with filtered catch (Exception ex) when (...) clauses that exclude OOM/SOE and are specific to PveApiException/HttpRequestException where appropriate", + "verified_by": "self-reported" + }, + "decisions_ref": "D005" + }, + { + "id": "F040", + "title": "Broad exception catches in status polling", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/PveCmdletBase.cs", + "src/PSProxmoxVE.Core/Services/VmService.cs", + "src/PSProxmoxVE.Core/Services/ContainerService.cs" + ], + "description": "Filtered catch(Exception ex) when(...) clauses are better than bare catches but still catch broadly. Could be more specific exception types.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ8", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "CQ8/M6", + "status": "open" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "report_id": "scan-4", + "verified_by": "scan", + "evidence": "PveCmdletBase.cs:149 uses 'when (ex is not OutOfMemoryException and not StackOverflowException)'. ContainerService.cs:44 and VmService.cs:48 filter to 'PveApiException or HttpRequestException'. All catch(Exception) blocks in cmdlets call ThrowTerminatingError (correct PS pattern). No bare catches found anywhere in src/." + } + }, + { + "id": "F041", + "title": "~95 cmdlets not sealed", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/" + ], + "description": "Approximately 95 cmdlet classes are not sealed, allowing unintended inheritance.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ18", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "All 169 cmdlets are now sealed", + "verified_by": "self-reported" + }, + "decisions_ref": "D008" + }, + { + "id": "F042", + "title": "SuspendPveVmCmdlet missing ConfirmImpact.High", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Vms/SuspendPveVmCmdlet.cs" + ], + "description": "Suspending a VM can cause issues but does not prompt for confirmation.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ19", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "ConfirmImpact.High added to SuspendPveVmCmdlet", + "verified_by": "self-reported" + }, + "decisions_ref": "D007" + }, + { + "id": "F043", + "title": "RestartPveVmCmdlet missing ConfirmImpact declaration", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs" + ], + "description": "Restarting a VM is disruptive but does not set ConfirmImpact.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ20", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "ConfirmImpact.High added to RestartPveVmCmdlet", + "verified_by": "self-reported" + }, + "decisions_ref": "D007" + }, + { + "id": "F044", + "title": "Dual JSON serialization attributes (JsonProperty + JsonPropertyName)", + "category": "code_quality", + "severity": "medium", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE.Core/Models/" + ], + "description": "Model classes have both Newtonsoft [JsonProperty] and System.Text.Json [JsonPropertyName] attributes. Only Newtonsoft is used at runtime.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ21", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "[JsonPropertyName] attributes removed, only [JsonProperty] (Newtonsoft) remains", + "verified_by": "self-reported" + }, + "decisions_ref": "D009" + }, + { + "id": "F045", + "title": "HttpClient per-call pattern bypasses connection pooling", + "category": "code_quality", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/" + ], + "description": "38 cmdlets create new PveHttpClient per operation instead of going through services, bypassing connection pooling.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ11", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "CQ11/M1", + "status": "open" + } + ] + }, + { + "id": "F046", + "title": "Significant integration test coverage gaps", + "category": "testing", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-21", + "files": [ + "tests/PSProxmoxVE.Tests/Integration/" + ], + "description": "Many cmdlets lack end-to-end integration tests. 64 of 169 cmdlets untested in integration as of latest scan.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "M3/M4", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "M2", + "status": "open" + } + ] + }, + { + "id": "F047", + "title": "net9.0 target framework is EOL", + "category": "code_quality", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/PSProxmoxVE.csproj", + "src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj" + ], + "description": ".NET 9.0 reached end of life in May 2025. Should upgrade to net10.0 (LTS).", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ6", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "CQ6/H3", + "status": "open" + } + ], + "decisions_ref": "D010" + }, + { + "id": "F048", + "title": "Sync-over-async via GetAwaiter().GetResult()", + "category": "code_quality", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE.Core/" + ], + "description": "~216 call sites use .GetAwaiter().GetResult(). Standard for PS binary modules but carries theoretical deadlock risk. Accepted tradeoff for PS 5.1 compatibility.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ7", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "CQ7", + "status": "open" + } + ] + }, + { + "id": "F049", + "title": "Auth header names are magic strings", + "category": "code_quality", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE.Core/Client/PveHttpClient.cs" + ], + "description": "Auth header names (PVEAPIToken=, CSRFPreventionToken) are inline string literals instead of named constants.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "CQ2/CQ22", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "Extracted to const string ApiTokenPrefix and CsrfHeaderName fields", + "verified_by": "self-reported" + }, + "decisions_ref": "D013" + }, + { + "id": "F050", + "title": "URL-encode snapshot/node names in API paths", + "category": "security", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE.Core/Services/SnapshotService.cs", + "src/PSProxmoxVE.Core/Services/ContainerService.cs" + ], + "description": "Snapshot names and node names in URL paths are not URL-encoded. Low risk as values come from validated/system sources, but defense-in-depth recommends encoding.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "S1/M5", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "Uri.EscapeDataString() now used consistently across all services", + "verified_by": "self-reported" + }, + "decisions_ref": "D003" + }, + { + "id": "F051", + "title": "SetPveVmGuestPassword uses plain string not SecureString", + "category": "security", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Vms/SetPveVmGuestPasswordCmdlet.cs" + ], + "description": "Guest password parameter is a plain string instead of SecureString, inconsistent with Connect-PveServer's credential handling.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "S1", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "Now uses SecureString with Marshal.SecureStringToGlobalAllocUnicode + ZeroFreeGlobalAllocUnicode in try/finally", + "verified_by": "self-reported" + }, + "decisions_ref": "D002" + }, + { + "id": "F052", + "title": "Debug script contains hardcoded API token and IP", + "category": "security", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "debug/Capture-UploadDiff.ps1" + ], + "description": "debug/Capture-UploadDiff.ps1 contained a hardcoded API token and PVE host IP.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "S2", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "Replaced with placeholder tokens: , @!=", + "verified_by": "self-reported" + } + }, + { + "id": "F053", + "title": "HA subsystem 0% coverage", + "category": "api_coverage", + "severity": "low", + "status": "open", + "first_detected": "2026-03-21", + "files": [], + "description": "21 HA endpoints + 5 new PVE 9.0 endpoints. Essential for production clusters. Resources, groups, status, and rules.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "L3", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "H1", + "status": "open" + } + ] + }, + { + "id": "F054", + "title": "Ceph subsystem 0% coverage", + "category": "api_coverage", + "severity": "low", + "status": "open", + "first_detected": "2026-03-21", + "files": [], + "description": "40 endpoints for OSD, MON, pool, status operations. Critical for hyperconverged infrastructure.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "L3", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "H2", + "status": "open" + } + ] + }, + { + "id": "F055", + "title": "Access groups and domains cmdlets missing", + "category": "api_coverage", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Users/" + ], + "description": "No cmdlets for /access/groups or /access/domains CRUD. Useful for LDAP/AD integration.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "L4", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "8 cmdlets added: Get/New/Set/Remove-PveGroup and Get/New/Set/Remove-PveDomain plus Set-PvePassword", + "verified_by": "self-reported" + } + }, + { + "id": "F056", + "title": "PSGallery version badge missing", + "category": "psgallery", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "README.md" + ], + "description": "README should show PSGallery version badge after first publish.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "L5", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "PSGallery badge present in README badges", + "verified_by": "self-reported" + } + }, + { + "id": "F057", + "title": "SDN IPAM/DNS/Controller cmdlets missing", + "category": "api_coverage", + "severity": "low", + "status": "resolved", + "first_detected": "2026-03-21", + "files": [ + "src/PSProxmoxVE/Cmdlets/Network/" + ], + "description": "SDN zones/VNets/subnets covered but IPAM, DNS, and controller management missing.", + "scan_history": [ + { + "scan_date": "2026-03-21", + "local_id": "L6", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": null, + "status": "fixed" + } + ], + "resolution": { + "scan_date": "2026-03-22", + "evidence": "12 cmdlets added: Get/New/Remove/Set for SdnIpam, SdnDns, SdnController plus Invoke-PveSdnApply", + "verified_by": "self-reported" + } + }, + { + "id": "F058", + "title": "Infinite loop task-polling in container snapshot and storage cmdlets", + "category": "code_quality", + "severity": "critical", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Containers/RestorePveContainerSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Storage/InvokePveStorageDownloadCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Storage/SendPveFileCmdlet.cs" + ], + "description": "while(true) with no timeout in NewPveContainerSnapshot, RemovePveContainerSnapshot, RestorePveContainerSnapshot, InvokePveStorageDownload, and SendPveFile. Same anti-pattern that was fixed in VM snapshot cmdlets. Cmdlets hang forever if task stalls.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "CQ1-CQ5/C1", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "C1", + "status": "open" + } + ], + "decisions_ref": "D001" + }, + { + "id": "F059", + "title": "VM/CT-level firewall 0% coverage", + "category": "api_coverage", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [], + "description": "~44 combined endpoints for per-VM and per-CT firewall rules. Cluster-level is covered (21 cmdlets) but VM/CT-level is not.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "M3", + "status": "new" + } + ] + }, + { + "id": "F060", + "title": "Cluster config 0% coverage", + "category": "api_coverage", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [], + "description": "10 endpoints for cluster create/join/node management. Fundamental for multi-node cluster automation.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "M4", + "status": "new" + } + ] + }, + { + "id": "F061", + "title": "PVE 9.0 endpoints 0% coverage", + "category": "api_drift", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [], + "description": "42 new PVE 9.0 endpoints available (HA rules, SDN fabrics, bulk actions, OCI registry). None implemented.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "M5", + "status": "new" + } + ] + }, + { + "id": "F062", + "title": "RestartPveContainerCmdlet missing ConfirmImpact.High", + "category": "code_quality", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "src/PSProxmoxVE/Cmdlets/Containers/RestartPveContainerCmdlet.cs" + ], + "description": "RestartPveVmCmdlet has ConfirmImpact.High but the container counterpart does not. Inconsistent.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "CQ9", + "status": "new" + } + ], + "decisions_ref": "D007" + }, + { + "id": "F063", + "title": "SuspendPveContainerCmdlet missing ConfirmImpact.High", + "category": "code_quality", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "src/PSProxmoxVE/Cmdlets/Containers/SuspendPveContainerCmdlet.cs" + ], + "description": "SuspendPveVmCmdlet has ConfirmImpact.High but the container counterpart does not. Inconsistent.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "CQ10", + "status": "new" + } + ], + "decisions_ref": "D007" + }, + { + "id": "F064", + "title": "System.Management.Automation pinned to 7.4.0", + "category": "code_quality", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "src/PSProxmoxVE/PSProxmoxVE.csproj" + ], + "description": "Consider updating when migrating to .NET 10 target framework.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "CQ12/L2", + "status": "new" + } + ], + "decisions_ref": "D010" + }, + { + "id": "F065", + "title": "No config.yml for issue templates", + "category": "community", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + ".github/ISSUE_TEMPLATE/" + ], + "description": "No .github/ISSUE_TEMPLATE/config.yml to control blank issue creation.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "L3", + "status": "new" + } + ] + }, + { + "id": "F066", + "title": "No CODEOWNERS file", + "category": "community", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "CODEOWNERS" + ], + "description": "Documents ownership for PR auto-assignment. Low priority for single-maintainer project.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "L4", + "status": "new" + } + ] + }, + { + "id": "F067", + "title": "Disk management 0% coverage", + "category": "api_coverage", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [], + "description": "18 endpoints for LVM, ZFS, SMART, wipe operations. Needed for storage provisioning.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "L5", + "status": "new" + } + ] + }, + { + "id": "F068", + "title": "Notifications subsystem 0% coverage", + "category": "api_coverage", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [], + "description": "32 cluster notification endpoints. New in PVE 8.1+, essential for monitoring/alerting automation.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "L6", + "status": "new" + } + ] + }, + { + "id": "F069", + "title": "ACME/Certificates 0% coverage", + "category": "api_coverage", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [], + "description": "23 combined endpoints for TLS certificate management and ACME.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "L7", + "status": "new" + } + ] + }, + { + "id": "F070", + "title": "Verify netstandard2.0 loads on Windows PowerShell 5.1", + "category": "psgallery", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + ".github/workflows/publish.yml" + ], + "description": "Publish workflow builds only netstandard2.0 but manifest declares Desktop compatible with DotNetFrameworkVersion 4.8. Should add PS 5.1 smoke test.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "L8", + "status": "new" + }, + { + "scan_date": "2026-03-22", + "local_id": "L8", + "status": "open" + } + ] + }, + { + "id": "F071", + "title": "Missing Uri.EscapeDataString in cmdlet URL constructions", + "category": "security", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "src/PSProxmoxVE/Cmdlets/Containers/RestorePveContainerSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Storage/SendPveFileCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Storage/InvokePveStorageDownloadCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/CloudInit/GetPveCloudInitConfigCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Network/GetPveNetworkCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Network/SetPveNetworkCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Network/NewPveNetworkCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/Network/RemovePveNetworkCmdlet.cs", + "src/PSProxmoxVE/Cmdlets/PveCmdletBase.cs" + ], + "description": "~15 cmdlets that bypass services and construct API URLs inline do not use Uri.EscapeDataString() on Node, Name, Storage, Iface path segments. Violates D003. Service layer is consistent but these cmdlets are not.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "CQ-NEW1", + "status": "new" + } + ], + "decisions_ref": "D003" + }, + { + "id": "F072", + "title": "Unnecessary System.Text.Json dependency in Core.csproj", + "category": "code_quality", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj" + ], + "description": "System.Text.Json 8.0.5 referenced for netstandard2.0/net48 targets despite D008 Newtonsoft-only policy. No [JsonPropertyName] attributes or System.Text.Json namespaces in source. Dependency appears unused and adds to assembly surface area.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "CQ-NEW2", + "status": "new" + } + ], + "decisions_ref": "D008" + }, + { + "id": "F073", + "title": "build.yml references net9.0 but test project targets net10.0", + "category": "code_quality", + "severity": "high", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + ".github/workflows/build.yml", + "tests/PSProxmoxVE.Core.Tests/PSProxmoxVE.Core.Tests.csproj" + ], + "description": "build.yml workflow builds and tests with --framework net9.0 but the test project now targets net10.0;net48. The workflow will fail because the test csproj no longer has a net9.0 target.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "PG-NEW1", + "status": "new" + } + ], + "decisions_ref": "D009" + }, + { + "id": "F074", + "title": "Publish workflow smoke test threshold too low", + "category": "psgallery", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + ".github/workflows/publish.yml" + ], + "description": "Publish workflow checks module has >= 60 commands but module exports ~169 cmdlets. Threshold should be ~150 to catch regressions that drop significant cmdlets.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "PG-NEW2", + "status": "new" + } + ] + }, + { + "id": "F075", + "title": "~88 cmdlets lack markdown help documentation", + "category": "psgallery", + "severity": "medium", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + "docs/cmdlets/" + ], + "description": "Only 81 of ~169 exported cmdlets have markdown help docs in docs/cmdlets/. Newer cmdlets added since initial documentation pass are missing.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "PG-NEW3", + "status": "new" + } + ] + }, + { + "id": "F076", + "title": "No dependabot or renovate for dependency updates", + "category": "community", + "severity": "low", + "status": "open", + "first_detected": "2026-03-22", + "files": [ + ".github/" + ], + "description": "No .github/dependabot.yml or renovate.json for automated dependency update PRs. NuGet and GitHub Actions versions will drift without manual monitoring.", + "scan_history": [ + { + "scan_date": "2026-03-22", + "local_id": "CM-NEW1", + "status": "new" + } + ] + } + ] +} \ No newline at end of file