Skip to content

Support for snyk's full, partial and no match response - #6835

Open
mehab wants to merge 1 commit into
DependencyTrack:mainfrom
mehab:feature/snykchecksumsupport
Open

Support for snyk's full, partial and no match response#6835
mehab wants to merge 1 commit into
DependencyTrack:mainfrom
mehab:feature/snykchecksumsupport

Conversation

@mehab

@mehab mehab commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Adds opt-in Snyk checksum-qualified PURL matching for Maven components, and compact result-cache encoding so large portfolio scans avoid Jackson deserialization for the common “no findings” path.

When checksumMatchingEnabled is true, Maven PURLs with a checksum qualifier are sent to Snyk as full canonical PURLs and evaluated via meta.packages match types (full / partial / none). Coordinates-only and flag-off behavior is unchanged.

Addressed Issue

#6204

Additional Details

Why

Snyk’s batch packages/issues API (2025-11-05) accepts checksum-qualified PURLs (e.g. from snyk sbom --include-provenance) and returns match quality in meta.packages. Previously Dependency-Track stripped PURL qualifiers, ignored meta, and could not distinguish a trustworthy empty result from an untrusted partial/none match.

Runtime behaviour

Config: checksumMatchingEnabled(default false) on the Snyk vuln-analyzer extension.

Coordinates-only (flag off, or no checksum on the sent PURL):

  • Request / cache key = lowercase coordinates
  • Findings come from data[] only; meta.packages is not used for branching
  • Snyk may still return match metadata (e.g. full with name_version: true, checksum: null when name/version matched, or name_version: false, checksum: null when the package is unknown). Unknown packages typically have empty data[], which we already treat as no findings / negative cache

Checksum-qualified (flag on + Maven + checksum qualifier):

  • Request / cache key = full canonical lowercase PURL (checksum preserved
  • full (name_version + checksum true) → attach findings; cache
  • partial → skip findings; cache (conservative; live partial bodies not yet confirmed)
  • none (both false) → skip findings; cache
  • Missing usable meta.packages or non-empty meta.errors → no findings; not cached as a successful empty result
  • PURL keys are correlated via PackageURL.canonicalize().toLowerCase() so sha1: and sha1%3A match
    No apiserver / BOM prep changes — checksums must already be present on component PURLs.

Cache design (portfolio scale)
Keys are shared by request PURL across projects (scales with unique PURLs, not project count).

Cache read branches on request key shape:

  • Coordinates-only keys → attach cached issues when present (always treated as trustworthy FULL)
  • Checksum-qualified keys → evaluate PARTIAL / NONE (skip findings, no re-fetch); FULL attaches issues when present
    SnykCacheCodec stores compact values when there are no findings:
Outcome Cached bytes Read cost
FULL, no issues null No Jackson
PARTIAL 1-byte sentinel No Jackson
NONE 1-byte sentinel No Jackson
Has issues JSON SnykCachedPurlResult Jackson (needed)

Legacy formats remain readable (null, SnykIssue[] JSON, structured SnykCachedPurlResult JSON).

Checklist

  • I have read and understand the contributing guidelines
  • This PR fixes a defect, and I have provided tests to verify that the fix is effective
  • This PR implements an enhancement, and I have provided tests to verify that it works as intended
  • This PR introduces changes to the database model, and I have updated the migration changelog accordingly
  • This PR introduces new or alters existing behavior, and I have updated the documentation accordingly
  • This PR is a substantial change (per the ADR criteria), and I have added an ADR under docs/adr/

Signed-off-by: Meha Bhargava <meha.bhargava2@gmail.com>
@owasp-dt-bot

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@codacy-production

codacy-production Bot commented Jul 28, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 115 complexity

Metric Results
Complexity 115

View in Codacy

🟢 Coverage 86.60% diff coverage · +0.00% coverage variation

Metric Results
Coverage variation +0.00% coverage variation (-1.00%)
Diff coverage 86.60% diff coverage (70.00%)

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (888cb05) 42943 37295 86.85%
Head commit (90fcd3f) 43120 (+177) 37449 (+154) 86.85% (+0.00%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#6835) 209 181 86.60%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@mehab
mehab marked this pull request as ready for review July 28, 2026 16:31
*/
static boolean requiresChecksumMeta(PackageURL purl, boolean checksumMatchingEnabled) {
return checksumMatchingEnabled
&& MAVEN.equals(purl.getType())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the limitation to Maven a vendor restriction or an active choice by you?

Comment on lines +27 to +35
/**
* Compact cache encoding for Snyk analyzer results.
* <p>
* Empty or untrusted outcomes use single-byte sentinels or {@code null} so portfolio-scale
* cache reads avoid JSON deserialization for the common negative-cache case.
*
* @since 5.1.0
*/
final class SnykCacheCodec {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite follow why this was needed. The old code already cached null (i.e. no serialization involved) for negative hits:

for (final String purl : purlBatch) {
if (!issuesByPurl.containsKey(purl)) {
entriesToCache.put(purl, null);
}
}

And null values were simply skipped, there's no deserialization:

for (final var entry : cachedBytesByPurl.entrySet()) {
final String purl = entry.getKey();
final byte[] cachedBytes = entry.getValue();
purlsToAnalyze.remove(purl);
if (cachedBytes == null) {
continue;
}

Comment on lines +411 to +432
if (isChecksumQualifiedRequestPurl(requestPurl)) {
if (cached.matchType() != SnykMatchType.FULL) {
// PARTIAL / NONE on checksum-qualified key: skip findings, do not re-fetch.
return;
}
// FULL checksum-qualified match: attach cached issues when present (or negative cache when empty).
attachCachedIssuesIfPresent(requestPurl, cached, issuesByPurl);
return;
}
// Coordinates-only keys are always cached as FULL; attach issues when present.
attachCachedIssuesIfPresent(requestPurl, cached, issuesByPurl);
}

private void attachCachedIssuesIfPresent(
String requestPurl,
SnykCachedPurlResult cached,
Map<String, List<SnykIssue>> issuesByPurl) {
if (cached.issues() != null && !cached.issues().isEmpty()) {
issuesByPurl.put(requestPurl, cached.issues());
}
// Empty issues is a cached negative result (shared across projects until TTL).
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handling for match types PARTIAL, NONE, and FULL-with-empty-issues is behaviorally the same. I think for all those cases we should just cache null, which removes the SnykCacheCodec overhead, slightly reduces storage cost, and makes the code simpler.

assertThat(analyzer.analyze(bom).getVulnerabilitiesList()).isEmpty();

verify(2, postRequestedFor(anyUrl()));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not cause a negative cache? If a PURL can't be analyzed for a permanent reason (e.g. due to Unsupported Ecosystem as in the test file), it won't be analyzable on the next run.

Comment on lines +339 to 345
// Issue PURL may include qualifiers; match on coordinates.
for (final var entry : issuesByIssuePurl.entrySet()) {
final String coords = coordinatesLower(entry.getKey());
if (requestPurl.equals(coords) && bomRefsByPurl.containsKey(requestPurl)) {
issues.addAll(entry.getValue());
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a source of false positives, and also somewhat defeats the purpose of the checksum matching, since the checksums here are also just PURL qualifiers which would be discarded.

"checksumMatchingEnabled": {
"type": "boolean",
"title": "Checksum Matching Enabled",
"description": "When enabled, send checksum qualifiers from Maven component PURLs to Snyk and interpret meta.packages match results. Only pkg:maven PURLs with a checksum qualifier use checksum matching; all others use coordinates-only matching.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few preconditions to make this feature work to begin with, which should be documented. For example, from what I understand, this practically only works when you generate your SBOMs with Snyk's tooling, as no other generator emits checksum PURL qualifiers.

*/
static boolean requiresChecksumMeta(PackageURL purl, boolean checksumMatchingEnabled) {
return checksumMatchingEnabled
&& MAVEN.equals(purl.getType())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
&& MAVEN.equals(purl.getType())
&& PackageURL.StandardTypes.MAVEN.equals(purl.getType())

try {
return SnykPurlUtil.requiresChecksumMeta(new PackageURL(requestPurl), true);
} catch (MalformedPackageURLException e) {
return requestPurl.contains("checksum=");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestPurl always comes from SnykPurlUtil.toSnykRequestPurl and should never be invalid. The fallback is dangerous because it could match on arbitrary prefixes of checksum like foo_checksum=. Either return false here or propagate the exception.

@nscuro nscuro added enhancement New feature or request integration/snyk Related to the Snyk integration labels Aug 3, 2026
@nscuro nscuro added this to the 5.x milestone Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request integration/snyk Related to the Snyk integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants