@@ -806,6 +806,90 @@ def create_security_comment_gitlab(diff: Diff) -> dict:
806806
807807 return gitlab_report
808808
809+ # A blank line terminates a CommonMark HTML block. When that happens inside
810+ # the alerts table the closing tags that follow are no longer treated as
811+ # markup, and because they are indented four or more spaces they render as a
812+ # literal code block containing `</blockquote></details>` instead.
813+ MAX_HTML_INDENT = 3
814+
815+ @staticmethod
816+ def inline_html_text (value ) -> str :
817+ """
818+ Collapses API supplied text onto a single line.
819+
820+ Alert descriptions and suggestions are interpolated into the comment HTML,
821+ so an embedded newline would otherwise be able to close the surrounding
822+ HTML block early.
823+
824+ :param value: The value to flatten. ``None`` becomes an empty string.
825+ :return: str - The value with all whitespace runs collapsed to a single space.
826+ """
827+ if value is None :
828+ return ""
829+ return " " .join (str (value ).split ())
830+
831+ @staticmethod
832+ def normalize_comment_html (comment : str ) -> str :
833+ """
834+ Makes generated comment markup safe for the CommonMark renderers used by
835+ GitHub and GitLab.
836+
837+ Drops whitespace-only lines (an optional section that rendered as empty
838+ leaves one behind) and caps indentation below the four spaces that would
839+ start an indented code block. Intentional separators - lines that are
840+ genuinely empty - are preserved so markdown blocks still break apart.
841+
842+ :param comment: str - The generated comment body.
843+ :return: str - The comment body with unrenderable whitespace removed.
844+ """
845+ lines = []
846+ for line in comment .split ("\n " ):
847+ if line and not line .strip ():
848+ continue
849+ stripped = line .lstrip ()
850+ indent = min (len (line ) - len (stripped ), Messages .MAX_HTML_INDENT )
851+ lines .append (" " * indent + stripped )
852+ return "\n " .join (lines )
853+
854+ @staticmethod
855+ def security_comment_no_alerts_template (view_report_url : str = "" ) -> str :
856+ """
857+ Generates the body used when there is nothing left to report.
858+
859+ Alerts raised on an early commit are frequently resolved later in the same
860+ pull request. Rewriting the comment to this body keeps the Socket comment
861+ in place - so a later commit that reintroduces an alert updates it rather
862+ than posting a second comment - without leaving the "Caution" banner above
863+ an empty alerts table.
864+
865+ :param view_report_url: str - Optional link to the full Socket report.
866+ :return: str - The formatted Markdown/HTML string.
867+ """
868+ lines = [
869+ "<!-- socket-security-comment-actions -->" ,
870+ "" ,
871+ "> **✅ Socket Security** " ,
872+ "> No dependency alerts to report. Any alerts previously reported on this "
873+ "pull request have been resolved or ignored." ,
874+ ]
875+ if view_report_url :
876+ lines += ["" , f"[View full report]({ view_report_url } )" ]
877+ return "\n " .join (lines ) + "\n "
878+
879+ @staticmethod
880+ def get_view_report_url (diff : Diff ) -> str :
881+ """
882+ Resolves the report link for a diff, preferring the PR/MR diff view.
883+
884+ :param diff: Diff - Diff report to pull the URL from.
885+ :return: str - The report URL, or an empty string when neither is set.
886+ """
887+ if getattr (diff , "diff_url" , None ):
888+ return diff .diff_url
889+ if getattr (diff , "report_url" , None ):
890+ return diff .report_url
891+ return ""
892+
809893 @staticmethod
810894 def security_comment_template (diff : Diff , config = None ) -> str :
811895 """
@@ -819,7 +903,7 @@ def security_comment_template(diff: Diff, config=None) -> str:
819903 # Group license policy violations by PURL (ecosystem/package@version)
820904 license_groups = {}
821905 security_alerts = []
822-
906+
823907 for alert in diff .new_alerts :
824908 if alert .type == "licenseSpdxDisj" :
825909 purl_key = f"{ alert .pkg_type } /{ alert .pkg_name } @{ alert .pkg_version } "
@@ -829,6 +913,13 @@ def security_comment_template(diff: Diff, config=None) -> str:
829913 else :
830914 security_alerts .append (alert )
831915
916+ view_report_url = Messages .get_view_report_url (diff )
917+
918+ # Without this the caution banner would sit above a table with no rows,
919+ # which is how a comment looks once every alert it raised is resolved.
920+ if not security_alerts and not license_groups :
921+ return Messages .security_comment_no_alerts_template (view_report_url )
922+
832923 # Start of the comment
833924 comment = """<!-- socket-security-comment-actions -->
834925
@@ -875,15 +966,15 @@ def security_comment_template(diff: Diff, config=None) -> str:
875966 </td>
876967 <td>
877968 <details { details_open } >
878- <summary>{ alert .pkg_name } @{ alert .pkg_version } - { alert .title } </summary>
879- <p><strong>Note:</strong> { alert .description } </p>
969+ <summary>{ alert .pkg_name } @{ alert .pkg_version } - { Messages . inline_html_text ( alert .title ) } </summary>
970+ <p><strong>Note:</strong> { Messages . inline_html_text ( alert .description ) } </p>
880971 <p><strong>Source:</strong> <a href="{ manifest_url } ">Manifest File</a></p>
881972 <p>ℹ️ Read more on:
882973 <a href="{ alert .purl } ">This package</a> |
883974 <a href="{ alert .url } ">This alert</a> |
884975 <a href="https://socket.dev/alerts/malware">What is known malware?</a></p>
885976 <blockquote>
886- <p><em>Suggestion:</em> { alert .suggestion } </p>
977+ <p><em>Suggestion:</em> { Messages . inline_html_text ( alert .suggestion ) } </p>
887978 { ignore_html }
888979 </blockquote>
889980 </details>
@@ -917,7 +1008,7 @@ def security_comment_template(diff: Diff, config=None) -> str:
9171008 <ul>
9181009"""
9191010 for finding in license_findings :
920- comment += f" <li>{ finding } </li>\n "
1011+ comment += f" <li>{ Messages . inline_html_text ( finding ) } </li>\n "
9211012
9221013
9231014 # Generate proper manifest URL for license violations
@@ -944,13 +1035,6 @@ def security_comment_template(diff: Diff, config=None) -> str:
9441035 """
9451036
9461037 # Close table
947- # Use diff_url for PRs, report_url for non-PR scans
948- view_report_url = ""
949- if hasattr (diff , 'diff_url' ) and diff .diff_url :
950- view_report_url = diff .diff_url
951- elif hasattr (diff , 'report_url' ) and diff .report_url :
952- view_report_url = diff .report_url
953-
9541038 comment += f"""
9551039 </tbody>
9561040</table>
@@ -959,7 +1043,7 @@ def security_comment_template(diff: Diff, config=None) -> str:
9591043[View full report]({ view_report_url } ?action=error%2Cwarn)
9601044 """
9611045
962- return comment
1046+ return Messages . normalize_comment_html ( comment )
9631047
9641048 @staticmethod
9651049 def get_severity_icon (severity : str ) -> str :
0 commit comments