socketsecurity 1.0.43__tar.gz → 1.0.47__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {socketsecurity-1.0.43/socketsecurity.egg-info → socketsecurity-1.0.47}/PKG-INFO +1 -1
  2. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/__init__.py +1 -1
  3. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/messages.py +179 -41
  4. {socketsecurity-1.0.43 → socketsecurity-1.0.47/socketsecurity.egg-info}/PKG-INFO +1 -1
  5. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/LICENSE +0 -0
  6. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/README.md +0 -0
  7. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/pyproject.toml +0 -0
  8. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/setup.cfg +0 -0
  9. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/__init__.py +0 -0
  10. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/classes.py +0 -0
  11. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/exceptions.py +0 -0
  12. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/git_interface.py +0 -0
  13. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/github.py +0 -0
  14. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/gitlab.py +0 -0
  15. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/issues.py +0 -0
  16. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/licenses.py +0 -0
  17. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/core/scm_comments.py +0 -0
  18. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity/socketcli.py +0 -0
  19. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity.egg-info/SOURCES.txt +0 -0
  20. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity.egg-info/dependency_links.txt +0 -0
  21. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity.egg-info/entry_points.txt +0 -0
  22. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity.egg-info/requires.txt +0 -0
  23. {socketsecurity-1.0.43 → socketsecurity-1.0.47}/socketsecurity.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: socketsecurity
3
- Version: 1.0.43
3
+ Version: 1.0.47
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Author-email: Douglas Coburn <douglas@socket.dev>
6
6
  Maintainer-email: Douglas Coburn <douglas@socket.dev>
@@ -1,2 +1,2 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '1.0.43'
2
+ __version__ = '1.0.47'
@@ -1,6 +1,9 @@
1
1
  import json
2
2
  import os
3
+ import re
4
+ import json
3
5
 
6
+ from pathlib import Path
4
7
  from mdutils import MdUtils
5
8
  from socketsecurity.core.classes import Diff, Purl, Issue
6
9
  from prettytable import PrettyTable
@@ -12,6 +15,10 @@ class Messages:
12
15
  def map_severity_to_sarif(severity: str) -> str:
13
16
  """
14
17
  Map Socket severity levels to SARIF levels (GitHub code scanning).
18
+
19
+ 'low' -> 'note'
20
+ 'medium' or 'middle' -> 'warning'
21
+ 'high' or 'critical' -> 'error'
15
22
  """
16
23
  severity_mapping = {
17
24
  "low": "note",
@@ -22,39 +29,168 @@ class Messages:
22
29
  }
23
30
  return severity_mapping.get(severity.lower(), "note")
24
31
 
25
-
26
32
  @staticmethod
27
- def find_line_in_file(pkg_name: str, manifest_file: str) -> tuple[int, str]:
33
+ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str) -> tuple:
28
34
  """
29
- Search 'manifest_file' for 'pkg_name'.
30
- Return (line_number, line_content) if found, else (1, fallback).
35
+ Finds the line number and snippet of code for the given package/version in a manifest file.
36
+ Returns a 2-tuple: (line_number, snippet_or_message).
37
+
38
+ Supports:
39
+ 1) JSON-based manifest files (package-lock.json, Pipfile.lock, composer.lock)
40
+ - Locates a dictionary entry with the matching package & version
41
+ - Does a rough line-based search to find the actual line in the raw text
42
+ 2) Text-based (requirements.txt, package.json, yarn.lock, etc.)
43
+ - Uses compiled regex patterns to detect a match line by line
31
44
  """
32
- if not manifest_file or not os.path.isfile(manifest_file):
33
- return 1, f"[No {manifest_file or 'manifest'} found in repo]"
45
+ # Extract just the file name to detect manifest type
46
+ file_type = Path(manifest_file).name
47
+
48
+ # ----------------------------------------------------
49
+ # 1) JSON-based manifest files
50
+ # ----------------------------------------------------
51
+ if file_type in ["package-lock.json", "Pipfile.lock", "composer.lock"]:
52
+ try:
53
+ # Read entire file so we can parse JSON and also do raw line checks
54
+ with open(manifest_file, "r", encoding="utf-8") as f:
55
+ raw_text = f.read()
56
+
57
+ # Attempt JSON parse
58
+ data = json.loads(raw_text)
59
+
60
+ # In practice, you may need to check data["dependencies"], data["default"], etc.
61
+ # This is an example approach.
62
+ packages_dict = (
63
+ data.get("packages")
64
+ or data.get("default")
65
+ or data.get("dependencies")
66
+ or {}
67
+ )
68
+
69
+ found_key = None
70
+ found_info = None
71
+ # Locate a dictionary entry whose 'version' matches
72
+ for key, value in packages_dict.items():
73
+ # For NPM package-lock, keys might look like "node_modules/axios"
74
+ if key.endswith(packagename) and "version" in value:
75
+ if value["version"] == packageversion:
76
+ found_key = key
77
+ found_info = value
78
+ break
79
+
80
+ if found_key and found_info:
81
+ # Search lines to approximate the correct line number
82
+ needle_key = f'"{found_key}":' # e.g. "node_modules/axios":
83
+ needle_version = f'"version": "{packageversion}"'
84
+ lines = raw_text.splitlines()
85
+ best_line = 1
86
+ snippet = None
87
+
88
+ for i, line in enumerate(lines, start=1):
89
+ if (needle_key in line) or (needle_version in line):
90
+ best_line = i
91
+ snippet = line.strip()
92
+ break # On first match, stop
93
+
94
+ # If we found an approximate line, return it; else fallback to line 1
95
+ if best_line > 0 and snippet:
96
+ return best_line, snippet
97
+ else:
98
+ return 1, f'"{found_key}": {found_info}'
99
+ else:
100
+ return 1, f"{packagename} {packageversion} (not found in {manifest_file})"
101
+
102
+ except (FileNotFoundError, json.JSONDecodeError):
103
+ return 1, f"Error reading {manifest_file}"
104
+
105
+ # ----------------------------------------------------
106
+ # 2) Text-based / line-based manifests
107
+ # ----------------------------------------------------
108
+ # Define a dictionary of patterns for common manifest types
109
+ search_patterns = {
110
+ "package.json": rf'"{packagename}":\s*"{packageversion}"',
111
+ "yarn.lock": rf'{packagename}@{packageversion}',
112
+ "pnpm-lock.yaml": rf'"{re.escape(packagename)}"\s*:\s*\{{[^}}]*"version":\s*"{re.escape(packageversion)}"',
113
+ "requirements.txt": rf'^{re.escape(packagename)}\s*(?:==|===|!=|>=|<=|~=|\s+)?\s*{re.escape(packageversion)}(?:\s*;.*)?$',
114
+ "pyproject.toml": rf'{packagename}\s*=\s*"{packageversion}"',
115
+ "Pipfile": rf'"{packagename}"\s*=\s*"{packageversion}"',
116
+ "go.mod": rf'require\s+{re.escape(packagename)}\s+{re.escape(packageversion)}',
117
+ "go.sum": rf'{re.escape(packagename)}\s+{re.escape(packageversion)}',
118
+ "pom.xml": rf'<artifactId>{re.escape(packagename)}</artifactId>\s*<version>{re.escape(packageversion)}</version>',
119
+ "build.gradle": rf'implementation\s+"{re.escape(packagename)}:{re.escape(packageversion)}"',
120
+ "Gemfile": rf'gem\s+"{re.escape(packagename)}",\s*"{re.escape(packageversion)}"',
121
+ "Gemfile.lock": rf'\s+{re.escape(packagename)}\s+\({re.escape(packageversion)}\)',
122
+ ".csproj": rf'<PackageReference\s+Include="{re.escape(packagename)}"\s+Version="{re.escape(packageversion)}"\s*/>',
123
+ ".fsproj": rf'<PackageReference\s+Include="{re.escape(packagename)}"\s+Version="{re.escape(packageversion)}"\s*/>',
124
+ "paket.dependencies": rf'nuget\s+{re.escape(packagename)}\s+{re.escape(packageversion)}',
125
+ "Cargo.toml": rf'{re.escape(packagename)}\s*=\s*"{re.escape(packageversion)}"',
126
+ "build.sbt": rf'"{re.escape(packagename)}"\s*%\s*"{re.escape(packageversion)}"',
127
+ "Podfile": rf'pod\s+"{re.escape(packagename)}",\s*"{re.escape(packageversion)}"',
128
+ "Package.swift": rf'\.package\(name:\s*"{re.escape(packagename)}",\s*url:\s*".*?",\s*version:\s*"{re.escape(packageversion)}"\)',
129
+ "mix.exs": rf'\{{:{re.escape(packagename)},\s*"{re.escape(packageversion)}"\}}',
130
+ "composer.json": rf'"{re.escape(packagename)}":\s*"{re.escape(packageversion)}"',
131
+ "conanfile.txt": rf'{re.escape(packagename)}/{re.escape(packageversion)}',
132
+ "vcpkg.json": rf'"{re.escape(packagename)}":\s*"{re.escape(packageversion)}"',
133
+ }
134
+
135
+ # If no specific pattern is found for this file name, fallback to a naive approach
136
+ searchstring = search_patterns.get(file_type, rf'{re.escape(packagename)}.*{re.escape(packageversion)}')
137
+
34
138
  try:
35
- with open(manifest_file, "r", encoding="utf-8") as f:
36
- lines = f.readlines()
37
- for i, line in enumerate(lines, start=1):
38
- if pkg_name.lower() in line.lower():
39
- return i, line.rstrip("\n")
139
+ # Read file lines and search for a match
140
+ with open(manifest_file, 'r', encoding="utf-8") as file:
141
+ lines = [line.rstrip("\n") for line in file]
142
+ for line_number, line_content in enumerate(lines, start=1):
143
+ # For Python conditional dependencies, ignore everything after first ';'
144
+ line_main = line_content.split(";", 1)[0].strip()
145
+
146
+ # Use a case-insensitive regex search
147
+ if re.search(searchstring, line_main, re.IGNORECASE):
148
+ return line_number, line_content.strip()
149
+
150
+ except FileNotFoundError:
151
+ return 1, f"{manifest_file} not found"
40
152
  except Exception as e:
41
- return 1, f"[Error reading {manifest_file}: {e}]"
42
- return 1, f"[Package '{pkg_name}' not found in {manifest_file}]"
43
-
153
+ return 1, f"Error reading {manifest_file}: {e}"
154
+
155
+ return 1, f"{packagename} {packageversion} (not found)"
156
+
44
157
  @staticmethod
45
- def create_security_comment_sarif(diff: Diff) -> dict:
158
+ def get_manifest_type_url(manifest_file: str, pkg_name: str, pkg_version: str) -> str:
46
159
  """
47
- Create SARIF-compliant output from the diff report.
160
+ Determine the correct URL path based on the manifest file type.
48
161
  """
49
- scan_failed = False
50
- if len(diff.new_alerts) == 0:
51
- for alert in diff.new_alerts:
52
- alert: Issue
53
- if alert.error:
54
- scan_failed = True
55
- break
162
+ manifest_to_url_prefix = {
163
+ "package.json": "npm",
164
+ "package-lock.json": "npm",
165
+ "yarn.lock": "npm",
166
+ "pnpm-lock.yaml": "npm",
167
+ "requirements.txt": "pypi",
168
+ "pyproject.toml": "pypi",
169
+ "Pipfile": "pypi",
170
+ "go.mod": "go",
171
+ "go.sum": "go",
172
+ "pom.xml": "maven",
173
+ "build.gradle": "maven",
174
+ ".csproj": "nuget",
175
+ ".fsproj": "nuget",
176
+ "paket.dependencies": "nuget",
177
+ "Cargo.toml": "cargo",
178
+ "Gemfile": "rubygems",
179
+ "Gemfile.lock": "rubygems",
180
+ "composer.json": "composer",
181
+ "vcpkg.json": "vcpkg",
182
+ }
56
183
 
57
- # Basic SARIF structure
184
+ file_type = Path(manifest_file).name
185
+ url_prefix = manifest_to_url_prefix.get(file_type, "unknown")
186
+ return f"https://socket.dev/{url_prefix}/package/{pkg_name}/alerts/{pkg_version}"
187
+
188
+ @staticmethod
189
+ def create_security_comment_sarif(diff) -> dict:
190
+ """
191
+ Create SARIF-compliant output from the diff report, including dynamic URL generation
192
+ based on manifest type and improved <br/> formatting for GitHub SARIF display.
193
+ """
58
194
  sarif_data = {
59
195
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
60
196
  "version": "2.1.0",
@@ -76,38 +212,39 @@ class Messages:
76
212
  results_list = []
77
213
 
78
214
  for alert in diff.new_alerts:
79
- alert: Issue
80
215
  pkg_name = alert.pkg_name
81
216
  pkg_version = alert.pkg_version
82
217
  rule_id = f"{pkg_name}=={pkg_version}"
83
218
  severity = alert.severity
84
219
 
85
- # Title and descriptions
86
- title = f"Alert generated for {pkg_name}=={pkg_version} by Socket Security"
87
- full_desc = f"{alert.title} - {alert.description}"
88
- short_desc = f"{alert.props.get('note', '')}\r\n\r\nSuggested Action:\r\n{alert.suggestion}"
89
-
90
- # Find the manifest file and line details
220
+ # Generate the correct URL for the alert based on manifest type
91
221
  introduced_list = alert.introduced_by
92
- if introduced_list and isinstance(introduced_list[0], list) and len(introduced_list[0]) > 1:
93
- manifest_file = introduced_list[0][1]
94
- else:
95
- manifest_file = alert.manifests or "requirements.txt"
222
+ manifest_file = introduced_list[0][1] if introduced_list and isinstance(introduced_list[0], list) else alert.manifests or "requirements.txt"
223
+ socket_url = Messages.get_manifest_type_url(manifest_file, pkg_name, pkg_version)
96
224
 
97
- line_number, line_content = Messages.find_line_in_file(pkg_name, manifest_file)
225
+ # Prepare descriptions with <br/> replacements
226
+ short_desc = f"{alert.props.get('note', '')}<br/><br/>Suggested Action:<br/>{alert.suggestion}<br/><a href=\"{socket_url}\">{socket_url}</a>"
227
+ full_desc = f"{alert.title} - {alert.description.replace('\r\n', '<br/>')}"
98
228
 
99
- # Define the rule if not already defined
229
+ # Identify the line and snippet in the manifest file
230
+ line_number, line_content = Messages.find_line_in_file(pkg_name, pkg_version, manifest_file)
231
+ if line_number < 1:
232
+ line_number = 1 # Ensure SARIF compliance
233
+
234
+ # Create the rule if not already defined
100
235
  if rule_id not in rules_map:
101
236
  rules_map[rule_id] = {
102
237
  "id": rule_id,
103
238
  "name": f"{pkg_name}=={pkg_version}",
104
- "shortDescription": {"text": title},
239
+ "shortDescription": {"text": f"Alert generated for {rule_id} by Socket Security"},
105
240
  "fullDescription": {"text": full_desc},
106
- "helpUri": alert.url,
107
- "defaultConfiguration": {"level": Messages.map_severity_to_sarif(severity)},
241
+ "helpUri": socket_url,
242
+ "defaultConfiguration": {
243
+ "level": Messages.map_severity_to_sarif(severity)
244
+ },
108
245
  }
109
246
 
110
- # Add the result
247
+ # Add the SARIF result
111
248
  result_obj = {
112
249
  "ruleId": rule_id,
113
250
  "message": {"text": short_desc},
@@ -125,6 +262,7 @@ class Messages:
125
262
  }
126
263
  results_list.append(result_obj)
127
264
 
265
+ # Attach rules and results
128
266
  sarif_data["runs"][0]["tool"]["driver"]["rules"] = list(rules_map.values())
129
267
  sarif_data["runs"][0]["results"] = results_list
130
268
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: socketsecurity
3
- Version: 1.0.43
3
+ Version: 1.0.47
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Author-email: Douglas Coburn <douglas@socket.dev>
6
6
  Maintainer-email: Douglas Coburn <douglas@socket.dev>
File without changes