Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion central/gen-advisory
Original file line number Diff line number Diff line change
Expand Up @@ -724,8 +724,22 @@ def generate_cdx_vex(advs, ov_map, advisory_id, timestamp):
{'name': 'wolfssl:fips:cmvp', 'value': m}
for m in prod['model_numbers']]
extra_components[ref] = comp
else:
elif prod['cdx_key'] == 'wolfssl':
ref = main_ref
else:
# Any other product (wolfSSH, wolfMQTT, wolfTPM, ...) gets its
# own correctly-named component rather than being collapsed
# onto the wolfssl metadata.component -- otherwise the CDX VEX
# attributes a non-wolfssl product's CVE to wolfssl.
ref = derived_uuid('cdx-component', prod['cdx_key'])
if ref not in extra_components:
extra_components[ref] = {
'bom-ref': ref, 'type': 'library',
'supplier': {'name': 'wolfSSL Inc.'},
'name': prod['product_name'],
'cpe': cpe_for(prod['product_name'], '*'),
'purl': f'pkg:github/wolfSSL/{prod["cdx_key"]}',
}
# CycloneDX affects[].versions[].status uses 'unaffected' for a
# not-affected product; the 'not_affected' term belongs to
# analysis.state only.
Expand Down
38 changes: 38 additions & 0 deletions central/test_gen_advisory.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,44 @@ def test_analysis_state_and_cwe_and_rating(self):
self.assertEqual(v['ratings'][0]['severity'], 'critical')


class TestCdxVexNonWolfsslProduct(unittest.TestCase):
"""Regression: a non-wolfssl product's CVE must be attributed to its own
component, not collapsed onto the wolfssl metadata.component (which would
make the CDX VEX say a wolfSSH bug is a wolfssl bug)."""

def setUp(self):
rec = {
'cveMetadata': {'cveId': 'CVE-2026-9001'},
'containers': {'cna': {
'descriptions': [{'lang': 'en', 'value': 'A wolfSSH issue.'}],
'affected': [{'vendor': 'wolfSSL', 'product': 'wolfSSH',
'versions': [{'version': '0',
'lessThanOrEqual': '1.4.19',
'status': 'affected'}]}],
}},
}
self.bom = ga.generate_cdx_vex([ga.parse_record(rec)], {},
'wolfSSH-SA-1', PINNED_EPOCH_ISO)

def test_metadata_component_stays_wolfssl_umbrella(self):
self.assertEqual(self.bom['metadata']['component']['name'], 'wolfssl')

def test_product_gets_its_own_component(self):
comps = {c['name']: c for c in self.bom['components']}
self.assertIn('wolfSSH', comps)
self.assertEqual(comps['wolfSSH']['purl'], 'pkg:github/wolfSSL/wolfssh')
self.assertEqual(comps['wolfSSH']['cpe'],
'cpe:2.3:a:wolfssl:wolfssh:*:*:*:*:*:*:*:*')

def test_affects_points_to_product_not_wolfssl(self):
wolfssh_ref = next(c['bom-ref'] for c in self.bom['components']
if c['name'] == 'wolfSSH')
main_ref = self.bom['metadata']['component']['bom-ref']
refs = {a['ref'] for a in self.bom['vulnerabilities'][0]['affects']}
self.assertIn(wolfssh_ref, refs)
self.assertNotIn(main_ref, refs)


# --------------------------------------------------------------------------- #
# Overlay matches its own schema vocabulary (lightweight, no jsonschema).
# The authoritative jsonschema pass runs in CI; this guards the committed
Expand Down
Loading