diff --git a/pyproject.toml b/pyproject.toml
index d500e71..1799b19 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project]
name = "socketsecurity"
-version = "2.0.52"
+version = "2.0.54"
requires-python = ">= 3.10"
license = {"file" = "LICENSE"}
dependencies = [
diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py
index 61801ac..92958d3 100644
--- a/socketsecurity/__init__.py
+++ b/socketsecurity/__init__.py
@@ -1,2 +1,2 @@
__author__ = 'socket.dev'
-__version__ = '2.0.52'
+__version__ = '2.0.54'
diff --git a/socketsecurity/core/__init__.py b/socketsecurity/core/__init__.py
index a5925a6..007af25 100644
--- a/socketsecurity/core/__init__.py
+++ b/socketsecurity/core/__init__.py
@@ -21,7 +21,7 @@
FullScan,
Issue,
Package,
- Purl,
+ Purl
)
from socketsecurity.core.exceptions import APIResourceNotFound
from socketsecurity.core.licenses import Licenses
@@ -644,7 +644,7 @@ def create_diff_report(
seen_removed_packages = set()
for package_id, package in added_packages.items():
- purl = Core.create_purl(package_id, added_packages)
+ purl = self.create_purl(package_id, added_packages)
base_purl = f"{purl.ecosystem}/{purl.name}@{purl.version}"
if (not direct_only or package.direct) and base_purl not in seen_new_packages:
@@ -658,7 +658,7 @@ def create_diff_report(
)
for package_id, package in removed_packages.items():
- purl = Core.create_purl(package_id, removed_packages)
+ purl = self.create_purl(package_id, removed_packages)
base_purl = f"{purl.ecosystem}/{purl.name}@{purl.version}"
if (not direct_only or package.direct) and base_purl not in seen_removed_packages:
@@ -682,8 +682,13 @@ def create_diff_report(
return diff
- @staticmethod
- def create_purl(package_id: str, packages: dict[str, Package]) -> Purl:
+ def get_all_scores(self, packages: dict[str, Package]) -> dict[str, Package]:
+ components = []
+ for package_id in packages:
+ package = packages[package_id]
+ return packages
+
+ def create_purl(self, package_id: str, packages: dict[str, Package]) -> Purl:
"""
Creates the extended PURL data for package identification and tracking.
@@ -707,7 +712,8 @@ def create_purl(package_id: str, packages: dict[str, Package]) -> Purl:
size=package.size,
transitives=package.transitives,
url=package.url,
- purl=package.purl
+ purl=package.purl,
+ scores=package.score
)
return purl
diff --git a/socketsecurity/core/classes.py b/socketsecurity/core/classes.py
index 416cd06..aefb0ab 100644
--- a/socketsecurity/core/classes.py
+++ b/socketsecurity/core/classes.py
@@ -370,7 +370,6 @@ def __init__(self, **kwargs):
def __str__(self):
return json.dumps(self.__dict__)
-
class Purl:
"""
Represents a Package URL (PURL) with extended metadata.
@@ -392,6 +391,7 @@ class Purl:
author_url: str
url: str
purl: str
+ scores: dict[str, int]
def __init__(self, **kwargs):
if kwargs:
diff --git a/socketsecurity/core/messages.py b/socketsecurity/core/messages.py
index db4c85f..b86b37f 100644
--- a/socketsecurity/core/messages.py
+++ b/socketsecurity/core/messages.py
@@ -302,26 +302,95 @@ def create_security_comment_json(diff: Diff) -> dict:
@staticmethod
def security_comment_template(diff: Diff) -> str:
"""
- Creates the security comment template
- :param diff: Diff - Diff report with the data needed for the template
- :return:
+ Generates the security comment template in the new required format.
+ Dynamically determines placement of the alerts table if markers like `` are used.
+
+ :param diff: Diff - Contains the detected vulnerabilities and warnings.
+ :return: str - The formatted Markdown/HTML string.
"""
- md = MdUtils(file_name="markdown_security_temp.md")
- md.new_line("")
- md.new_header(level=1, title="Socket Security: Issues Report")
- md.new_line("Potential security issues detected. Learn more about [socket.dev](https://socket.dev)")
- md.new_line("To accept the risk, merge this PR and you will not be notified again.")
- md.new_line()
- md.new_line("")
- md, ignore_commands, next_steps = Messages.create_security_alert_table(diff, md)
- md.new_line("")
- md.new_line()
- md = Messages.create_next_steps(md, next_steps)
- md = Messages.create_deeper_look(md)
- md = Messages.create_remove_package(md)
- md = Messages.create_acceptable_risk(md, ignore_commands)
- md.create_md_file()
- return md.file_data_text.lstrip()
+ # Start of the comment
+ comment = """
+
+> **❗️ Caution**
+> **Review the following alerts detected in dependencies.**
+>
+> According to your organization’s Security Policy, you **must** resolve all **“Block”** alerts before proceeding. It’s recommended to resolve **“Warn”** alerts too.
+> Learn more about [Socket for GitHub](https://socket.dev?utm_medium=gh).
+
+
+
+
+
+ Action |
+ Severity |
+ Alert (click for details) |
+
+
+
+ """
+
+ # Loop through alerts, dynamically generating rows
+ for alert in diff.new_alerts:
+ severity_icon = Messages.get_severity_icon(alert.severity)
+ action = "Block" if alert.error else "Warn"
+ details_open = ""
+ # Generate a table row for each alert
+ comment += f"""
+
+
+ {action} |
+
+
+ |
+
+
+ {alert.pkg_name}@{alert.pkg_version} - {alert.title}
+ Note: {alert.description}
+ Source: Manifest File
+ ℹ️ Read more on:
+ This package |
+ This alert |
+ What is known malware?
+
+ Suggestion: {alert.suggestion}
+ Mark as acceptable risk: To ignore this alert only in this pull request, reply with:
+ @SocketSecurity ignore {alert.pkg_name}@{alert.pkg_version}
+ Or ignore all future alerts with:
+ @SocketSecurity ignore-all
+
+
+ |
+
+
+ """
+
+ # Close table and comment
+ comment += """
+
+
+
+
+[View full report](https://socket.dev/...&action=error%2Cwarn)
+ """
+
+ return comment
+
+ @staticmethod
+ def get_severity_icon(severity: str) -> str:
+ """
+ Maps severity levels to their corresponding badge/icon URLs.
+
+ :param severity: str - Severity level (e.g., "Critical", "High").
+ :return: str - Badge/icon URL.
+ """
+ severity_map = {
+ "critical": "https://github-app-statics.socket.dev/severity-3.svg",
+ "high": "https://github-app-statics.socket.dev/severity-2.svg",
+ "medium": "https://github-app-statics.socket.dev/severity-1.svg",
+ "low": "https://github-app-statics.socket.dev/severity-0.svg",
+ }
+ return severity_map.get(severity.lower(), "https://github-app-statics.socket.dev/severity-0.svg")
+
@staticmethod
def create_next_steps(md: MdUtils, next_steps: dict):
@@ -456,11 +525,9 @@ def dependency_overview_template(diff: Diff) -> str:
md = MdUtils(file_name="markdown_overview_temp.md")
md.new_line("")
md.new_header(level=1, title="Socket Security: Dependency Overview")
- md.new_line("New and removed dependencies detected. Learn more about [socket.dev](https://socket.dev)")
+ md.new_line("Review the following changes in direct dependencies. Learn more about [socket.dev](https://socket.dev)")
md.new_line()
md = Messages.create_added_table(diff, md)
- if len(diff.removed_packages) > 0:
- md = Messages.create_remove_line(diff, md)
md.create_md_file()
if len(md.file_data_text.lstrip()) >= 65500:
md = Messages.short_dependency_overview_comment(diff)
@@ -471,7 +538,7 @@ def short_dependency_overview_comment(diff: Diff) -> MdUtils:
md = MdUtils(file_name="markdown_overview_temp.md")
md.new_line("")
md.new_header(level=1, title="Socket Security: Dependency Overview")
- md.new_line("New and removed dependencies detected. Learn more about [socket.dev](https://socket.dev)")
+ md.new_line("Review the following changes in direct dependencies. Learn more about [socket.dev](https://socket.dev)")
md.new_line()
md.new_line("The amount of dependency changes were to long for this comment. Please check out the full report")
md.new_line(f"To view more information about this report checkout the [Full Report]({diff.diff_url})")
@@ -498,40 +565,63 @@ def create_remove_line(diff: Diff, md: MdUtils) -> MdUtils:
def create_added_table(diff: Diff, md: MdUtils) -> MdUtils:
"""
Create the Added packages table for the Dependency Overview template
- :param diff: Diff - Diff report with the Added packages information
+ :param diff: Diff - Diff report with the Added package information
:param md: MdUtils - Main markdown variable
:return:
"""
+ # Table column headers
overview_table = [
+ "Diff",
"Package",
- "Direct",
- "Capabilities",
- "Transitives",
- "Size",
- "Author"
+ "Supply Chain
Security",
+ "Vulnerability",
+ "Quality",
+ "Maintenance",
+ "License"
]
num_of_overview_columns = len(overview_table)
+
count = 0
for added in diff.new_packages:
- added: Purl
- package_url = Messages.create_purl_link(added)
- capabilities = ", ".join(added.capabilities)
+ added: Purl # Ensure `added` has scores and relevant attributes.
+
+ package_url = f"[{added.purl}]({added.url})"
+ diff_badge = f"[]({added.url})"
+
+ # Scores dynamically converted to badge URLs and linked
+ def score_to_badge(score):
+ score_percent = int(score * 100) # Convert to integer percentage
+ return f"[]({added.url})"
+
+ # Generate badges for each score type
+ supply_chain_risk_badge = score_to_badge(added.scores.get("supplyChain", 100))
+ vulnerability_badge = score_to_badge(added.scores.get("vulnerability", 100))
+ quality_badge = score_to_badge(added.scores.get("quality", 100))
+ maintenance_badge = score_to_badge(added.scores.get("maintenance", 100))
+ license_badge = score_to_badge(added.scores.get("license", 100))
+
+ # Add the row for this package
row = [
+ diff_badge,
package_url,
- added.direct,
- capabilities,
- added.transitives,
- f"{added.size} KB",
- added.author_url
+ supply_chain_risk_badge,
+ vulnerability_badge,
+ quality_badge,
+ maintenance_badge,
+ license_badge
]
overview_table.extend(row)
- count += 1
- num_of_overview_rows = count + 1
+ count += 1 # Count total packages
+
+ # Calculate total rows for table
+ num_of_overview_rows = count + 1 # Include header row
+
+ # Generate Markdown table
md.new_table(
columns=num_of_overview_columns,
rows=num_of_overview_rows,
text=overview_table,
- text_align="left"
+ text_align="center"
)
return md
diff --git a/socketsecurity/core/scm_comments.py b/socketsecurity/core/scm_comments.py
index 7bcb203..bd1b4d7 100644
--- a/socketsecurity/core/scm_comments.py
+++ b/socketsecurity/core/scm_comments.py
@@ -84,9 +84,22 @@ def is_heading_line(line) -> bool:
@staticmethod
def process_security_comment(comment: Comment, comments) -> str:
- lines = []
- start = False
ignore_all, ignore_commands = Comments.get_ignore_options(comments)
+ if "start-socket-alerts-table" in "".join(comment.body_list):
+ new_body = Comments.process_original_security_comment(comment, ignore_all, ignore_commands)
+ else:
+ new_body = Comments.process_updated_security_comment(comment, ignore_all, ignore_commands)
+
+ return new_body
+
+ @staticmethod
+ def process_original_security_comment(
+ comment: Comment,
+ ignore_all: bool,
+ ignore_commands: list[tuple[str, str]]
+ ) -> str:
+ start = False
+ lines = []
for line in comment.body_list:
line = line.strip()
if "start-socket-alerts-table" in line:
@@ -110,8 +123,97 @@ def process_security_comment(comment: Comment, comments) -> str:
lines.append(line)
else:
lines.append(line)
- new_body = "\n".join(lines)
- return new_body
+ return "\n".join(lines)
+
+ @staticmethod
+ def process_updated_security_comment(
+ comment: Comment,
+ ignore_all: bool,
+ ignore_commands: list[tuple[str, str]]
+ ) -> str:
+ """
+ Processes an updated security comment containing an HTML table with alert sections.
+ Removes entire sections marked by start and end hidden comments if the alert matches
+ ignore conditions.
+
+ :param comment: Comment - The raw comment object containing the existing information.
+ :param ignore_all: bool - Flag to ignore all alerts.
+ :param ignore_commands: list of tuples - Specific ignore commands representing (pkg_name, pkg_version).
+ :return: str - The updated comment as a single string.
+ """
+ lines = []
+ ignore_section = False
+ pkg_name = pkg_version = "" # Track current package and version
+
+ # Loop through the comment lines
+ for line in comment.body_list:
+ line = line.strip()
+
+ # Detect the start of an alert section
+ if line.startswith("