Skip to content

Commit b546f07

Browse files
committed
fix: emit consistent defaults for FOSSA fields with no Socket source
Adds customRiskScore: None to vulnerability entries (FOSSA samples include this field, sometimes null). Documents all gap fields and their defaults in the module docstring. Locks the new key in EXPECTED_VULNERABILITY_KEYS.
1 parent ab3bac0 commit b546f07

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

socketsecurity/fossa_compat.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
"""FOSSA-compat output shaping for `--legal-format fossa`.
2+
3+
Builds two artifacts whose top-level shapes mirror real FOSSA pipeline outputs:
4+
5+
fossa-analyze.json — wrapper of {project, vulnerability, licensing, quality},
6+
where `project` is the raw `fossa analyze --json` shape and
7+
the three arrays are FOSSA /api/v2/issues-shaped items.
8+
fossa-sbom.json — `fossa report --json attribution` shape: 5 top-level keys
9+
(copyrightsByLicense, deepDependencies, directDependencies,
10+
licenses, project).
11+
12+
Fields without a Socket data source are emitted as documented defaults:
13+
14+
vulnerability[]:
15+
epss -> {score: None, percentile: None}
16+
cvssVector -> None
17+
exploitability -> None
18+
cveStatus -> None
19+
published -> None
20+
containerLayers -> {base: 0, other: 0}
21+
customRiskScore -> None
22+
remediation.partialFixDistance, completeFixDistance -> None (semver-distance TBD)
23+
projects[].scannedAt, analyzedAt, firstFoundAt -> None
24+
25+
dependencies[] (SBOM):
26+
description, downloadUrl, projectUrl -> ""
27+
hash, isGolang -> None (always null in real FOSSA samples)
28+
notes -> []
29+
30+
Top-level SBOM:
31+
copyrightsByLicense -> {} (would require parsing attribText for `Copyright (c)` lines)
32+
licenses -> {} (would require bundling SPDX license body texts)
33+
"""
134
from __future__ import annotations
235

336
from typing import Any, Iterable, Optional
@@ -238,6 +271,7 @@ def _build_vulnerability_entry(
238271
"exploitability": props.get("exploitability"),
239272
"epss": _build_epss(props),
240273
"cpes": _extract_string_list(props.get("cpes")),
274+
"customRiskScore": None,
241275
}
242276

243277

tests/unit/test_fossa_compat.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"containerLayers",
1414
"cpes",
1515
"createdAt",
16+
"customRiskScore",
1617
"cve",
1718
"cveStatus",
1819
"cwes",
@@ -237,6 +238,46 @@ def test_analyze_payload_empty_diff_yields_empty_arrays():
237238
assert payload["quality"] == []
238239

239240

241+
def test_vulnerability_gap_fields_emit_known_defaults():
242+
"""Fields with no Socket data source emit documented null/empty defaults."""
243+
from socketsecurity.fossa_compat import _build_vulnerability_entry
244+
issue = Issue(
245+
type="criticalCVE",
246+
severity="high",
247+
key="x",
248+
pkg_type="pypi",
249+
pkg_name="x",
250+
pkg_version="1.0",
251+
props={},
252+
)
253+
package = Package(
254+
type="pypi",
255+
name="x",
256+
version="1.0",
257+
id="pip+x$1.0",
258+
score={},
259+
alerts=[],
260+
direct=True,
261+
)
262+
project = {"branch": "m", "id": "a$x", "project": "a", "projectId": "a", "revision": "x", "url": "u"}
263+
entry = _build_vulnerability_entry(issue, package, project, index=1)
264+
# Documented gap fields:
265+
assert entry["epss"] == {"score": None, "percentile": None}
266+
assert entry["cvssVector"] is None
267+
assert entry["exploitability"] is None
268+
assert entry["cveStatus"] is None
269+
assert entry["published"] is None
270+
assert entry["containerLayers"] == {"base": 0, "other": 0}
271+
assert entry["remediation"]["partialFixDistance"] is None
272+
assert entry["remediation"]["completeFixDistance"] is None
273+
assert "customRiskScore" in entry
274+
assert entry["customRiskScore"] is None
275+
proj_entry = entry["projects"][0]
276+
assert proj_entry["scannedAt"] is None
277+
assert proj_entry["analyzedAt"] is None
278+
assert proj_entry["firstFoundAt"] is None
279+
280+
240281
def test_vulnerability_version_ranges_sourced_from_socket_fields():
241282
"""affectedVersionRanges/patchedVersionRanges come from Socket's singular fields, wrapped."""
242283
from socketsecurity.fossa_compat import _build_vulnerability_entry

0 commit comments

Comments
 (0)