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/DockerScoutVulnerabilityProvider.cs fetches
GET {DockerScoutBaseUrl}/v1/repositories/{ns}/{repo}/tags/{tag}/vulnerabilities
and deserializes the response into Vulnerabilities/Data/ScoutVulnerabilityResponse.cs, which only reads the vulnerabilities array. If the API paginates (common for images with hundreds of CVEs), everything after the first page is silently dropped — the stored finding count would be wrong without any error.
Task
- Verify the actual response shape first. Call the endpoint against a real repository with many CVEs (any public image with a large CVE count) using the configured Docker Hub credentials flow, and inspect the raw JSON. Document what you find in the PR description. Expected possibilities:
- a cursor/link field in the body (e.g.
next, page, has_more),
- RFC 5988
Link headers,
- or no pagination at all (everything in one response).
- If paginated: extend
ScoutVulnerabilityResponse with the pagination field(s) ([JsonPropertyName]-annotated, internal sealed record, one type per file) and loop in FetchVulnerabilitiesAsync until the last page, aggregating advisories into one list before MapAdvisories. Guard against infinite loops with a hard page cap (e.g. 50 pages) — if the cap is hit, log a warning (add a new LoggerMessage in DockerScoutVulnerabilityProviderLogging.cs; next free EventId after 3417) and return what was collected as Succeeded (partial data is better than none, but it must be visible in the logs).
- If not paginated: close this issue with a comment containing the verified evidence (raw response structure, image used, CVE count) — do not add speculative code.
Testing (when pagination exists)
- Extend
src/Tests/DockerUpdateGuard.Tests/DockerScoutVulnerabilityProviderTests.cs. The tests use SequenceHttpMessageHandler (Tests/Helper/), which queues multiple responses per URL — queue page 1 (with a next-page marker) and page 2 (without), assert the merged advisory count and that the observed requests hit the expected page URLs.
- Add a test for the page-cap warning path (queue responses that always advertise a next page, assert the cap stops the loop and the warning EventId is logged via
TestLogger).
- Follow
{Class}{Scenario}{ExpectedResult} naming with assertion messages.
Acceptance criteria
Background
src/DockerUpdateGuard/Vulnerabilities/DockerScoutVulnerabilityProvider.csfetchesGET {DockerScoutBaseUrl}/v1/repositories/{ns}/{repo}/tags/{tag}/vulnerabilitiesand deserializes the response into
Vulnerabilities/Data/ScoutVulnerabilityResponse.cs, which only reads thevulnerabilitiesarray. If the API paginates (common for images with hundreds of CVEs), everything after the first page is silently dropped — the stored finding count would be wrong without any error.Task
next,page,has_more),Linkheaders,ScoutVulnerabilityResponsewith the pagination field(s) ([JsonPropertyName]-annotated,internal sealed record, one type per file) and loop inFetchVulnerabilitiesAsyncuntil the last page, aggregating advisories into one list beforeMapAdvisories. Guard against infinite loops with a hard page cap (e.g. 50 pages) — if the cap is hit, log a warning (add a newLoggerMessageinDockerScoutVulnerabilityProviderLogging.cs; next free EventId after 3417) and return what was collected asSucceeded(partial data is better than none, but it must be visible in the logs).GetOrCreateTokenAsync/ theUnauthorizedtuple flag). The retry applies at most once per whole fetch, not once per page.Testing (when pagination exists)
src/Tests/DockerUpdateGuard.Tests/DockerScoutVulnerabilityProviderTests.cs. The tests useSequenceHttpMessageHandler(Tests/Helper/), which queues multiple responses per URL — queue page 1 (with a next-page marker) and page 2 (without), assert the merged advisory count and that the observed requests hit the expected page URLs.TestLogger).{Class}{Scenario}{ExpectedResult}naming with assertion messages.Acceptance criteria
reihitsu-format ./clean,dotnet build DockerUpdateGuard.slnx -c Releaseclean, all tests pass.