CycloneDX output omits ownership-by-file-overlap relationships preserved in SPDX, leading to false-positive Grype matches
- Dominant language
- Go
- Stars
- 9.6k
- Forks
- 954
- Avg merge
- 23h 27m
- Merged PRs (30d)
- 48
Description
## Summary
When generating an SBOM for an RPM-based container image (e.g. `redhat/ubi9`), Syft's SPDX-JSON output preserves `ownership-by-file-overlap` relationships between distro RPMs (e.g. `python3-requests`) and the language-ecosystem packages they bundle (e.g. `requests`). The CycloneDX-JSON output does not preserve these relationships — they are absent from `dependencies[]`, component `pedigree`/`evidence`, and component `properties`.
Grype consumers of the resulting CycloneDX SBOM produce extra Python-ecosystem GHSA matches that are *not* produced when scanning the same image via SPDX-JSON or by direct image scan. The matches are likely false positives for packages that the distro vendor is responsible for patching.
## Reproduction
```bash
# Generate both SBOMs
syft redhat/ubi9 -o spdx-json=ubi9.spdx.json -o cyclonedx-json=ubi9.cdx.json -q
# Scan each via Grype
grype sbom:ubi9.spdx.json -o json --quiet > ubi9.spdx-results.json
grype sbom:ubi9.cdx.json -o json --quiet > ubi9.cdx-results.json
grype redhat/ubi9 -o json --quiet > ubi9.direct-results.json
```
## Observed
| Source | Match rows for `{idna, requests, setuptools, urllib3}` |
|---|---|
| SPDX-JSON SBOM | 0 |
| CycloneDX-JSON SBOM | **15** |
| Direct image scan | 0 |
All 15 CycloneDX-only matches go through `matcher: python-matcher`, `type: exact-direct-match`. They are present only when Grype reads the CycloneDX-format SBOM.
## Investigation
The SPDX SBOM contains explicit ownership relationships:
```json
{
"spdxElementId": "SPDXRef-Package-rpm-python3-requests-...",
"relatedSpdxElement": "SPDXRef-Package-python-requests-...",
"relationshipType": "OTHER",
"comment": "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by"
}
```
There are four such relationships in this SBOM, one per `python3-*` RPM that ships a Python package.
The CycloneDX SBOM has no equivalent: the string `ownership-by-file-overlap` does not appear anywhere. `dependencies[]` only models the Python-side dependency graph (e.g. `requests` -> `idna`, `urllib3`, etc.), not the RPM -> Python ownership relationship.
## Environment
- syft v1.44.0 (windows/amd64)
- grype v0.112.0 (windows/amd64)
- Image: `redhat/ubi9:latest` at time of test
## Notes
- I don't know whether CycloneDX 1.6 has a natural representation for this kind of \"parent RPM owns child language-ecosystem package\" relationship. `dependencies[]` only models \"dependsOn\" semantics, and component-level fields like `pedigree` are about origin/lineage rather than ownership. Possibly this needs a property convention or a different encoding choice.
- Haven't verified whether the same asymmetry exists for non-Python language ecosystems bundled in distro packages (`gem`, `npm`, etc.).
- This is distinct from #2911 (which is about the `EXCLUDE_BINARY_OVERLAP_BY_OWNERSHIP` toggle, a separate concern).
- This was found while validating a separate CycloneDX fix (#4791); happy to share additional reproduction artifacts on request.
Contributor guide
Research direction
Start by reproducing the issue with the listed syft and grype commands against redhat/ubi9, then compare the SPDX-JSON and CycloneDX-JSON relationship data. Trace the existing SPDX and CycloneDX SBOM generation paths and determine how ownership-by-file-overlap can be represented in CycloneDX. Done means the relationship is preserved without introducing the CycloneDX-only false-positive matches.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- devtools, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100