Skip to content

bug(pom): 429 from remote Maven repository is silently swallowed, producing incomplete scan results #10691

Description

@DmitriyLewen

Description

In May 2026 Sonatype significantly tightened the rate limits for Maven Central (see the FAQ). As a result, 429 Too Many Requests responses are now routinely returned to clients that fetch many artifacts from a single IP — including Trivy when the local ~/.m2 cache is empty or partially populated.

When the Maven pom parser receives 429 Too Many Requests from a remote repository (Maven Central or any other configured remote), the response is logged at DEBUG and the dependency is skipped without surfacing any warning or error to the user. Trivy completes the scan and reports "no issues" even when a large portion of the dependency tree was never resolved.

Reported by users in #10672.

What did you expect to happen?

On 429 from a remote Maven repository, Trivy aborts the scan with a clear error stating which URL was rate-limited, the Retry-After value if provided, and how to mitigate (populate the local Maven cache before scanning).

What happened instead?

The scan exits successfully with WARN [report] Supported files for scanner(s) not found. or with a report that omits Maven dependencies. The 429 is only visible at --debug log level.

Why retry / continue does not work

I measured the actual behaviour of Maven Central by running controlled HTTP probes from a single IP, both as a parallel burst and as a steady sequential stream that mimics how the pom parser walks the dependency queue.

Threshold

Pattern Result
Sequential, ~4 RPS, distinct artifacts First 429 at request #100 (~24 sec from start)
Parallel, 50 workers, 389 URLs First 429 after ~40 successful edge-cache hits, ~86% of remaining requests 429

Block duration on the first hit

The block is communicated via Retry-After. Observed values on the very first violation from a clean state:

Trigger Retry-After
Parallel burst 1586 sec (~26 min)
Sequential ~4 RPS 1800 sec (30 min)

Per the Sonatype FAQ, repeated requests during the block extend it up to 24 hours.

The block applies to everything, including cache

During the block, every request from the offending IP returns 429 — including requests for artifacts that would normally be served from the Cloudflare edge cache and would not have hit Maven Central's origin at all. Verified:

HTTP/1.1 429 Too Many Requests
Retry-After: 1565

…against commons-lang3-3.12.0.pom which is permanently cached at the edge (CF-Cache-Status: HIT outside the block).

Retry budget is pointless

A "retry-with-Retry-After" strategy fails because:

  • The minimum observed wait is ~26 min for a first violation, and grows on repeat. A scan of a non-trivial project will hit this on the first artifact and stall the pipeline for the entire Retry-After duration.
  • During the block, even cached HITs return 429. The remaining ~N unresolved artifacts cannot be fetched until the block clears, so retrying per-artifact serially gives no improvement.
  • Continuing the scan and skipping the failed artifact (current behaviour) is equivalent to silently producing an incomplete result, because all subsequent fetches from the same IP will also fail.

The only behaviour that does not produce silently incomplete data is to abort the scan on the first 429 with a clear, actionable error.

Reproduction

  1. Trigger the rate limit from the host that will run Trivy (e.g. burst a few hundred parallel requests to varied POMs that bypass the edge cache).

  2. Run Trivy against any Java project whose POM resolves dependencies not present in ~/.m2:

    trivy fs --debug ./project

Expected: scan aborts with explanatory error.
Actual: scan finishes successfully; only a DEBUG [pom] Failed to fetch ... statusCode=429 line in the log.

Root Cause

Two layers conspire to hide the failure:

  1. The pom parser uniformly downgrades any non-200 response to a debug log + soft skip. 404 (artifact not found) and 429 (we are rate-limited) are treated identically, even though their semantics are completely different.
  2. The analyzer layer swallows every parser error except context.DeadlineExceeded, with no escape hatch for "this is fatal, surface it".

A 429 must be distinguished from 404: the former implies the scan is guaranteed to be incomplete because the rate limit applies to every subsequent request, not only to the offending artifact.

Environment

  • Trivy: any current version (behaviour unchanged since the POM parser was introduced)
  • Target: any Java project that triggers transitive resolution against a remote Maven repository

Metadata

Metadata

Assignees

Labels

kind/bugCategorizes issue or PR as related to a bug.scan/vulnerabilityIssues relating to vulnerability scanningtarget/filesystemIssues relating to filesystem scanning

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions