Builds on the vulnerability rework in PR #164. Read CLAUDE.md and .github/instructions/csharp.instructions.md first — the code-style rules there (regions, XML docs on everything, == false instead of !, aligned wrapped parameters, MSTest assertion messages, reihitsu-format ./ before building) are binding.
Background
src/DockerUpdateGuard/Vulnerabilities/TrivyVulnerabilityProvider.cs runs the Trivy CLI in client mode (trivy image --server …) via IProcessRunner (Infrastructure/ProcessRunner). The scan target is built from the image reference (GetScanTarget), including non-Docker-Hub registries as registry/repository:tag[@digest]. However, no registry credentials are ever passed to the Trivy process, so scanning images from private registries fails with an authentication error from Trivy — the finding status ends up Failed with a cryptic CLI excerpt.
Trivy supports registry authentication via the environment variables TRIVY_USERNAME and TRIVY_PASSWORD (single registry), and honors the local Docker config when present. See the Trivy docs on private registries.
Task
1. Configuration
Add per-registry credentials to Configuration/VulnerabilityOptions.cs:
/// <summary>
/// Registry credentials used by the Trivy CLI for private registries
/// </summary>
public List<TrivyRegistryCredentialOptions> TrivyRegistryCredentials { get; set; } = [];
with a new options class Configuration/TrivyRegistryCredentialOptions.cs (one type per file) containing Registry (host, e.g. registry.example.com), Username, Password — all XML-documented. Validate in DockerUpdateGuardOptionsValidator.ValidateVulnerabilityOptions: when Trivy is the enabled provider, every credential entry must have all three values non-blank, and Registry values must be unique (case-insensitive) — follow the duplicate-name pattern used in ValidateDockerInstances.
2. Provider changes
IProcessRunner.RunAsync currently takes no environment variables — check its signature in Infrastructure/ first. If needed, add an optional IReadOnlyDictionary<string, string>? environmentVariables = null parameter to the interface and thread it through ProcessRunner (ProcessStartInfo.Environment[...]). Update all existing callers/substitutes (compile errors will point at them).
- In
TrivyVulnerabilityProvider.GetVulnerabilitiesAsync, when the image's registry (case-insensitive) matches a configured credential entry, pass TRIVY_USERNAME/TRIVY_PASSWORD for the spawned process. Never log the password; do not put credentials into CLI arguments (they would leak into process lists).
- When the registry is not
docker.io and no credential entry matches, behavior stays as today (Trivy may still succeed for public registries).
3. Documentation
- Document the new configuration block with an example in
DOCKER.md (where the other Vulnerabilities settings are documented) and note the security caveat that credentials come from configuration/environment like the other secrets in this app.
- Also document the existing limitation that the Trivy server needs network access to the registries it scans.
Testing
TrivyVulnerabilityProviderTests: extend the IProcessRunner substitute capture to record the environment dictionary; assert (a) matching registry → TRIVY_USERNAME/TRIVY_PASSWORD set with configured values, (b) non-matching registry → no Trivy credential variables passed, (c) docker.io images → no credential variables passed.
DockerUpdateGuardOptionsValidatorTests: incomplete credential entry fails validation with the exact property path in the message; duplicate registries fail; valid setup passes.
- Follow
{Class}{Scenario}{ExpectedResult} naming with assertion messages.
Acceptance criteria
Background
src/DockerUpdateGuard/Vulnerabilities/TrivyVulnerabilityProvider.csruns the Trivy CLI in client mode (trivy image --server …) viaIProcessRunner(Infrastructure/ProcessRunner). The scan target is built from the image reference (GetScanTarget), including non-Docker-Hub registries asregistry/repository:tag[@digest]. However, no registry credentials are ever passed to the Trivy process, so scanning images from private registries fails with an authentication error from Trivy — the finding status ends upFailedwith a cryptic CLI excerpt.Trivy supports registry authentication via the environment variables
TRIVY_USERNAMEandTRIVY_PASSWORD(single registry), and honors the local Docker config when present. See the Trivy docs on private registries.Task
1. Configuration
Add per-registry credentials to
Configuration/VulnerabilityOptions.cs:with a new options class
Configuration/TrivyRegistryCredentialOptions.cs(one type per file) containingRegistry(host, e.g.registry.example.com),Username,Password— all XML-documented. Validate inDockerUpdateGuardOptionsValidator.ValidateVulnerabilityOptions: when Trivy is the enabled provider, every credential entry must have all three values non-blank, andRegistryvalues must be unique (case-insensitive) — follow the duplicate-name pattern used inValidateDockerInstances.2. Provider changes
IProcessRunner.RunAsynccurrently takes no environment variables — check its signature inInfrastructure/first. If needed, add an optionalIReadOnlyDictionary<string, string>? environmentVariables = nullparameter to the interface and thread it throughProcessRunner(ProcessStartInfo.Environment[...]). Update all existing callers/substitutes (compile errors will point at them).TrivyVulnerabilityProvider.GetVulnerabilitiesAsync, when the image's registry (case-insensitive) matches a configured credential entry, passTRIVY_USERNAME/TRIVY_PASSWORDfor the spawned process. Never log the password; do not put credentials into CLI arguments (they would leak into process lists).docker.ioand no credential entry matches, behavior stays as today (Trivy may still succeed for public registries).3. Documentation
DOCKER.md(where the otherVulnerabilitiessettings are documented) and note the security caveat that credentials come from configuration/environment like the other secrets in this app.Testing
TrivyVulnerabilityProviderTests: extend theIProcessRunnersubstitute capture to record the environment dictionary; assert (a) matching registry →TRIVY_USERNAME/TRIVY_PASSWORDset with configured values, (b) non-matching registry → no Trivy credential variables passed, (c) docker.io images → no credential variables passed.DockerUpdateGuardOptionsValidatorTests: incomplete credential entry fails validation with the exact property path in the message; duplicate registries fail; valid setup passes.{Class}{Scenario}{ExpectedResult}naming with assertion messages.Acceptance criteria
reihitsu-format ./clean,dotnet build DockerUpdateGuard.slnx -c Releaseclean, all tests pass.