Skip to content

Commit 73e1ce2

Browse files
committed
Fixed logic for removed dependencies triggering dependency overview and fixed regex expander
1 parent e039fb7 commit 73e1ce2

File tree

4 files changed

+77
-97
lines changed

4 files changed

+77
-97
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.1.4"
9+
version = "2.1.5"
1010
requires-python = ">= 3.10"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.1.4'
2+
__version__ = '2.1.5'

socketsecurity/core/__init__.py

Lines changed: 74 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -133,25 +133,40 @@ def create_sbom_output(self, diff: Diff) -> dict:
133133
@staticmethod
134134
def expand_brace_pattern(pattern: str) -> List[str]:
135135
"""
136-
Expands brace expressions (e.g., {a,b,c}) into separate patterns.
137-
"""
138-
brace_regex = re.compile(r"\{([^{}]+)\}")
139-
140-
# Expand all brace groups
141-
expanded_patterns = [pattern]
142-
while any("{" in p for p in expanded_patterns):
143-
new_patterns = []
144-
for pat in expanded_patterns:
145-
match = brace_regex.search(pat)
146-
if match:
147-
options = match.group(1).split(",") # Extract values inside {}
148-
prefix, suffix = pat[:match.start()], pat[match.end():]
149-
new_patterns.extend([prefix + opt + suffix for opt in options])
150-
else:
151-
new_patterns.append(pat)
152-
expanded_patterns = new_patterns
153-
154-
return expanded_patterns
136+
Recursively expands brace expressions (e.g., {a,b,c}) into separate patterns, supporting nested braces.
137+
"""
138+
def recursive_expand(pat: str) -> List[str]:
139+
stack = []
140+
for i, c in enumerate(pat):
141+
if c == '{':
142+
stack.append(i)
143+
elif c == '}' and stack:
144+
start = stack.pop()
145+
if not stack:
146+
# Found the outermost pair
147+
before = pat[:start]
148+
after = pat[i+1:]
149+
inner = pat[start+1:i]
150+
# Split on commas not inside nested braces
151+
options = []
152+
depth = 0
153+
last = 0
154+
for j, ch in enumerate(inner):
155+
if ch == '{':
156+
depth += 1
157+
elif ch == '}':
158+
depth -= 1
159+
elif ch == ',' and depth == 0:
160+
options.append(inner[last:j])
161+
last = j+1
162+
options.append(inner[last:])
163+
results = []
164+
for opt in options:
165+
expanded = before + opt + after
166+
results.extend(recursive_expand(expanded))
167+
return results
168+
return [pat]
169+
return recursive_expand(pattern)
155170

156171
@staticmethod
157172
def is_excluded(file_path: str, excluded_dirs: Set[str]) -> bool:
@@ -176,13 +191,7 @@ def find_files(self, path: str) -> List[str]:
176191
files: Set[str] = set()
177192

178193
# Get supported patterns from the API
179-
try:
180-
patterns = self.get_supported_patterns()
181-
except Exception as e:
182-
log.error(f"Error getting supported patterns from API: {e}")
183-
log.warning("Falling back to local patterns")
184-
from .utils import socket_globs as fallback_patterns
185-
patterns = fallback_patterns
194+
patterns = self.get_supported_patterns()
186195

187196
for ecosystem in patterns:
188197
if ecosystem in self.config.excluded_ecosystems:
@@ -425,7 +434,7 @@ def create_packages_dict(self, sbom_artifacts: list[SocketArtifact]) -> dict[str
425434
packages = {}
426435
top_level_count = {}
427436
for artifact in sbom_artifacts:
428-
package = Package.from_diff_artifact(artifact.__dict__)
437+
package = Package.from_socket_artifact(asdict(artifact))
429438
if package.id in packages:
430439
print("Duplicate package?")
431440
else:
@@ -534,44 +543,22 @@ def update_package_values(pkg: Package) -> Package:
534543
pkg.url += f"/{pkg.name}/overview/{pkg.version}"
535544
return pkg
536545

537-
def get_added_and_removed_packages(
538-
self,
539-
head_full_scan_id: str,
540-
new_full_scan_id: str,
541-
merge: bool = False,
542-
external_href: str = None,
543-
) -> Tuple[Dict[str, Package], Dict[str, Package], str, str]:
546+
def get_added_and_removed_packages(self, head_full_scan_id: str, new_full_scan_id: str) -> Tuple[Dict[str, Package], Dict[str, Package]]:
544547
"""
545548
Get packages that were added and removed between scans.
546549
547550
Args:
548-
head_full_scan_id: Previous scan
549-
new_full_scan_id: New scan just created
550-
merge: Whether the scan is merged into the default branch
551-
external_href: External reference
551+
head_full_scan: Previous scan (may be None if first scan)
552+
head_full_scan_id: New scan just created
553+
552554
Returns:
553555
Tuple of (added_packages, removed_packages) dictionaries
554556
"""
555557

556558
log.info(f"Comparing scans - Head scan ID: {head_full_scan_id}, New scan ID: {new_full_scan_id}")
557559
diff_start = time.time()
558560
try:
559-
params = {
560-
"before": head_full_scan_id,
561-
"after": new_full_scan_id,
562-
"description": f"Diff scan between head {head_full_scan_id} and new {new_full_scan_id} scans",
563-
"merge": merge,
564-
}
565-
if external_href:
566-
params["external_href"] = external_href
567-
new_diff_scan = self.sdk.diffscans.create_from_ids(self.config.org_slug, params)
568-
data = new_diff_scan.get("diff_scan", {})
569-
diff_scan_id = data.get("id")
570-
if not diff_scan_id:
571-
log.error(f"Failed to get diff scan ID for {new_full_scan_id}")
572-
log.error(new_diff_scan)
573-
sys.exit(1)
574-
diff_report = self.sdk.diffscans.get(self.config.org_slug, diff_scan_id)
561+
diff_report = self.sdk.fullscans.stream_diff(self.config.org_slug, head_full_scan_id, new_full_scan_id, use_types=True).data
575562
except APIFailure as e:
576563
log.error(f"API Error: {e}")
577564
sys.exit(1)
@@ -581,63 +568,44 @@ def get_added_and_removed_packages(
581568
log.error(f"Stack trace:\n{traceback.format_exc()}")
582569
raise
583570

584-
diff_data = diff_report.get("diff_scan", {})
585571
diff_end = time.time()
586-
diff_url = diff_data.get("html_url")
587-
after_data = diff_data.get("after_full_scan")
588-
if after_data:
589-
new_full_scan_url = after_data.get("html_url")
590-
else:
591-
new_full_scan_url = ""
592-
artifacts = diff_data.get("artifacts", {})
593-
added = artifacts.get("added", [])
594-
removed = artifacts.get("removed", [])
595-
unchanged = artifacts.get("unchanged", [])
596-
replaced = artifacts.get("replaced", [])
597-
updated = artifacts.get("updated", [])
598572
log.info(f"Diff Report Gathered in {diff_end - diff_start:.2f} seconds")
599573
log.info("Diff report artifact counts:")
600-
log.info(f"Added: {len(added)}")
601-
log.info(f"Removed: {len(removed)}")
602-
log.info(f"Unchanged: {len(unchanged)}")
603-
log.info(f"Replaced: {len(replaced)}")
604-
log.info(f"Updated: {len(updated)}")
574+
log.info(f"Added: {len(diff_report.artifacts.added)}")
575+
log.info(f"Removed: {len(diff_report.artifacts.removed)}")
576+
log.info(f"Unchanged: {len(diff_report.artifacts.unchanged)}")
577+
log.info(f"Replaced: {len(diff_report.artifacts.replaced)}")
578+
log.info(f"Updated: {len(diff_report.artifacts.updated)}")
605579

606-
added_artifacts = added + updated
607-
removed_artifacts = removed
580+
added_artifacts = diff_report.artifacts.added + diff_report.artifacts.updated
581+
removed_artifacts = diff_report.artifacts.removed + diff_report.artifacts.replaced
608582

609583
added_packages: Dict[str, Package] = {}
610584
removed_packages: Dict[str, Package] = {}
611585

612586
for artifact in added_artifacts:
613-
artifact_id = artifact.get("id")
614-
artifact_name = artifact.get("name")
615-
artifact_version = artifact.get("version")
616587
try:
617-
pkg = Package.from_diff_artifact(artifact)
588+
pkg = Package.from_diff_artifact(asdict(artifact))
618589
pkg = Core.update_package_values(pkg)
619-
added_packages[pkg.id] = pkg
590+
added_packages[artifact.id] = pkg
620591
except KeyError:
621-
log.error(f"KeyError: Could not create package from added artifact {artifact_id}")
622-
log.error(f"Artifact details - name: {artifact_name}, version: {artifact_version}")
592+
log.error(f"KeyError: Could not create package from added artifact {artifact.id}")
593+
log.error(f"Artifact details - name: {artifact.name}, version: {artifact.version}")
623594
log.error("No matching packages found in new_full_scan")
624595

625596
for artifact in removed_artifacts:
626-
artifact_id = artifact.get("id")
627-
artifact_name = artifact.get("name")
628-
artifact_version = artifact.get("version")
629597
try:
630-
pkg = Package.from_diff_artifact(artifact)
598+
pkg = Package.from_diff_artifact(asdict(artifact))
631599
pkg = Core.update_package_values(pkg)
632600
if pkg.namespace:
633601
pkg.purl += f"{pkg.namespace}/{pkg.purl}"
634-
removed_packages[pkg.id] = pkg
602+
removed_packages[artifact.id] = pkg
635603
except KeyError:
636-
log.error(f"KeyError: Could not create package from removed artifact {artifact_id}")
637-
log.error(f"Artifact details - name: {artifact_name}, version: {artifact_version}")
604+
log.error(f"KeyError: Could not create package from removed artifact {artifact.id}")
605+
log.error(f"Artifact details - name: {artifact.name}, version: {artifact.version}")
638606
log.error("No matching packages found in head_full_scan")
639607

640-
return added_packages, removed_packages, diff_url, new_full_scan_url
608+
return added_packages, removed_packages
641609

642610
def create_new_diff(
643611
self,
@@ -683,6 +651,7 @@ def create_new_diff(
683651
try:
684652
new_scan_start = time.time()
685653
new_full_scan = self.create_full_scan(files_for_sending, params)
654+
new_full_scan.sbom_artifacts = self.get_sbom_data(new_full_scan.id)
686655
new_scan_end = time.time()
687656
log.info(f"Total time to create new full scan: {new_scan_end - new_scan_start:.2f}")
688657
except APIFailure as e:
@@ -694,15 +663,26 @@ def create_new_diff(
694663
log.error(f"Stack trace:\n{traceback.format_exc()}")
695664
raise
696665

697-
added_packages, removed_packages, diff_url, report_url = self.get_added_and_removed_packages(
698-
head_full_scan_id,
699-
new_full_scan.id
700-
)
666+
scans_ready = self.check_full_scans_status(head_full_scan_id, new_full_scan.id)
667+
if scans_ready is False:
668+
log.error(f"Full scans did not complete within {self.config.timeout} seconds")
669+
added_packages, removed_packages = self.get_added_and_removed_packages(head_full_scan_id, new_full_scan.id)
701670

702671
diff = self.create_diff_report(added_packages, removed_packages)
672+
673+
base_socket = "https://socket.dev/dashboard/org"
703674
diff.id = new_full_scan.id
675+
676+
report_url = f"{base_socket}/{self.config.org_slug}/sbom/{diff.id}"
677+
if not params.include_license_details:
678+
report_url += "?include_license_details=false"
704679
diff.report_url = report_url
705-
diff.diff_url = diff_url
680+
681+
if head_full_scan_id is not None:
682+
diff.diff_url = f"{base_socket}/{self.config.org_slug}/diff/{head_full_scan_id}/{diff.id}"
683+
else:
684+
diff.diff_url = diff.report_url
685+
706686
return diff
707687

708688
def create_diff_report(

socketsecurity/socketcli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def main_code():
235235
log.debug("Updated security comment with no new alerts")
236236

237237
# FIXME: diff.new_packages is never populated, neither is removed_packages
238-
if (len(diff.new_packages) == 0 and len(diff.removed_packages) == 0) or config.disable_overview:
238+
if (len(diff.new_packages) == 0) or config.disable_overview:
239239
if not update_old_overview_comment:
240240
new_overview_comment = False
241241
log.debug("No new/removed packages or Dependency Overview comment disabled")

0 commit comments

Comments
 (0)