Files
PSProxmoxVE/docs/decisions/0002-password-parameters-must-use-securestring.md
T
goodolclint-claude[bot] b90791e2bf docs: migrate DECISIONS.md to house-format ADRs, and retire the review folder (#131)
D001-D021 become ADR 0001-0021 in docs/decisions/, one decision per file.
D017's PESTER_VERSION amendment was a second decision in one entry and becomes
ADR 0022. ADR 0023 records the migration and reverses the lane2-change-plan
ruling that deliberately kept DECISIONS.md until the CI lane work landed.

DECISIONS.md is reduced to a stub with a D-to-ADR redirect table, so the four
released CHANGELOG entries and older issue bodies that cite it degrade to a
redirect rather than a dead reference.

docs/review/ and docs/lane2-change-plan.md are deleted (ADR 0024). Of 91
findings, 83 were resolved and six of the seven still open were already GitHub
issues; F021 was the exception and is now #130.

CLAUDE.md's Key Conventions list gains the two rules it was missing and becomes
the checklist, with the ADRs carrying rationale.
2026-09-02 14:49:07 +00:00

1.7 KiB

ADR 0002 — Password parameters must use SecureString

  • Status: Accepted
  • Date: 2026-03-22
  • Deciders: unrecorded; adopted during review scan 2026-03-22
  • Context source: docs/review/findings.json F051

Context

Set-PveVmGuestPassword accepted a plain string password parameter, leaving the credential in managed memory indefinitely.

Connect-PveServer already took a PSCredential, so the module was inconsistent with itself about how sensitive input arrives.

Decision

Every cmdlet parameter that accepts a password is a SecureString, extracted with Marshal.SecureStringToGlobalAllocUnicode and freed with ZeroFreeGlobalAllocUnicode in a finally.

[Parameter(Mandatory = true)]
public SecureString Password { get; set; }

IntPtr ptr = IntPtr.Zero;
try
{
    ptr = Marshal.SecureStringToGlobalAllocUnicode(Password);
    string plainText = Marshal.PtrToStringUni(ptr);
}
finally
{
    if (ptr != IntPtr.Zero)
        Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}

Rejected alternatives

A plain string parameter. It is simpler to write and to test, and it leaves the credential recoverable from a memory dump for the lifetime of the process:

[Parameter(Mandatory = true)]
public string Password { get; set; }

Consequences

The service layer below the cmdlet still receives a plain string — the conversion happens at the cmdlet boundary, and ClusterConfigService.JoinCluster documents that it expects the converted value. The guarantee is about the module's public surface and the window of exposure, not about the credential never existing in managed memory.

A TLS private key is at least as sensitive as a password; anything accepting one is covered by the same rule.