Skip to content

security-guard review: voice subsystem post phase-2 — 2 Low findings #6

@psmon

Description

@psmon

Source

Verdict

Release-pipeline gate: PASS — 0 Critical, 0 High, 0 Medium, 2 Low, 5 Info.
The 5 Info-level items are documented in the log only (per the harness handoff
rule, Info severity does not file an issue). The 2 Low items below are tracked
here for visibility — neither blocks a release, but both warrant follow-up
before the credential surface grows further.

Findings

F-2 — Plaintext credentials at rest (CWE-256)

File: Project/ZeroCommon/Voice/VoiceSettingsStore.cs:39
Affects: %LOCALAPPDATA%\AgentZeroLite\voice-settings.json

Settings persistence writes API keys (SttOpenAIApiKey, TtsOpenAIApiKey)
straight into JSON via JsonSerializer.Serialize + File.WriteAllText.
Same pattern exists in the pre-existing llm-settings.json
(OpenAIApiKey). Voice phase-2 added two more credential slots, raising
the total from one to three across the two side-car files.

Threat model under per-user Windows ACLs:

  • Same user / different process → can read the file (e.g. malicious
    PowerShell, IDE plugin running as the user) — not blocked
  • Backup / cloud sync (OneDrive, Time Machine, Dropbox) → keys leave
    the machine in plaintext — not blocked
  • Disk theft / forensics on a non-TPM machine → keys readable —
    not blocked
  • Different user account on same machine → blocked by NTFS ACL — OK

Proposed remediation — single sweep across both files using DPAPI
with DataProtectionScope.CurrentUser:

// Save
var protectedBytes = ProtectedData.Protect(
    Encoding.UTF8.GetBytes(apiKey), null, DataProtectionScope.CurrentUser);
// Stored as base64 in the JSON, or as a side-car .bin

// Load
var apiKey = Encoding.UTF8.GetString(
    ProtectedData.Unprotect(protectedBytes, null, DataProtectionScope.CurrentUser));

Built-in to .NET (no NuGet add). CurrentUser scope means a backup
exfiltrated to another machine cannot be decrypted there. Same-user
processes can still decrypt — accepted residual risk consistent with how
VS Code / Postman / Notion handle their tokens.

F-3 — Preview package version pin (OWASP A06)

File: Project/AgentZeroWpf/AgentZeroWpf.csproj

<PackageReference Include="System.Speech" Version="10.0.0-preview.3.25171.5" />

System.Speech is pinned to a .NET 10 preview-3 build. Consistent
with the project's <TargetFramework>net10.0-windows</TargetFramework>
preview SDK, but a stable .NET 10 GA will ship a stable counterpart
that should replace this pin in the same commit that updates the SDK.

Proposed remediation — at .NET 10 GA:

  1. Bump <TargetFramework> to the stable net10.0-windows.
  2. Update System.Speech to its matching stable version (likely
    10.0.0 or the first stable patch release).
  3. Re-run build-doctor to confirm no behavioral diff.

Add a one-line tracker in memory/project_gemma4_self_build_lifecycle.md
under the existing self-built-DLL lifecycle so the bump is surfaced when
that file is consulted.

Recommendation

Sequence by effort and trigger:

  1. Defer F-2 until first OpenAI key actually entered. The current
    voice-settings.json on the dev machine has both Stt/Tts OpenAI key
    slots empty — there is nothing to leak today. The DPAPI sweep should
    land in the same commit that the user first enables an OpenAI STT or
    TTS provider, so the migration is exercised on a non-empty file.

  2. F-3 on .NET 10 GA day. No action until the stable SDK is
    available; tracking line in the memory file is enough.

Both items are independent — fixing one does not require touching the
other.

Closes-when

  • F-2 — DPAPI-protect the credential fields in voice-settings.json
    AND llm-settings.json (single sweep, both files), and a load-time
    migration path for any existing plaintext entries.
  • F-3 — System.Speech bumped to the stable .NET 10 GA version,
    verified by build-doctor.
  • Both items tracked in memory/ with an absolute-date line so
    future security passes know where to look.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions