11import json
22import logging
3+ import os
34import re
45from pathlib import Path
56from mdutils import MdUtils
@@ -29,6 +30,92 @@ def map_severity_to_sarif(severity: str) -> str:
2930 }
3031 return severity_mapping .get (severity .lower (), "note" )
3132
33+ @staticmethod
34+ def get_manifest_file_url (diff : Diff , manifest_path : str , config = None ) -> str :
35+ """
36+ Generate proper URL for manifest file based on the repository type and diff URL.
37+
38+ :param diff: Diff object containing diff_url and report_url
39+ :param manifest_path: Path to the manifest file (can contain multiple files separated by ';')
40+ :param config: Configuration object to determine SCM type
41+ :return: Properly formatted URL for the manifest file
42+ """
43+ if not manifest_path :
44+ return ""
45+
46+ # Handle multiple manifest files separated by ';' - use the first one
47+ first_manifest = manifest_path .split (';' )[0 ] if ';' in manifest_path else manifest_path
48+
49+ # Clean up the manifest path - remove build agent paths and normalize
50+ clean_path = first_manifest
51+
52+ # Remove common build agent path prefixes
53+ prefixes_to_remove = [
54+ 'opt/buildagent/work/' ,
55+ '/opt/buildagent/work/' ,
56+ 'home/runner/work/' ,
57+ '/home/runner/work/' ,
58+ ]
59+
60+ for prefix in prefixes_to_remove :
61+ if clean_path .startswith (prefix ):
62+ # Find the part after the build ID (usually a hash)
63+ parts = clean_path [len (prefix ):].split ('/' , 2 )
64+ if len (parts ) >= 3 :
65+ clean_path = parts [2 ] # Take everything after build ID and repo name
66+ break
67+
68+ # Remove leading slashes
69+ clean_path = clean_path .lstrip ('/' )
70+
71+ # Determine SCM type from config or diff_url
72+ scm_type = "api" # Default to API
73+ if config and hasattr (config , 'scm' ):
74+ scm_type = config .scm .lower ()
75+ elif hasattr (diff , 'diff_url' ) and diff .diff_url :
76+ diff_url = diff .diff_url .lower ()
77+ if 'github.com' in diff_url or 'github' in diff_url :
78+ scm_type = "github"
79+ elif 'gitlab' in diff_url :
80+ scm_type = "gitlab"
81+ elif 'bitbucket' in diff_url :
82+ scm_type = "bitbucket"
83+
84+ # Generate URL based on SCM type using config information
85+ # NEVER use diff.diff_url for SCM URLs - those are Socket URLs for "View report" links
86+ if scm_type == "github" :
87+ if config and hasattr (config , 'repo' ) and config .repo :
88+ # Get branch from config, default to main
89+ branch = getattr (config , 'branch' , 'main' ) if hasattr (config , 'branch' ) and config .branch else 'main'
90+ # Construct GitHub URL from repo info (could be github.com or GitHub Enterprise)
91+ github_server = os .getenv ('GITHUB_SERVER_URL' , 'https://github.com' )
92+ return f"{ github_server } /{ config .repo } /blob/{ branch } /{ clean_path } "
93+
94+ elif scm_type == "gitlab" :
95+ if config and hasattr (config , 'repo' ) and config .repo :
96+ # Get branch from config, default to main
97+ branch = getattr (config , 'branch' , 'main' ) if hasattr (config , 'branch' ) and config .branch else 'main'
98+ # Construct GitLab URL from repo info (could be gitlab.com or self-hosted GitLab)
99+ gitlab_server = os .getenv ('CI_SERVER_URL' , 'https://gitlab.com' )
100+ return f"{ gitlab_server } /{ config .repo } /-/blob/{ branch } /{ clean_path } "
101+
102+ elif scm_type == "bitbucket" :
103+ if config and hasattr (config , 'repo' ) and config .repo :
104+ # Get branch from config, default to main
105+ branch = getattr (config , 'branch' , 'main' ) if hasattr (config , 'branch' ) and config .branch else 'main'
106+ # Construct Bitbucket URL from repo info (could be bitbucket.org or Bitbucket Server)
107+ bitbucket_server = os .getenv ('BITBUCKET_SERVER_URL' , 'https://bitbucket.org' )
108+ return f"{ bitbucket_server } /{ config .repo } /src/{ branch } /{ clean_path } "
109+
110+ # Fallback to Socket file view for API or unknown repository types
111+ if hasattr (diff , 'report_url' ) and diff .report_url :
112+ # Strip leading slash and URL encode for Socket dashboard
113+ socket_path = clean_path .lstrip ('/' )
114+ encoded_path = socket_path .replace ('/' , '%2F' )
115+ return f"{ diff .report_url } ?tab=files&file={ encoded_path } "
116+
117+ return ""
118+
32119 @staticmethod
33120 def find_line_in_file (packagename : str , packageversion : str , manifest_file : str ) -> tuple :
34121 """
@@ -301,12 +388,13 @@ def create_security_comment_json(diff: Diff) -> dict:
301388 return output
302389
303390 @staticmethod
304- def security_comment_template (diff : Diff ) -> str :
391+ def security_comment_template (diff : Diff , config = None ) -> str :
305392 """
306393 Generates the security comment template in the new required format.
307394 Dynamically determines placement of the alerts table if markers like `<!-- start-socket-alerts-table -->` are used.
308395
309396 :param diff: Diff - Contains the detected vulnerabilities and warnings.
397+ :param config: Optional configuration object to determine SCM type.
310398 :return: str - The formatted Markdown/HTML string.
311399 """
312400 # Group license policy violations by PURL (ecosystem/package@version)
@@ -348,6 +436,8 @@ def security_comment_template(diff: Diff) -> str:
348436 severity_icon = Messages .get_severity_icon (alert .severity )
349437 action = "Block" if alert .error else "Warn"
350438 details_open = ""
439+ # Generate proper manifest URL
440+ manifest_url = Messages .get_manifest_file_url (diff , alert .manifests , config )
351441 # Generate a table row for each alert
352442 comment += f"""
353443<!-- start-socket-alert-{ alert .pkg_name } @{ alert .pkg_version } -->
@@ -360,7 +450,7 @@ def security_comment_template(diff: Diff) -> str:
360450 <details { details_open } >
361451 <summary>{ alert .pkg_name } @{ alert .pkg_version } - { alert .title } </summary>
362452 <p><strong>Note:</strong> { alert .description } </p>
363- <p><strong>Source:</strong> <a href="{ alert . manifests } ">Manifest File</a></p>
453+ <p><strong>Source:</strong> <a href="{ manifest_url } ">Manifest File</a></p>
364454 <p>ℹ️ Read more on:
365455 <a href="{ alert .purl } ">This package</a> |
366456 <a href="{ alert .url } ">This alert</a> |
@@ -405,8 +495,12 @@ def security_comment_template(diff: Diff) -> str:
405495 for finding in license_findings :
406496 comment += f" <li>{ finding } </li>\n "
407497
498+
499+ # Generate proper manifest URL for license violations
500+ license_manifest_url = Messages .get_manifest_file_url (diff , first_alert .manifests , config )
501+
408502 comment += f""" </ul>
409- <p><strong>From:</strong> { first_alert . manifests } </p>
503+ <p><strong>From:</strong> <a href=" { license_manifest_url } ">Manifest File</a> </p>
410504 <p>ℹ️ Read more on: <a href="{ first_alert .purl } ">This package</a> | <a href="https://socket.dev/alerts/license">What is a license policy violation?</a></p>
411505 <blockquote>
412506 <p><em>Next steps:</em> Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at <strong>support@socket.dev</strong>.</p>
@@ -420,12 +514,19 @@ def security_comment_template(diff: Diff) -> str:
420514 """
421515
422516 # Close table
423- comment += """
517+ # Use diff_url for PRs, report_url for non-PR scans
518+ view_report_url = ""
519+ if hasattr (diff , 'diff_url' ) and diff .diff_url :
520+ view_report_url = diff .diff_url
521+ elif hasattr (diff , 'report_url' ) and diff .report_url :
522+ view_report_url = diff .report_url
523+
524+ comment += f"""
424525 </tbody>
425526</table>
426527<!-- end-socket-alerts-table -->
427528
428- [View full report](https://socket.dev/...& action=error%2Cwarn)
529+ [View full report]({ view_report_url } ? action=error%2Cwarn)
429530 """
430531
431532 return comment
@@ -519,7 +620,7 @@ def create_acceptable_risk(md: MdUtils, ignore_commands: list) -> MdUtils:
519620 return md
520621
521622 @staticmethod
522- def create_security_alert_table (diff : Diff , md : MdUtils ) -> ( MdUtils , list , dict ) :
623+ def create_security_alert_table (diff : Diff , md : MdUtils ) -> tuple [ MdUtils , list , dict ] :
523624 """
524625 Creates the detected issues table based on the Security Policy
525626 :param diff: Diff - Diff report with the detected issues
@@ -730,7 +831,7 @@ def create_console_security_alert_table(diff: Diff) -> PrettyTable:
730831 return alert_table
731832
732833 @staticmethod
733- def create_sources (alert : Issue , style = "md" ) -> [str , str ]:
834+ def create_sources (alert : Issue , style = "md" ) -> tuple [str , str ]:
734835 sources = []
735836 manifests = []
736837
0 commit comments