Skip to content

Harden the vulnerability refresh pipeline against long-running scans - #228

Merged
LarsLaskowski merged 2 commits into
mainfrom
claude/fixu-fazein
Jul 29, 2026
Merged

Harden the vulnerability refresh pipeline against long-running scans#228
LarsLaskowski merged 2 commits into
mainfrom
claude/fixu-fazein

Conversation

@LarsLaskowski

@LarsLaskowski LarsLaskowski commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add Vulnerabilities:MaxParallelScans (default 1, validated 1..8, added to appsettings.json) and process image versions in batches of that size: the provider calls of a batch run concurrently through Task.WhenAll, while loading findings, upserting, and SaveChangesAsync stay strictly sequential on one flow. The value is additionally clamped to 1..8 at runtime.
  • Raise the Vulnerabilities:RequestTimeoutSeconds default from 30 to 120 in VulnerabilityOptions, appsettings.json, README.md, and DOCKER.md. The validator range of 1..300 is unchanged.
  • Mark Running scan runs that never completed as Failed in ScanCleanupBackgroundService, setting CompletedAtUtc and an explanatory ErrorMessage, and log the repaired count under a new event id 2021.
  • Add ScheduledBackgroundService.ExecuteStartupAsync, a virtual, exception-guarded startup hook that runs before the scheduled loop independently of ShouldExecuteImmediately. The cleanup service uses it so abandoned runs are repaired right after a restart without pulling the deletion pass onto the startup path.

Why

A Trivy scan of a large image can take minutes, and the vulnerability refresh processed every image version strictly sequentially, so one cycle could exceed Scanning:VulnerabilityRefreshIntervalMinutes. The 30 second request timeout was too tight for first-time scans of large images. When the process crashed mid-run, the ScanRun row stayed in status Running forever, because only the normal completion path was covered so far.

Linked issues

Closes #170

Review notes

  • Stale threshold choice: implemented generically for all run types as twice the run type's configured interval (OwnImageBaseScanIntervalMinutes, RuntimeImageUpdateScanIntervalMinutes, VulnerabilityRefreshIntervalMinutes), with a floor of 24 hours. With the default intervals the floor applies to every type, and a deliberately long refresh interval still gets a proportionally longer grace period instead of being cut off at a fixed 24 hours.
  • DockerUpdateGuardDbContext is never touched from a parallel task. Only IVulnerabilityProvider.GetVulnerabilitiesAsync runs concurrently; the image reference is formatted before the task starts and the results are applied one by one afterwards, so the per-image finalStatus aggregation keeps the previous Partial/Failed semantics.
  • With MaxParallelScans = 1 the batch size is one and the behavior is identical to the previous sequential loop. One difference exists at higher values: a provider exception no longer stops the remaining calls of the same batch, since Task.WhenAll observes all of them before the run is marked as failed.
  • New tests: bounded concurrency with three images and MaxParallelScans = 2 via ConcurrencyTrackingVulnerabilityProvider, plus stale-run repair on both the cleanup cycle and the startup step. DOCKER.md did not document the timeout before, so the two vulnerability keys were added to its JSON path table.

Follow-up work

None

claude added 2 commits July 29, 2026 05:28
Add the Vulnerabilities:MaxParallelScans option (validated 1..8, default 1)
and process image versions in batches of that size, running only the provider
calls in parallel while every database interaction stays on a single flow.
Raise the vulnerability request timeout default from 30 to 120 seconds because
first-time Trivy scans of large images regularly exceed the shorter window.
Let the scan cleanup mark abandoned Running scan runs as Failed on startup and
on every cleanup cycle, using twice the run type interval with a floor of 24
hours as the stale threshold.

Closes #170
Add ScheduledBackgroundServiceTests together with a test double that drives
the scheduled loop, covering the startup hook ordering, the logged startup
failure, and the cancellation path during shutdown. Extend the scan cleanup
tests with abandoned observed image and untyped runs and with a refresh
interval whose doubled value exceeds the minimum threshold of 24 hours. All
lines and branches added by this branch are covered now.
@sonarqubecloud

Copy link
Copy Markdown

@LarsLaskowski
LarsLaskowski merged commit a7186b4 into main Jul 29, 2026
4 checks passed
@LarsLaskowski
LarsLaskowski deleted the claude/fixu-fazein branch July 29, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Harden the vulnerability refresh pipeline: bounded parallelism, Trivy timeout default, stale Running runs

2 participants