Skip to content

Commit 52fd1dc

Browse files
leliaclaude
andcommitted
fix(gitlab): report a real manifest and real directness in report locations
Two defects in the same location block. The manifest path fell back to "unknown" whenever a package had no introducing chain. That happens routinely for a transitive package whose top-level ancestors are absent from the scan's package set, which a diff-scoped run causes by construction. The package records its own manifest files regardless, so those are now used before giving up. Directness was inferred by looking for " > " in the introducing entry, but no producer emits that separator -- get_source_data yields either ("direct", files) or (ancestor_purl, files). Every finding was therefore reported as direct, including transitive ones. It now comes from the package record. The dependency chain was also parsed into a local that was never read, and the docstring advertised a dependency_path key the function never returned. Both are removed rather than wired up, since the GitLab schema expects dependency references rather than a name path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 44746b2 commit 52fd1dc

5 files changed

Lines changed: 76 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
missing now logs a warning rather than emitting a broken link silently.
1313
- GitLab dependency-scanning reports emit CVE and GHSA identifiers from current
1414
API fields while remaining compatible with legacy CVE data.
15+
- GitLab report findings record the manifest they came from when the package's
16+
introducing chain is unavailable, instead of reporting the location as
17+
`unknown`, and report whether a dependency is direct from the package record
18+
rather than inferring it from a dependency-path string that is never produced.
1519
- Implicit diff baselines are selected from the same workspace, scan type,
1620
repository, and default branch. A baseline lookup that fails is reported as an
1721
API error instead of resolving to an empty baseline, and temporary scans are

socketsecurity/core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,6 +2503,8 @@ def add_package_alerts_to_collection(self, package: Package, alerts_collection:
25032503
suggestion=props.suggestion,
25042504
next_step_title=props.nextStepTitle,
25052505
introduced_by=introduced_by,
2506+
manifest_files=package.manifestFiles or [],
2507+
direct=bool(package.direct),
25062508
purl=package.purl,
25072509
url=package.url
25082510
)

socketsecurity/core/classes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,11 @@ class Issue:
317317
manifests: str
318318
url: str
319319
purl: str
320+
# The package's own manifest files, independent of how it was introduced. A
321+
# transitive package whose ancestors are absent from the scan has no
322+
# introduced_by chain, but its manifest is still known.
323+
manifest_files: list
324+
direct: bool
320325

321326
def __init__(self, **kwargs):
322327
if kwargs:
@@ -325,6 +330,10 @@ def __init__(self, **kwargs):
325330

326331
if hasattr(self, "created_at"):
327332
self.created_at = self.created_at.strip(" (Coordinated Universal Time)")
333+
if not hasattr(self, "manifest_files"):
334+
self.manifest_files = []
335+
if not hasattr(self, "direct"):
336+
self.direct = False
328337
if not hasattr(self, "manifests"):
329338
self.manifests = ""
330339
if not hasattr(self, "suggestion"):

socketsecurity/core/messages.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -697,37 +697,35 @@ def extract_location_gitlab(alert: Issue) -> dict:
697697
GitLab location requires:
698698
- file: path to manifest file
699699
- dependency: package name and version
700-
- dependency_path (optional): dependency chain
701700
"""
702-
# Get manifest file from introduced_by or manifests attribute
703-
manifest_file = "unknown"
704-
dependency_path = []
705-
is_direct = True
706-
707-
if hasattr(alert, 'introduced_by') and alert.introduced_by:
708-
if isinstance(alert.introduced_by, list) and len(alert.introduced_by) > 0:
709-
first_entry = alert.introduced_by[0]
710-
if isinstance(first_entry, (list, tuple)) and len(first_entry) >= 2:
711-
dependency_path_str = first_entry[0]
712-
manifest_file = first_entry[1].split(';')[0] if ';' in first_entry[1] else first_entry[1]
713-
714-
# Parse dependency path
715-
if ' > ' in dependency_path_str:
716-
dependency_path = dependency_path_str.split(' > ')
717-
# If there's a chain, it's transitive (not direct)
718-
is_direct = len(dependency_path) <= 1
719-
720-
elif hasattr(alert, 'manifests') and alert.manifests:
721-
manifest_file = alert.manifests.split(';')[0]
701+
manifest_file = ""
702+
703+
introduced_by = getattr(alert, "introduced_by", None)
704+
if isinstance(introduced_by, list) and introduced_by:
705+
first_entry = introduced_by[0]
706+
if isinstance(first_entry, (list, tuple)) and len(first_entry) >= 2:
707+
manifest_file = (first_entry[1] or "").split(";")[0]
708+
709+
if not manifest_file:
710+
manifest_file = (getattr(alert, "manifests", "") or "").split(";")[0]
711+
712+
if not manifest_file:
713+
# A transitive package whose ancestors are not in this scan has no
714+
# introduced_by chain, but the package still records its own manifest.
715+
for entry in getattr(alert, "manifest_files", None) or []:
716+
candidate = entry.get("file") if isinstance(entry, dict) else None
717+
if candidate:
718+
manifest_file = candidate
719+
break
722720

723721
location = {
724-
"file": manifest_file,
722+
"file": manifest_file or "unknown",
725723
"dependency": {
726724
"package": {
727725
"name": alert.pkg_name
728726
},
729727
"version": alert.pkg_version,
730-
"direct": is_direct
728+
"direct": bool(getattr(alert, "direct", False))
731729
}
732730
}
733731

tests/unit/test_gitlab_format.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def test_identifier_extraction_ignores_unusable_prop_values(self):
190190
assert [item["type"] for item in identifiers] == ["socket_alert"]
191191

192192
def test_dependency_chain_handling_transitive(self):
193-
"""Test transitive dependency path is captured"""
193+
"""Directness comes from the package record, not from parsing a path string"""
194194
diff = Diff()
195195
diff.id = "test-scan-id"
196196
diff.diff_url = "https://socket.dev/test"
@@ -204,6 +204,7 @@ def test_dependency_chain_handling_transitive(self):
204204
introduced_by=[
205205
["top-level > intermediate > transitive-dep", "package.json"]
206206
],
207+
direct=False,
207208
pkg_type="npm",
208209
key="test-key",
209210
purl="pkg:npm/transitive-dep@1.5.0"
@@ -231,6 +232,7 @@ def test_dependency_chain_handling_direct(self):
231232
introduced_by=[
232233
["direct-dep", "package.json"]
233234
],
235+
direct=True,
234236
pkg_type="npm",
235237
key="test-key",
236238
purl="pkg:npm/direct-dep@3.0.0"
@@ -242,6 +244,43 @@ def test_dependency_chain_handling_direct(self):
242244

243245
assert vuln["location"]["dependency"]["direct"] is True
244246

247+
def test_location_file_falls_back_to_the_package_manifest(self):
248+
"""A package with no introduced_by chain still knows its own manifest"""
249+
issue = Issue(
250+
pkg_name="transitive-dep",
251+
pkg_version="1.5.0",
252+
type="malware",
253+
severity="critical",
254+
title="Malware Found",
255+
introduced_by=[],
256+
manifest_files=[{"file": "services/api/pom.xml"}],
257+
direct=False,
258+
pkg_type="maven",
259+
key="test-key",
260+
purl="pkg:maven/org.example/transitive-dep@1.5.0",
261+
)
262+
263+
location = Messages.extract_location_gitlab(issue)
264+
265+
assert location["file"] == "services/api/pom.xml"
266+
assert location["dependency"]["direct"] is False
267+
268+
def test_location_file_is_unknown_only_when_nothing_is_known(self):
269+
"""The unknown placeholder is a last resort, not the first answer"""
270+
issue = Issue(
271+
pkg_name="orphan",
272+
pkg_version="1.0.0",
273+
type="malware",
274+
severity="critical",
275+
title="Malware Found",
276+
introduced_by=[],
277+
pkg_type="npm",
278+
key="test-key",
279+
purl="pkg:npm/orphan@1.0.0",
280+
)
281+
282+
assert Messages.extract_location_gitlab(issue)["file"] == "unknown"
283+
245284
def test_severity_mapping(self):
246285
"""Test all Socket severities map to GitLab severities"""
247286
severity_tests = [

0 commit comments

Comments
 (0)