Skip to content

XRAY-156485 - Match violation component ids to SBOM refs case-insensitively - #830

Merged
Jordanh1996 merged 2 commits into
jfrog:devfrom
Jordanh1996:fix/XRAY-156485-nuget-case-insensitive-bomref-match
Aug 4, 2026
Merged

XRAY-156485 - Match violation component ids to SBOM refs case-insensitively#830
Jordanh1996 merged 2 commits into
jfrog:devfrom
Jordanh1996:fix/XRAY-156485-nuget-case-insensitive-bomref-match

Conversation

@Jordanh1996

@Jordanh1996 Jordanh1996 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
  • The pull request is targeting the dev branch.
  • The code has been validated to compile successfully by running go vet ./....
  • The code has been formatted properly using go fmt ./....
  • All static analysis checks passed.
  • All tests have passed. If this feature is not already covered by the tests, new tests have been added.
  • Updated the Contributing page / ReadMe page / CI Workflow files if needed.
  • All changes are detailed at the description. if not already covered at JFrog Documentation, new documentation have been added.

Problem

XRAY-156485 — customer case 428503, Critical.

When a git-repository watch is bound, Frogbot correlates each Xray violation's infected component id back to a component in the scan SBOM. For NuGet, that correlation fails and the violation is silently dropped, so the PR is decorated as having no findings even though SCA detected the CVEs:

SBOM component : nuget://System.Security.Cryptography.Xml:9.0.13
Xray violation : nuget://system.security.cryptography.xml:9.0.13

Could not locate component nuget://system.security.cryptography.xml:9.0.13 ... No violations found.

With JF_FAIL=true the build cannot gate on findings that were dropped. The only customer workaround was to unbind the watch, which downgrades PR comments from violations to plain vulnerabilities.

Root cause

Two independent facts combine:

  1. Xray lowercases component ids for case-insensitive package types. lowerCasePackageTypes = {NuGet, Pypi} in Xray's sbom_utils.go. This is deliberate and correct — nuget.org and PyPI are case-insensitive registries — and Xray cannot echo the original casing back, because only the folded value is persisted. PascalCase package names are the NuGet norm, so NuGet drifts on almost every project.
  2. V3 static-SCA BOMRefs carry a purl qualifier. Every component ref looks like pkg:nuget/Name@9.0.13?hash=5058f1af, while ref is built from the violation id by XrayComponentIdToCdxComponentRef and is a bare purl.

The previous match was:

strings.HasPrefix(component.BOMRef, ref) || strings.EqualFold(component.BOMRef, ref)

HasPrefix tolerates the qualifier suffix but is case-sensitive. EqualFold tolerates case but only on a full-length match, which the qualifier makes impossible. So on V3 the EqualFold branch is unreachable and only the case-sensitive comparison is load-bearing. Any case drift drops the violation.

V2 was never affected — its BOMRefs are bare, so EqualFold still fired. That is exactly why "unbind the watch" worked as a workaround.

Fix

Strip the purl qualifiers (?qualifiers / #subpath) off the BOMRef, then compare case-insensitively. This restores the case-insensitive intent the original comment described, and drops the HasPrefix over-match that let @9.0.131 satisfy a violation on @9.0.13.

Notes for the reviewer

  • Only bomRef is stripped, not ref. ref comes from XrayComponentIdToCdxComponentRefToPackageRef, which calls ToPackageUrl with no qualifier arguments, so it is bare by construction. Stripping both would be symmetric but is dead code today.
  • The fold is deliberately package-type agnostic. Narrowing it to nuget/pypi (Xray's exact allowlist) looks tighter but regresses cpp: Xray's GetComponentIdFromCycloneDXComponent lowercases the whole purl wholesale, so e.g. pkg:github/FFmpeg/FFmpeg@n6.1.1cpp://ffmpeg:FFmpeg:n6.1.1 — case-drifted despite cpp not being in the allowlist. That is XRAY-135509, still open on the Xray side; the type-agnostic fold is what carries it today.
  • Case-insensitive matching can in principle alias two components that differ only in case (the classic Sirupsen/logrus vs sirupsen/logrus). I did not add an exact-match-first pass, because the ambiguity is already unresolvable: the violation id reaching us has been lowercased server-side, so Xray cannot say which of the two it meant either. The realistic outcome is a possibly-mislabeled impact path rather than a dropped violation, which is strictly better than today. Happy to add a prefer-exact pass if you'd rather.
  • Out of scope, observed while here: locateBomComponentInfo breaks out of the component loop but not the target loop, so a match in a later target silently overwrites an earlier one. Pre-existing, unchanged by this PR.
  • Per David Erukhimovich on the ticket, this is the agreed direction: the client-side comparison is the right place to fix it, and the whole class of issue goes away once the Xray-side refactor lands and Xray stops re-matching against normalized ids.

Testing

TestLocateBomComponentInfo_matchesQualifiedAndCaseDriftedRefs is a table test over the real customer refs, covering exact / qualified / case-drifted / qualified+case-drifted matches plus the two negatives (version-prefix collision, different component). It fails on dev and passes here.

Worth noting which case fails on dev: only qualified_and_case_drifted_ref. case_drifted_ref on its own still passes, because a bare BOMRef lets the old EqualFold branch fire — that is V2, and V2 was never affected. It takes the qualifier that V3 static-SCA adds to push the ref past EqualFold's full-length match and leave only the case-sensitive comparison load-bearing.

Verified in addition by replaying two real Frogbot V3 CycloneDX SBOMs through locateBomComponentInfo with the lowercased violation ids Xray actually returns:

SBOM Package components Dropped before Dropped after
The customer's NuGet case (428503) 2 2 0
Real Frogbot V3 scan of WebGoat, pulled from Artifactory 42 2 0

The two WebGoat drops are its only two BOMRefs containing uppercase, @2025.4-SNAPSHOT and @3.1.3.RELEASE — i.e. Maven is affected too, not just NuGet.

Finally, reproduced end to end against a live Xray: jf audit --static-sca --watches=… on a NuGet project pinning System.Security.Cryptography.Xml 6.0.0, run twice with a jf built from this commit and from its parent, everything else identical.

BEFORE (without this commit)                    AFTER (with it)
Xray returned      : 3 violations               Xray returned      : 3 violations
Dropped by matcher : 3                          Dropped by matcher : 0

  Could not locate component                      CVE-2022-34716  (Medium)
    nuget://system.formats.asn1:6.0.0             CVE-2023-29331  (High)
    nuget://system.security.cryptography.pkcs     CVE-2024-38095  (High)
    nuget://system.security.cryptography.xml

Reported: "No security violations were found"

@Jordanh1996
Jordanh1996 marked this pull request as ready for review August 4, 2026 13:35
@attiasas attiasas added the bug Something isn't working label Aug 4, 2026
@attiasas
attiasas requested a review from a team August 4, 2026 13:37
…tively

Xray lowercases component ids of case-insensitive package types such as
NuGet, while the SBOM BOMRef keeps the original case and carries purl
qualifiers. The previous match combined a case-sensitive HasPrefix with a
full-length EqualFold, so on V3 SBOMs the EqualFold branch could never
fire and any case drift dropped the violation from the PR.

Strip the purl qualifiers off the BOMRef before comparing, then compare
case-insensitively. This also removes the HasPrefix over-match that let
version 9.0.131 satisfy a violation on 9.0.13.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jordanh1996
Jordanh1996 force-pushed the fix/XRAY-156485-nuget-case-insensitive-bomref-match branch from 0b30a56 to a168b30 Compare August 4, 2026 13:38
@Jordanh1996

Copy link
Copy Markdown
Contributor Author

The rest I ran for it:
image

prioject with a simple csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
    <PackageReference Include="System.Security.Cryptography.Xml" Version="6.0.0" />
  </ItemGroup>

</Project>

@Jordanh1996 Jordanh1996 added the safe to test Approve running integration tests on a pull request label Aug 4, 2026
@github-actions github-actions Bot removed the safe to test Approve running integration tests on a pull request label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

👍 Frogbot scanned this pull request and did not find any new security issues.


@attiasas attiasas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM — this correctly replaces the case-sensitive HasPrefix + full-length EqualFold combo that made EqualFold unreachable on V3 qualified BOMRefs.

Stripping only bomRef (not ref) is right: XrayComponentIdToCdxComponentRef → ToPackageRef produces a bare purl. Type-agnostic EqualFold is also the right call given cpp/Maven case drift (XRAY-135509).

Nice bonus that this also kills the @9.0.131 / @9.0.13 HasPrefix over-match.

Comment thread policy/enforcer/policyenforcer.go
Comment thread policy/enforcer/policyenforcer_test.go
…f match test

Address review: the matcher strips both purl qualifier separators and folds
case for every package type, but the table only exercised ?hash= on NuGet.

The subpath row fails if the strip is ever narrowed to '?' alone, and the
Maven row (a real BOMRef from a Frogbot V3 scan, whose version case drifts
even though Xray does not lower case gav ids) fails if the fold is ever
narrowed to the case-insensitive package types.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jordanh1996 Jordanh1996 added the safe to test Approve running integration tests on a pull request label Aug 4, 2026
@github-actions github-actions Bot removed the safe to test Approve running integration tests on a pull request label Aug 4, 2026
@Jordanh1996
Jordanh1996 merged commit a1b82f3 into jfrog:dev Aug 4, 2026
64 of 65 checks passed
@Jordanh1996
Jordanh1996 deleted the fix/XRAY-156485-nuget-case-insensitive-bomref-match branch August 4, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants