Skip to content

Commit d35da29

Browse files
committed
Updated the Security Issue comment to the new template
1 parent e5e4dfb commit d35da29

File tree

4 files changed

+196
-25
lines changed

4 files changed

+196
-25
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.0.53"
9+
version = "2.0.54"
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.0.53'
2+
__version__ = '2.0.54'

socketsecurity/core/messages.py

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -302,26 +302,95 @@ def create_security_comment_json(diff: Diff) -> dict:
302302
@staticmethod
303303
def security_comment_template(diff: Diff) -> str:
304304
"""
305-
Creates the security comment template
306-
:param diff: Diff - Diff report with the data needed for the template
307-
:return:
305+
Generates the security comment template in the new required format.
306+
Dynamically determines placement of the alerts table if markers like `<!-- start-socket-alerts-table -->` are used.
307+
308+
:param diff: Diff - Contains the detected vulnerabilities and warnings.
309+
:return: str - The formatted Markdown/HTML string.
308310
"""
309-
md = MdUtils(file_name="markdown_security_temp.md")
310-
md.new_line("<!-- socket-security-comment-actions -->")
311-
md.new_header(level=1, title="Socket Security: Issues Report")
312-
md.new_line("Potential security issues detected. Learn more about [socket.dev](https://socket.dev)")
313-
md.new_line("To accept the risk, merge this PR and you will not be notified again.")
314-
md.new_line()
315-
md.new_line("<!-- start-socket-alerts-table -->")
316-
md, ignore_commands, next_steps = Messages.create_security_alert_table(diff, md)
317-
md.new_line("<!-- end-socket-alerts-table -->")
318-
md.new_line()
319-
md = Messages.create_next_steps(md, next_steps)
320-
md = Messages.create_deeper_look(md)
321-
md = Messages.create_remove_package(md)
322-
md = Messages.create_acceptable_risk(md, ignore_commands)
323-
md.create_md_file()
324-
return md.file_data_text.lstrip()
311+
# Start of the comment
312+
comment = """<!-- socket-security-comment-actions -->
313+
314+
> **❗️ Caution**
315+
> **Review the following alerts detected in dependencies.**
316+
>
317+
> According to your organization’s Security Policy, you **must** resolve all **“Block”** alerts before proceeding. It’s recommended to resolve **“Warn”** alerts too.
318+
> Learn more about [Socket for GitHub](https://socket.dev?utm_medium=gh).
319+
320+
<!-- start-socket-updated-alerts-table -->
321+
<table>
322+
<thead>
323+
<tr>
324+
<th>Action</th>
325+
<th>Severity</th>
326+
<th align="left">Alert (click for details)</th>
327+
</tr>
328+
</thead>
329+
<tbody>
330+
"""
331+
332+
# Loop through alerts, dynamically generating rows
333+
for alert in diff.new_alerts:
334+
severity_icon = Messages.get_severity_icon(alert.severity)
335+
action = "Block" if alert.error else "Warn"
336+
details_open = ""
337+
# Generate a table row for each alert
338+
comment += f"""
339+
<!-- start-socket-alert-{alert.pkg_name}@{alert.pkg_version} -->
340+
<tr>
341+
<td><strong>{action}</strong></td>
342+
<td align="center">
343+
<img src="{severity_icon}" alt="{alert.severity}" width="20" height="20">
344+
</td>
345+
<td>
346+
<details {details_open}>
347+
<summary>{alert.pkg_name}@{alert.pkg_version} - {alert.title}</summary>
348+
<p><strong>Note:</strong> {alert.description}</p>
349+
<p><strong>Source:</strong> <a href="{alert.manifests}">Manifest File</a></p>
350+
<p>ℹ️ Read more on:
351+
<a href="{alert.purl}">This package</a> |
352+
<a href="{alert.url}">This alert</a> |
353+
<a href="https://socket.dev/alerts/malware">What is known malware?</a></p>
354+
<blockquote>
355+
<p><em>Suggestion:</em> {alert.suggestion}</p>
356+
<p><em>Mark as acceptable risk:</em> To ignore this alert only in this pull request, reply with:<br/>
357+
<code>@SocketSecurity ignore {alert.pkg_name}@{alert.pkg_version}</code><br/>
358+
Or ignore all future alerts with:<br/>
359+
<code>@SocketSecurity ignore-all</code></p>
360+
</blockquote>
361+
</details>
362+
</td>
363+
</tr>
364+
<!-- end-socket-alert-{alert.pkg_name}@{alert.pkg_version} -->
365+
"""
366+
367+
# Close table and comment
368+
comment += """
369+
</tbody>
370+
</table>
371+
<!-- end-socket-alerts-table -->
372+
373+
[View full report](https://socket.dev/...&action=error%2Cwarn)
374+
"""
375+
376+
return comment
377+
378+
@staticmethod
379+
def get_severity_icon(severity: str) -> str:
380+
"""
381+
Maps severity levels to their corresponding badge/icon URLs.
382+
383+
:param severity: str - Severity level (e.g., "Critical", "High").
384+
:return: str - Badge/icon URL.
385+
"""
386+
severity_map = {
387+
"critical": "https://github-app-statics.socket.dev/severity-3.svg",
388+
"high": "https://github-app-statics.socket.dev/severity-2.svg",
389+
"medium": "https://github-app-statics.socket.dev/severity-1.svg",
390+
"low": "https://github-app-statics.socket.dev/severity-0.svg",
391+
}
392+
return severity_map.get(severity.lower(), "https://github-app-statics.socket.dev/severity-0.svg")
393+
325394

326395
@staticmethod
327396
def create_next_steps(md: MdUtils, next_steps: dict):

socketsecurity/core/scm_comments.py

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,22 @@ def is_heading_line(line) -> bool:
8484

8585
@staticmethod
8686
def process_security_comment(comment: Comment, comments) -> str:
87-
lines = []
88-
start = False
8987
ignore_all, ignore_commands = Comments.get_ignore_options(comments)
88+
if "start-socket-alerts-table" in "".join(comment.body_list):
89+
new_body = Comments.process_original_security_comment(comment, ignore_all, ignore_commands)
90+
else:
91+
new_body = Comments.process_updated_security_comment(comment, ignore_all, ignore_commands)
92+
93+
return new_body
94+
95+
@staticmethod
96+
def process_original_security_comment(
97+
comment: Comment,
98+
ignore_all: bool,
99+
ignore_commands: list[tuple[str, str]]
100+
) -> str:
101+
start = False
102+
lines = []
90103
for line in comment.body_list:
91104
line = line.strip()
92105
if "start-socket-alerts-table" in line:
@@ -110,8 +123,97 @@ def process_security_comment(comment: Comment, comments) -> str:
110123
lines.append(line)
111124
else:
112125
lines.append(line)
113-
new_body = "\n".join(lines)
114-
return new_body
126+
return "\n".join(lines)
127+
128+
@staticmethod
129+
def process_updated_security_comment(
130+
comment: Comment,
131+
ignore_all: bool,
132+
ignore_commands: list[tuple[str, str]]
133+
) -> str:
134+
"""
135+
Processes an updated security comment containing an HTML table with alert sections.
136+
Removes entire sections marked by start and end hidden comments if the alert matches
137+
ignore conditions.
138+
139+
:param comment: Comment - The raw comment object containing the existing information.
140+
:param ignore_all: bool - Flag to ignore all alerts.
141+
:param ignore_commands: list of tuples - Specific ignore commands representing (pkg_name, pkg_version).
142+
:return: str - The updated comment as a single string.
143+
"""
144+
lines = []
145+
ignore_section = False
146+
pkg_name = pkg_version = "" # Track current package and version
147+
148+
# Loop through the comment lines
149+
for line in comment.body_list:
150+
line = line.strip()
151+
152+
# Detect the start of an alert section
153+
if line.startswith("<!-- start-socket-alert-"):
154+
# Extract package name and version from the comment
155+
try:
156+
start_marker = line[len("<!-- start-socket-alert-"):-4] # Strip the comment markers
157+
pkg_name, pkg_version = start_marker.split("@") # Extract pkg_name and pkg_version
158+
except ValueError:
159+
pkg_name, pkg_version = "", ""
160+
161+
# Determine if we should ignore this alert
162+
ignore_section = ignore_all or any(
163+
Comments.is_ignore(pkg_name, pkg_version, name, version)
164+
for name, version in ignore_commands
165+
)
166+
167+
# If not ignored, include this start marker
168+
if not ignore_section:
169+
lines.append(line)
170+
171+
# Detect the end of an alert section
172+
elif line.startswith("<!-- end-socket-alert-"):
173+
# Only include if we are not ignoring this section
174+
if not ignore_section:
175+
lines.append(line)
176+
ignore_section = False # Reset ignore flag
177+
178+
# Include lines inside an alert section only if not ignored
179+
elif not ignore_section:
180+
lines.append(line)
181+
182+
return "\n".join(lines)
183+
184+
@staticmethod
185+
def extract_alert_details_from_row(row: str, ignore_all: bool, ignore_commands: list[tuple[str, str]]) -> tuple:
186+
"""
187+
Parses an HTML table row (<tr>) to extract alert details and determine if it should be ignored.
188+
189+
:param row: str - The HTML table row as a string.
190+
:param ignore_all: bool - Flag to ignore all alerts.
191+
:param ignore_commands: list of tuples - List of (pkg_name, pkg_version) to ignore.
192+
:return: tuple - (pkg_name, pkg_version, ignore)
193+
"""
194+
# Extract package details (pkg_name and pkg_version) from the HTML table row
195+
try:
196+
# Find the relevant <summary> element to extract package information
197+
start_index = row.index("<summary>")
198+
end_index = row.index("</summary>")
199+
summary_content = row[start_index + 9:end_index] # Extract content between <summary> tags
200+
201+
# Example: "npm/[email protected] - Known Malware Alert"
202+
pkg_info, _ = summary_content.split(" - ", 1)
203+
pkg_name, pkg_version = pkg_info.split("@")
204+
except ValueError:
205+
# If parsing fails, skip this row
206+
return "", "", False
207+
208+
# Check ignore logic
209+
ignore = False
210+
for name, version in ignore_commands:
211+
if ignore_all or Comments.is_ignore(pkg_name, pkg_version, name, version):
212+
ignore = True
213+
break
214+
215+
return pkg_name, pkg_version, ignore
216+
115217

116218
@staticmethod
117219
def check_for_socket_comments(comments: dict):

0 commit comments

Comments
 (0)