Skip to content

fix: include release component in upstream qualifier #740

Merged
adrobuta merged 3 commits intomainfrom
fix/CN-482
Dec 24, 2025
Merged

fix: include release component in upstream qualifier #740
adrobuta merged 3 commits intomainfrom
fix/CN-482

Conversation

@adrobuta
Copy link
Copy Markdown
Contributor

  • Ready for review
  • Follows CONTRIBUTING rules
  • Reviewed by Snyk internal team

What does this PR do?

Fixes a regression introduced in CLI version 1.1298.1+ that cause false positive vulnerabilities reported when scanning RHEL 10 container images.

The issue is in how the upstream qualifier in the PURL is constructed for RPM packages. We only included the version from the source RPM, omitting the release component:

  • without this fix (broken): upstream=glibc@2.39
  • with this fix (fixed): upstream=glibc@2.39-46.el10_0

This causes version comparison failures because in RPM versioning, a version without a release is considered older than the same version with a release.

Example: Epoch 0 False Positive (libarchive)

✗ High severity vulnerability found in libarchive
  Description: Double Free
  Info: https://security.snyk.io/vuln/SNYK-RHEL10-LIBARCHIVE-12093027
  Introduced through: libarchive@3.7.7-4.el10_0
  From: libarchive@3.7.7-4.el10_0
  Fixed in: 0:3.7.7-4.el10_0

This was a false positive because 3.7.7-4.el10_0 and 0:3.7.7-4.el10_0 are the same version (the "0:" is the default epoch prefix). Without the release in the upstream qualifier, the comparison 3.7.7 < 3.7.7-4.el10_0 incorrectly evaluates to TRUE.

Where should the reviewer start?

lib/analyzer/package-managers/rpm.ts - the purl function where the upstream qualifier is constructed (lines 48-59).

How should this be manually tested?

  1. Build the plugin locally
  2. Run container test against a RHEL 10 image:
    snyk container test registry.access.redhat.com/ubi10/ubi-minimal:10.0-1755721767
    
  3. Verify that packages with equal versions (installed = fixed) are NOT flagged as vulnerable
  4. Compare results:
Metric Without Fix (CLI 1.1298.0+) With Fix
Total Issues 63 44
False Positives 19+ 0

False positives that should no longer appear:

Package Installed Fixed In Issue
openssl-libs 1:3.2.2-16.el10 1:3.2.2-16.el10 Equal versions
sqlite-libs 3.46.1-5.el10_0 0:3.46.1-5.el10_0 Equal versions
xz-libs 1:5.6.2-4.el10_0 1:5.6.2-4.el10_0 Equal versions
libarchive 3.7.7-4.el10_0 0:3.7.7-4.el10_0 Equal versions
glibc 2.39-46.el10_0 0:2.39-43.el10_0 46 > 43, already fixed
libxml2 2.12.5-9.el10_0 0:2.12.5-7.el10_0 9 > 7, already fixed

Any background context you want to provide?

Why source package matching for RHEL 10:

From RHEL 10 onward, Red Hat publishes vulnerability data in CSAF/VEX format (replacing OVAL). This new format references source packages instead of binary packages. The upstream qualifier in the PURL identifies the source package for accurate vulnerability matching.

Note on Epoch Prefix:

The "0:" prefix in versions like 0:3.7.7-4.el10_0 is the RPM epoch (default value). Versions 3.7.7-4.el10_0 and 0:3.7.7-4.el10_0 are semantically identical. The false positives occurred because the missing release component causes incorrect version comparisons, not because of epoch handling.

What are the relevant tickets?

Addresses customer-reported false positives for RHEL 10 images affecting:

  • openssl-libs
  • sqlite-libs
  • xz-libs
  • libarchive
  • glibc
  • libxml2
  • krb5-libs
  • glib2

Screenshots

N/A

Additional questions

None

The upstream qualifier in the PURL was only including the version from
the source RPM, omitting the release component. This caused false
positive vulnerabilities for RHEL 10 packages where the installed
version was equal to or greater than the fix version.

Example:
- Before: upstream=glibc@2.39 (missing release)
- After:  upstream=glibc@2.39-46.el10_0 (complete version)
This caused version comparison failures because in RPM versioning,
a version without a release is considered older than the same version with a release:
2.39 < 2.39-43.el10_0 → TRUE (incorrectly triggers vulnerability)
@adrobuta adrobuta requested a review from a team as a code owner December 23, 2025 11:25
@adrobuta adrobuta requested a review from SteveShani December 23, 2025 11:25
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Redundant Logic / Dead Code

The parseSourceRPM function (defined in this file) strictly enforces that name, version, and release are all present; otherwise, it returns undefined. Therefore, inside the if (sourcePackage) block, sourcePackage.version and sourcePackage.release are guaranteed to be defined. The condition if (sourcePackage.version && sourcePackage.release) will always evaluate to true, making the else if block unreachable. The logic can be simplified to unconditionally append the release component.

if (sourcePackage.version && sourcePackage.release) {
  // Include full version with release for accurate vulnerability matching
  upstream += `@${sourcePackage.version}-${sourcePackage.release}`;
} else if (sourcePackage.version) {
  upstream += `@${sourcePackage.version}`;
}
📚 Repository Context Analyzed

This review considered 12 relevant code sections from 10 files (average relevance: 0.79)

SteveShani
SteveShani previously approved these changes Dec 23, 2025
Copy link
Copy Markdown
Contributor

@SteveShani SteveShani left a comment

Choose a reason for hiding this comment

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

We're sure that vuln service knows how to handle this?

tyler-catlin
tyler-catlin previously approved these changes Dec 23, 2025
Copy link
Copy Markdown
Contributor

@tyler-catlin tyler-catlin left a comment

Choose a reason for hiding this comment

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

🎉

@adrobuta adrobuta dismissed stale reviews from tyler-catlin and SteveShani via abdb36f December 24, 2025 07:32
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Potential upstream version encoding

In test/lib/analyzer/package-managers/rpm.spec.ts, the expected PURL strings show URL-encoded characters for the upstream qualifier (e.g., %40 for @).

While packageurl-js typically handles encoding, the manual concatenation in upstream += \@${sourcePackage.version}-${sourcePackage.release}`;might be creating a string that is then assigned toqualifiers.upstream`.

Verify if packageurl-js automatically encodes values passed in the qualifiers object or if the test expectation of %40 implies that the library encodes the key/values. If the library encodes everything, adding @ inside the value might result in double encoding or specific handling. The test expects upstream=pam%401.6.1-8.el10, which suggests upstream value is pam@1.6.1-8.el10. The code constructs upstream as name + @ + version.

Wait, if upstream is a qualifier key, its value is pam@1.6.1-8.el10. PURL qualifiers are key=value. So upstream=pam@....
The test expectation upstream=pam%401.6.1... implies the @ in the value is being encoded. This seems correct for PURL spec, but just double-check that this format matches how the vulnerability matching service expects to parse the upstream component. The PR description says: upstream=glibc@2.39-46.el10_0 (unencoded in description) vs upstream=glibc%402.39... (encoded in test). Ensure the backend handles the encoded PURL correctly if it wasn't before.

upstream += `@${sourcePackage.version}-${sourcePackage.release}`;
📚 Repository Context Analyzed

This review considered 12 relevant code sections from 10 files (average relevance: 0.79)

@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Dead Code / Redundant Logic

The parseSourceRPM function (defined in this file) enforces that name, version, and release must all be present; otherwise, it returns undefined. Therefore, if sourcePackage is defined, sourcePackage.release is guaranteed to exist. The else if (sourcePackage.version) block appears to be unreachable dead code based on the current implementation of parseSourceRPM. Consider simplifying the logic to assume release is present when sourcePackage is valid.

} else if (sourcePackage.version) {
  upstream += `@${sourcePackage.version}`;
}
📚 Repository Context Analyzed

This review considered 12 relevant code sections from 10 files (average relevance: 0.79)

@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected
📚 Repository Context Analyzed

This review considered 12 relevant code sections from 10 files (average relevance: 0.79)

@adrobuta adrobuta merged commit eaeeab6 into main Dec 24, 2025
13 checks passed
@adrobuta adrobuta deleted the fix/CN-482 branch December 24, 2025 09:14
@snyksec
Copy link
Copy Markdown

snyksec commented Dec 24, 2025

🎉 This PR is included in version 8.15.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants