socketsecurity 2.0.7__tar.gz → 2.0.8__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.
- {socketsecurity-2.0.7/socketsecurity.egg-info → socketsecurity-2.0.8}/PKG-INFO +3 -2
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/pyproject.toml +2 -1
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/__init__.py +102 -104
- {socketsecurity-2.0.7 → socketsecurity-2.0.8/socketsecurity.egg-info}/PKG-INFO +3 -2
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity.egg-info/requires.txt +2 -1
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/LICENSE +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/README.md +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/setup.cfg +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/config.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/git_interface.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/issues.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/licenses.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/messages.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/scm/gitlab.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/scm_comments.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/output.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity/socketcli.py +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity.egg-info/SOURCES.txt +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity.egg-info/dependency_links.txt +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity.egg-info/entry_points.txt +0 -0
- {socketsecurity-2.0.7 → socketsecurity-2.0.8}/socketsecurity.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: socketsecurity
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.8
|
|
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>
|
|
@@ -19,7 +19,7 @@ Requires-Dist: prettytable
|
|
|
19
19
|
Requires-Dist: GitPython
|
|
20
20
|
Requires-Dist: packaging
|
|
21
21
|
Requires-Dist: python-dotenv
|
|
22
|
-
Requires-Dist: socket-sdk-python>=2.0.
|
|
22
|
+
Requires-Dist: socket-sdk-python>=2.0.8
|
|
23
23
|
Provides-Extra: test
|
|
24
24
|
Requires-Dist: pytest>=7.4.0; extra == "test"
|
|
25
25
|
Requires-Dist: pytest-cov>=4.1.0; extra == "test"
|
|
@@ -28,6 +28,7 @@ Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
|
|
|
28
28
|
Requires-Dist: pytest-watch>=4.2.0; extra == "test"
|
|
29
29
|
Provides-Extra: dev
|
|
30
30
|
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine; extra == "dev"
|
|
31
32
|
Requires-Dist: pip-tools>=7.4.0; extra == "dev"
|
|
32
33
|
|
|
33
34
|
# Socket Security CLI
|
|
@@ -13,7 +13,7 @@ dependencies = [
|
|
|
13
13
|
'GitPython',
|
|
14
14
|
'packaging',
|
|
15
15
|
'python-dotenv',
|
|
16
|
-
'socket-sdk-python>=2.0.
|
|
16
|
+
'socket-sdk-python>=2.0.8'
|
|
17
17
|
]
|
|
18
18
|
readme = "README.md"
|
|
19
19
|
description = "Socket Security CLI for CI/CD"
|
|
@@ -41,6 +41,7 @@ test = [
|
|
|
41
41
|
]
|
|
42
42
|
dev = [
|
|
43
43
|
"ruff>=0.3.0",
|
|
44
|
+
"twine", # for building
|
|
44
45
|
"pip-tools>=7.4.0", # for pip-compile
|
|
45
46
|
]
|
|
46
47
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__author__ = 'socket.dev'
|
|
2
|
-
__version__ = '2.0.
|
|
2
|
+
__version__ = '2.0.8'
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import time
|
|
3
2
|
import sys
|
|
3
|
+
import time
|
|
4
4
|
from dataclasses import asdict
|
|
5
5
|
from glob import glob
|
|
6
6
|
from pathlib import PurePath
|
|
7
7
|
from typing import BinaryIO, Dict, List, Tuple
|
|
8
|
+
|
|
8
9
|
from socketdev import socketdev
|
|
9
|
-
from socketdev.
|
|
10
|
-
|
|
11
|
-
SocketArtifact
|
|
12
|
-
)
|
|
10
|
+
from socketdev.exceptions import APIFailure
|
|
11
|
+
from socketdev.fullscans import FullScanParams, SocketArtifact
|
|
13
12
|
from socketdev.org import Organization
|
|
14
13
|
from socketdev.repos import RepositoryInfo
|
|
15
14
|
from socketdev.settings import SecurityPolicyRule
|
|
@@ -23,10 +22,7 @@ from socketsecurity.core.classes import (
|
|
|
23
22
|
Package,
|
|
24
23
|
Purl,
|
|
25
24
|
)
|
|
26
|
-
from socketsecurity.core.exceptions import
|
|
27
|
-
APIResourceNotFound
|
|
28
|
-
)
|
|
29
|
-
from socketdev.exceptions import APIFailure
|
|
25
|
+
from socketsecurity.core.exceptions import APIResourceNotFound
|
|
30
26
|
from socketsecurity.core.licenses import Licenses
|
|
31
27
|
|
|
32
28
|
from .socket_config import SocketConfig
|
|
@@ -43,11 +39,11 @@ log = logging.getLogger("socketdev")
|
|
|
43
39
|
|
|
44
40
|
class Core:
|
|
45
41
|
"""Main class for interacting with Socket Security API and processing scan results."""
|
|
46
|
-
|
|
42
|
+
|
|
47
43
|
ALERT_TYPE_TO_CAPABILITY = {
|
|
48
44
|
"envVars": "Environment Variables",
|
|
49
45
|
"networkAccess": "Network Access",
|
|
50
|
-
"filesystemAccess": "File System Access",
|
|
46
|
+
"filesystemAccess": "File System Access",
|
|
51
47
|
"shellAccess": "Shell Access",
|
|
52
48
|
"usesEval": "Uses Eval",
|
|
53
49
|
"unsafe": "Unsafe"
|
|
@@ -77,7 +73,7 @@ class Core:
|
|
|
77
73
|
|
|
78
74
|
def get_org_id_slug(self) -> Tuple[str, str]:
|
|
79
75
|
"""Gets the Org ID and Org Slug for the API Token."""
|
|
80
|
-
response = self.sdk.org.get()
|
|
76
|
+
response = self.sdk.org.get(use_types=True)
|
|
81
77
|
organizations: Dict[str, Organization] = response.get("organizations", {})
|
|
82
78
|
|
|
83
79
|
if len(organizations) == 1:
|
|
@@ -87,33 +83,33 @@ class Core:
|
|
|
87
83
|
|
|
88
84
|
def get_sbom_data(self, full_scan_id: str) -> Dict[str, SocketArtifact]:
|
|
89
85
|
"""Returns the list of SBOM artifacts for a full scan."""
|
|
90
|
-
response = self.sdk.fullscans.stream(self.config.org_slug, full_scan_id)
|
|
86
|
+
response = self.sdk.fullscans.stream(self.config.org_slug, full_scan_id, use_types=True)
|
|
91
87
|
if not response.success:
|
|
92
88
|
log.debug(f"Failed to get SBOM data for full-scan {full_scan_id}")
|
|
93
89
|
log.debug(response.message)
|
|
94
90
|
return {}
|
|
95
91
|
|
|
96
92
|
return response.artifacts
|
|
97
|
-
|
|
93
|
+
|
|
98
94
|
def get_sbom_data_list(self, artifacts_dict: Dict[str, SocketArtifact]) -> list[SocketArtifact]:
|
|
99
95
|
"""Converts artifacts dictionary to a list."""
|
|
100
96
|
return list(artifacts_dict.values())
|
|
101
97
|
|
|
102
98
|
def get_security_policy(self) -> Dict[str, SecurityPolicyRule]:
|
|
103
99
|
"""Gets the organization's security policy."""
|
|
104
|
-
response = self.sdk.settings.get(self.config.org_slug)
|
|
105
|
-
|
|
100
|
+
response = self.sdk.settings.get(self.config.org_slug, use_types=True)
|
|
101
|
+
|
|
106
102
|
if not response.success:
|
|
107
103
|
log.error(f"Failed to get security policy: {response.status}")
|
|
108
104
|
log.error(response.message)
|
|
109
105
|
raise Exception(f"Failed to get security policy: {response.status}, message: {response.message}")
|
|
110
|
-
|
|
106
|
+
|
|
111
107
|
return response.securityPolicyRules
|
|
112
108
|
|
|
113
109
|
def create_sbom_output(self, diff: Diff) -> dict:
|
|
114
110
|
"""Creates CycloneDX output for a given diff."""
|
|
115
111
|
try:
|
|
116
|
-
result = self.sdk.export.cdx_bom(self.config.org_slug, diff.id)
|
|
112
|
+
result = self.sdk.export.cdx_bom(self.config.org_slug, diff.id, use_types=True)
|
|
117
113
|
if not result.success:
|
|
118
114
|
log.error(f"Failed to get CycloneDX Output for full-scan {diff.id}")
|
|
119
115
|
log.error(result.message)
|
|
@@ -121,7 +117,7 @@ class Core:
|
|
|
121
117
|
|
|
122
118
|
result.pop("success", None)
|
|
123
119
|
return result
|
|
124
|
-
except Exception
|
|
120
|
+
except Exception:
|
|
125
121
|
log.error(f"Unable to get CycloneDX Output for {diff.id}")
|
|
126
122
|
log.error(result.get("message", "No error message provided"))
|
|
127
123
|
return {}
|
|
@@ -130,17 +126,17 @@ class Core:
|
|
|
130
126
|
def find_files(path: str) -> List[str]:
|
|
131
127
|
"""
|
|
132
128
|
Finds supported manifest files in the given path.
|
|
133
|
-
|
|
129
|
+
|
|
134
130
|
Args:
|
|
135
131
|
path: Path to search for manifest files
|
|
136
|
-
|
|
132
|
+
|
|
137
133
|
Returns:
|
|
138
134
|
List of found manifest file paths
|
|
139
135
|
"""
|
|
140
136
|
log.debug("Starting Find Files")
|
|
141
137
|
start_time = time.time()
|
|
142
138
|
files = set()
|
|
143
|
-
|
|
139
|
+
|
|
144
140
|
for ecosystem in socket_globs:
|
|
145
141
|
patterns = socket_globs[ecosystem]
|
|
146
142
|
for file_name in patterns:
|
|
@@ -165,18 +161,18 @@ class Core:
|
|
|
165
161
|
else:
|
|
166
162
|
log.debug(f"{len(files_list)} Files found ({total_time:.2f}s): {', '.join(files_list)}")
|
|
167
163
|
return list(files)
|
|
168
|
-
|
|
164
|
+
|
|
169
165
|
@staticmethod
|
|
170
166
|
def to_case_insensitive_regex(input_string: str) -> str:
|
|
171
167
|
"""
|
|
172
168
|
Converts a string into a case-insensitive regex pattern.
|
|
173
|
-
|
|
169
|
+
|
|
174
170
|
Args:
|
|
175
171
|
input_string: String to convert
|
|
176
|
-
|
|
172
|
+
|
|
177
173
|
Returns:
|
|
178
174
|
Case-insensitive regex pattern
|
|
179
|
-
|
|
175
|
+
|
|
180
176
|
Example:
|
|
181
177
|
"pipfile" -> "[Pp][Ii][Pp][Ff][Ii][Ll][Ee]"
|
|
182
178
|
"""
|
|
@@ -186,52 +182,52 @@ class Core:
|
|
|
186
182
|
def load_files_for_sending(files: List[str], workspace: str) -> List[Tuple[str, Tuple[str, BinaryIO]]]:
|
|
187
183
|
"""
|
|
188
184
|
Prepares files for sending to the Socket API.
|
|
189
|
-
|
|
185
|
+
|
|
190
186
|
Args:
|
|
191
187
|
files: List of file paths from find_files()
|
|
192
188
|
workspace: Base directory path to make paths relative to
|
|
193
|
-
|
|
189
|
+
|
|
194
190
|
Returns:
|
|
195
191
|
List of tuples formatted for requests multipart upload:
|
|
196
192
|
[(field_name, (filename, file_object)), ...]
|
|
197
193
|
"""
|
|
198
194
|
send_files = []
|
|
199
|
-
|
|
195
|
+
|
|
200
196
|
for file_path in files:
|
|
201
197
|
if "/" in file_path:
|
|
202
198
|
_, name = file_path.rsplit("/", 1)
|
|
203
199
|
else:
|
|
204
200
|
name = file_path
|
|
205
|
-
|
|
201
|
+
|
|
206
202
|
if file_path.startswith(workspace):
|
|
207
203
|
key = file_path[len(workspace):]
|
|
208
204
|
else:
|
|
209
205
|
key = file_path
|
|
210
|
-
|
|
206
|
+
|
|
211
207
|
key = key.lstrip("/")
|
|
212
208
|
key = key.lstrip("./")
|
|
213
|
-
|
|
209
|
+
|
|
214
210
|
f = open(file_path, 'rb')
|
|
215
211
|
payload = (key, (name, f))
|
|
216
212
|
send_files.append(payload)
|
|
217
|
-
|
|
213
|
+
|
|
218
214
|
return send_files
|
|
219
215
|
|
|
220
216
|
def create_full_scan(self, files: List[str], params: FullScanParams, has_head_scan: bool = False) -> FullScan:
|
|
221
217
|
"""
|
|
222
218
|
Creates a new full scan via the Socket API.
|
|
223
|
-
|
|
219
|
+
|
|
224
220
|
Args:
|
|
225
221
|
files: List of files to scan
|
|
226
222
|
params: Parameters for the full scan
|
|
227
|
-
|
|
223
|
+
|
|
228
224
|
Returns:
|
|
229
225
|
FullScan object with scan results
|
|
230
226
|
"""
|
|
231
227
|
log.debug("Creating new full scan")
|
|
232
228
|
create_full_start = time.time()
|
|
233
229
|
|
|
234
|
-
res = self.sdk.fullscans.post(files, params)
|
|
230
|
+
res = self.sdk.fullscans.post(files, params, use_types=True)
|
|
235
231
|
if not res.success:
|
|
236
232
|
log.error(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
237
233
|
raise Exception(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
@@ -245,20 +241,20 @@ class Core:
|
|
|
245
241
|
create_full_end = time.time()
|
|
246
242
|
total_time = create_full_end - create_full_start
|
|
247
243
|
log.debug(f"New Full Scan created in {total_time:.2f} seconds")
|
|
248
|
-
|
|
244
|
+
|
|
249
245
|
return full_scan
|
|
250
246
|
|
|
251
247
|
def get_full_scan(self, full_scan_id: str) -> FullScan:
|
|
252
248
|
"""
|
|
253
249
|
Get a FullScan object for an existing full scan including sbom_artifacts and packages.
|
|
254
|
-
|
|
250
|
+
|
|
255
251
|
Args:
|
|
256
252
|
full_scan_id: The ID of the full scan to get
|
|
257
|
-
|
|
253
|
+
|
|
258
254
|
Returns:
|
|
259
255
|
The FullScan object with populated artifacts and packages
|
|
260
256
|
"""
|
|
261
|
-
full_scan_metadata = self.sdk.fullscans.metadata(self.config.org_slug, full_scan_id)
|
|
257
|
+
full_scan_metadata = self.sdk.fullscans.metadata(self.config.org_slug, full_scan_id, use_types=True)
|
|
262
258
|
full_scan = FullScan(**asdict(full_scan_metadata.data))
|
|
263
259
|
full_scan_artifacts_dict = self.get_sbom_data(full_scan_id)
|
|
264
260
|
full_scan.sbom_artifacts = self.get_sbom_data_list(full_scan_artifacts_dict)
|
|
@@ -268,10 +264,10 @@ class Core:
|
|
|
268
264
|
def create_packages_dict(self, sbom_artifacts: list[SocketArtifact]) -> dict[str, Package]:
|
|
269
265
|
"""
|
|
270
266
|
Creates a dictionary of Package objects from SBOM artifacts.
|
|
271
|
-
|
|
267
|
+
|
|
272
268
|
Args:
|
|
273
269
|
sbom_artifacts: List of SBOM artifacts from the scan
|
|
274
|
-
|
|
270
|
+
|
|
275
271
|
Returns:
|
|
276
272
|
Dictionary mapping package IDs to Package objects
|
|
277
273
|
"""
|
|
@@ -289,51 +285,51 @@ class Core:
|
|
|
289
285
|
top_level_count[top_id] = 1
|
|
290
286
|
else:
|
|
291
287
|
top_level_count[top_id] += 1
|
|
292
|
-
|
|
288
|
+
|
|
293
289
|
for package_id, package in packages.items():
|
|
294
290
|
package.transitives = top_level_count.get(package_id, 0)
|
|
295
291
|
|
|
296
292
|
return packages
|
|
297
|
-
|
|
293
|
+
|
|
298
294
|
def get_package_license_text(self, package: Package) -> str:
|
|
299
295
|
"""
|
|
300
296
|
Gets the license text for a package if available.
|
|
301
|
-
|
|
297
|
+
|
|
302
298
|
Args:
|
|
303
299
|
package: Package object to get license text for
|
|
304
|
-
|
|
300
|
+
|
|
305
301
|
Returns:
|
|
306
302
|
License text if found, empty string otherwise
|
|
307
303
|
"""
|
|
308
304
|
if package.license is None:
|
|
309
305
|
return ""
|
|
310
|
-
|
|
306
|
+
|
|
311
307
|
license_raw = package.license
|
|
312
308
|
all_licenses = Licenses()
|
|
313
309
|
license_str = Licenses.make_python_safe(license_raw)
|
|
314
|
-
|
|
310
|
+
|
|
315
311
|
if license_str is not None and hasattr(all_licenses, license_str):
|
|
316
312
|
license_obj = getattr(all_licenses, license_str)
|
|
317
313
|
return license_obj.licenseText
|
|
318
|
-
|
|
314
|
+
|
|
319
315
|
return ""
|
|
320
316
|
|
|
321
317
|
def get_repo_info(self, repo_slug: str, default_branch: str = "socket-default-branch") -> RepositoryInfo:
|
|
322
318
|
"""
|
|
323
319
|
Gets repository information from the Socket API.
|
|
324
|
-
|
|
320
|
+
|
|
325
321
|
Args:
|
|
326
322
|
repo_slug: Repository slug to get info for
|
|
327
323
|
default_branch: Default branch string to use if the repo doesn't exist
|
|
328
|
-
|
|
324
|
+
|
|
329
325
|
Returns:
|
|
330
326
|
RepositoryInfo object
|
|
331
|
-
|
|
327
|
+
|
|
332
328
|
Raises:
|
|
333
329
|
Exception: If API request fails
|
|
334
330
|
"""
|
|
335
331
|
try:
|
|
336
|
-
response = self.sdk.repos.repo(self.config.org_slug, repo_slug)
|
|
332
|
+
response = self.sdk.repos.repo(self.config.org_slug, repo_slug, use_types=True)
|
|
337
333
|
if not response.success:
|
|
338
334
|
log.error(f"Failed to get repository: {response.status}")
|
|
339
335
|
log.error(response.message)
|
|
@@ -354,10 +350,10 @@ class Core:
|
|
|
354
350
|
def get_head_scan_for_repo(self, repo_slug: str) -> str:
|
|
355
351
|
"""
|
|
356
352
|
Gets the head scan ID for a repository.
|
|
357
|
-
|
|
353
|
+
|
|
358
354
|
Args:
|
|
359
355
|
repo_slug: Repository slug to get head scan for
|
|
360
|
-
|
|
356
|
+
|
|
361
357
|
Returns:
|
|
362
358
|
Head scan ID if it exists, None otherwise
|
|
363
359
|
"""
|
|
@@ -377,24 +373,34 @@ class Core:
|
|
|
377
373
|
def get_added_and_removed_packages(self, head_full_scan_id: str, new_full_scan: FullScan) -> Tuple[Dict[str, Package], Dict[str, Package]]:
|
|
378
374
|
"""
|
|
379
375
|
Get packages that were added and removed between scans.
|
|
380
|
-
|
|
376
|
+
|
|
381
377
|
Args:
|
|
382
378
|
head_full_scan: Previous scan (may be None if first scan)
|
|
383
379
|
head_full_scan_id: New scan just created
|
|
384
|
-
|
|
380
|
+
|
|
385
381
|
Returns:
|
|
386
382
|
Tuple of (added_packages, removed_packages) dictionaries
|
|
387
383
|
"""
|
|
388
384
|
if head_full_scan_id is None:
|
|
389
385
|
log.info(f"No head scan found. New scan ID: {new_full_scan.id}")
|
|
390
386
|
return new_full_scan.packages, {}
|
|
391
|
-
|
|
387
|
+
|
|
392
388
|
log.info(f"Comparing scans - Head scan ID: {head_full_scan_id}, New scan ID: {new_full_scan.id}")
|
|
393
389
|
diff_start = time.time()
|
|
394
|
-
|
|
390
|
+
try:
|
|
391
|
+
diff_report = self.sdk.fullscans.stream_diff(self.config.org_slug, head_full_scan_id, new_full_scan.id, use_types=True).data
|
|
392
|
+
except APIFailure as e:
|
|
393
|
+
log.error(f"API Error: {e}")
|
|
394
|
+
sys.exit(1)
|
|
395
|
+
except Exception as e:
|
|
396
|
+
import traceback
|
|
397
|
+
log.error(f"Error getting diff report: {str(e)}")
|
|
398
|
+
log.error(f"Stack trace:\n{traceback.format_exc()}")
|
|
399
|
+
raise
|
|
400
|
+
|
|
395
401
|
diff_end = time.time()
|
|
396
402
|
log.info(f"Diff Report Gathered in {diff_end - diff_start:.2f} seconds")
|
|
397
|
-
log.info(
|
|
403
|
+
log.info("Diff report artifact counts:")
|
|
398
404
|
log.info(f"Added: {len(diff_report.artifacts.added)}")
|
|
399
405
|
log.info(f"Removed: {len(diff_report.artifacts.removed)}")
|
|
400
406
|
log.info(f"Unchanged: {len(diff_report.artifacts.unchanged)}")
|
|
@@ -442,7 +448,6 @@ class Core:
|
|
|
442
448
|
Args:
|
|
443
449
|
path: Path to look for manifest files
|
|
444
450
|
params: Query params for the Full Scan endpoint
|
|
445
|
-
|
|
446
451
|
no_change: If True, return empty diff
|
|
447
452
|
"""
|
|
448
453
|
log.debug(f"starting create_new_diff with no_change: {no_change}")
|
|
@@ -464,7 +469,7 @@ class Core:
|
|
|
464
469
|
head_full_scan_id = None
|
|
465
470
|
has_head_scan = False
|
|
466
471
|
|
|
467
|
-
|
|
472
|
+
# Create new scan
|
|
468
473
|
try:
|
|
469
474
|
new_scan_start = time.time()
|
|
470
475
|
new_full_scan = self.create_full_scan(files_for_sending, params, has_head_scan)
|
|
@@ -474,17 +479,12 @@ class Core:
|
|
|
474
479
|
log.error(f"API Error: {e}")
|
|
475
480
|
sys.exit(1)
|
|
476
481
|
except Exception as e:
|
|
477
|
-
|
|
478
|
-
|
|
482
|
+
import traceback
|
|
483
|
+
log.error(f"Error creating new full scan: {str(e)}")
|
|
484
|
+
log.error(f"Stack trace:\n{traceback.format_exc()}")
|
|
485
|
+
raise
|
|
479
486
|
|
|
480
|
-
|
|
481
|
-
added_packages, removed_packages = self.get_added_and_removed_packages(head_full_scan_id, new_full_scan)
|
|
482
|
-
except APIFailure as e:
|
|
483
|
-
log.error(f"API Error: {e}")
|
|
484
|
-
sys.exit(1)
|
|
485
|
-
except Exception as e:
|
|
486
|
-
log.error(f"Unexpected error while comparing packages: {e}")
|
|
487
|
-
sys.exit(1)
|
|
487
|
+
added_packages, removed_packages = self.get_added_and_removed_packages(head_full_scan_id, new_full_scan)
|
|
488
488
|
|
|
489
489
|
diff = self.create_diff_report(added_packages, removed_packages)
|
|
490
490
|
|
|
@@ -495,7 +495,7 @@ class Core:
|
|
|
495
495
|
if not params.include_license_details:
|
|
496
496
|
report_url += "?include_license_details=false"
|
|
497
497
|
diff.report_url = report_url
|
|
498
|
-
|
|
498
|
+
|
|
499
499
|
if head_full_scan_id is not None:
|
|
500
500
|
diff.diff_url = f"{base_socket}/{self.config.org_slug}/diff/{diff.id}/{head_full_scan_id}"
|
|
501
501
|
else:
|
|
@@ -504,25 +504,25 @@ class Core:
|
|
|
504
504
|
return diff
|
|
505
505
|
|
|
506
506
|
def create_diff_report(
|
|
507
|
-
self,
|
|
508
|
-
added_packages: Dict[str, Package],
|
|
507
|
+
self,
|
|
508
|
+
added_packages: Dict[str, Package],
|
|
509
509
|
removed_packages: Dict[str, Package],
|
|
510
510
|
direct_only: bool = True
|
|
511
511
|
) -> Diff:
|
|
512
512
|
"""
|
|
513
513
|
Creates a diff report comparing two sets of packages.
|
|
514
|
-
|
|
514
|
+
|
|
515
515
|
Takes packages that were added and removed between two scans and:
|
|
516
516
|
1. Records new/removed packages (direct only by default)
|
|
517
517
|
2. Collects alerts from both sets of packages
|
|
518
518
|
3. Determines new capabilities introduced
|
|
519
|
-
|
|
519
|
+
|
|
520
520
|
Args:
|
|
521
521
|
added_packages: Dict of packages added in new scan
|
|
522
522
|
removed_packages: Dict of packages removed in new scan
|
|
523
523
|
direct_only: If True, only direct dependencies are included in new/removed lists
|
|
524
524
|
(but alerts are still processed for all packages)
|
|
525
|
-
|
|
525
|
+
|
|
526
526
|
Returns:
|
|
527
527
|
Diff object containing the comparison results
|
|
528
528
|
"""
|
|
@@ -577,11 +577,11 @@ class Core:
|
|
|
577
577
|
def create_purl(package_id: str, packages: dict[str, Package]) -> Purl:
|
|
578
578
|
"""
|
|
579
579
|
Creates the extended PURL data for package identification and tracking.
|
|
580
|
-
|
|
580
|
+
|
|
581
581
|
Args:
|
|
582
582
|
package_id: Package ID to create PURL data for
|
|
583
583
|
packages: Dictionary of all packages for transitive dependency lookup
|
|
584
|
-
|
|
584
|
+
|
|
585
585
|
Returns:
|
|
586
586
|
Purl object containing package metadata and dependency information
|
|
587
587
|
"""
|
|
@@ -606,14 +606,14 @@ class Core:
|
|
|
606
606
|
def get_source_data(package: Package, packages: dict) -> list:
|
|
607
607
|
"""
|
|
608
608
|
Determines how a package was introduced into the dependency tree.
|
|
609
|
-
|
|
609
|
+
|
|
610
610
|
For direct dependencies, records the manifest file.
|
|
611
611
|
For transitive dependencies, records the top-level package that introduced it.
|
|
612
|
-
|
|
612
|
+
|
|
613
613
|
Args:
|
|
614
614
|
package: Package to analyze
|
|
615
615
|
packages: Dictionary of all packages for ancestor lookup
|
|
616
|
-
|
|
616
|
+
|
|
617
617
|
Returns:
|
|
618
618
|
List of tuples containing (source, manifest_file) information
|
|
619
619
|
"""
|
|
@@ -648,7 +648,7 @@ class Core:
|
|
|
648
648
|
def add_purl_capabilities(diff: Diff) -> None:
|
|
649
649
|
"""
|
|
650
650
|
Adds capability information to each package in the diff's new_packages list.
|
|
651
|
-
|
|
651
|
+
|
|
652
652
|
Args:
|
|
653
653
|
diff: Diff object to update with capability information
|
|
654
654
|
"""
|
|
@@ -662,18 +662,18 @@ class Core:
|
|
|
662
662
|
new_packages.append(new_purl)
|
|
663
663
|
else:
|
|
664
664
|
new_packages.append(purl)
|
|
665
|
-
|
|
665
|
+
|
|
666
666
|
diff.new_packages = new_packages
|
|
667
667
|
|
|
668
668
|
def add_package_alerts_to_collection(self, package: Package, alerts_collection: dict, packages: dict) -> dict:
|
|
669
669
|
"""
|
|
670
670
|
Processes alerts from a package and adds them to a shared alerts collection.
|
|
671
|
-
|
|
671
|
+
|
|
672
672
|
Args:
|
|
673
673
|
package: Package to process alerts from
|
|
674
674
|
alerts_collection: Dictionary to store processed alerts
|
|
675
675
|
packages: Dictionary of all packages for dependency lookup
|
|
676
|
-
|
|
676
|
+
|
|
677
677
|
Returns:
|
|
678
678
|
Updated alerts collection dictionary
|
|
679
679
|
"""
|
|
@@ -723,11 +723,11 @@ class Core:
|
|
|
723
723
|
def save_file(file_name: str, content: str) -> None:
|
|
724
724
|
"""
|
|
725
725
|
Saves content to a file, raising an error if the save fails.
|
|
726
|
-
|
|
726
|
+
|
|
727
727
|
Args:
|
|
728
728
|
file_name: Path to save the file
|
|
729
729
|
content: Content to write to the file
|
|
730
|
-
|
|
730
|
+
|
|
731
731
|
Raises:
|
|
732
732
|
IOError: If file cannot be written
|
|
733
733
|
"""
|
|
@@ -742,10 +742,10 @@ class Core:
|
|
|
742
742
|
def has_manifest_files(files: list) -> bool:
|
|
743
743
|
"""
|
|
744
744
|
Checks if any files in the list are supported manifest files.
|
|
745
|
-
|
|
745
|
+
|
|
746
746
|
Args:
|
|
747
747
|
files: List of file paths to check
|
|
748
|
-
|
|
748
|
+
|
|
749
749
|
Returns:
|
|
750
750
|
True if any files match manifest patterns, False otherwise
|
|
751
751
|
"""
|
|
@@ -764,41 +764,41 @@ class Core:
|
|
|
764
764
|
def get_capabilities_for_added_packages(added_packages: Dict[str, Package]) -> Dict[str, List[str]]:
|
|
765
765
|
"""
|
|
766
766
|
Maps added packages to their capabilities based on their alerts.
|
|
767
|
-
|
|
767
|
+
|
|
768
768
|
Args:
|
|
769
769
|
added_packages: Dictionary of packages added in new scan
|
|
770
|
-
|
|
770
|
+
|
|
771
771
|
Returns:
|
|
772
772
|
Dictionary mapping package IDs to their capability lists
|
|
773
773
|
"""
|
|
774
774
|
capabilities: Dict[str, List[str]] = {}
|
|
775
|
-
|
|
775
|
+
|
|
776
776
|
for package_id, package in added_packages.items():
|
|
777
777
|
for alert in package.alerts:
|
|
778
778
|
if alert["type"] in Core.ALERT_TYPE_TO_CAPABILITY:
|
|
779
779
|
value = Core.ALERT_TYPE_TO_CAPABILITY[alert["type"]]
|
|
780
|
-
|
|
780
|
+
|
|
781
781
|
if package_id not in capabilities:
|
|
782
782
|
capabilities[package_id] = [value]
|
|
783
783
|
elif value not in capabilities[package_id]:
|
|
784
784
|
capabilities[package_id].append(value)
|
|
785
|
-
|
|
785
|
+
|
|
786
786
|
return capabilities
|
|
787
787
|
|
|
788
788
|
@staticmethod
|
|
789
789
|
def get_new_alerts(
|
|
790
|
-
added_package_alerts: Dict[str, List[Issue]],
|
|
790
|
+
added_package_alerts: Dict[str, List[Issue]],
|
|
791
791
|
removed_package_alerts: Dict[str, List[Issue]],
|
|
792
792
|
ignore_readded: bool = True
|
|
793
793
|
) -> List[Issue]:
|
|
794
794
|
"""
|
|
795
795
|
Find alerts that are new or changed between added and removed packages.
|
|
796
|
-
|
|
796
|
+
|
|
797
797
|
Args:
|
|
798
798
|
added_package_alerts: Dictionary of alerts from packages that were added
|
|
799
799
|
removed_package_alerts: Dictionary of alerts from packages that were removed
|
|
800
800
|
ignore_readded: If True, don't report alerts that were both removed and added
|
|
801
|
-
|
|
801
|
+
|
|
802
802
|
Returns:
|
|
803
803
|
List of newly found alerts
|
|
804
804
|
"""
|
|
@@ -810,7 +810,7 @@ class Core:
|
|
|
810
810
|
new_alerts = added_package_alerts[alert_key]
|
|
811
811
|
for alert in new_alerts:
|
|
812
812
|
alert_str = f"{alert.purl},{alert.manifests},{alert.type}"
|
|
813
|
-
|
|
813
|
+
|
|
814
814
|
if alert.error or alert.warn:
|
|
815
815
|
if alert_str not in consolidated_alerts:
|
|
816
816
|
alerts.append(alert)
|
|
@@ -818,10 +818,10 @@ class Core:
|
|
|
818
818
|
else:
|
|
819
819
|
new_alerts = added_package_alerts[alert_key]
|
|
820
820
|
removed_alerts = removed_package_alerts[alert_key]
|
|
821
|
-
|
|
821
|
+
|
|
822
822
|
for alert in new_alerts:
|
|
823
823
|
alert_str = f"{alert.purl},{alert.manifests},{alert.type}"
|
|
824
|
-
|
|
824
|
+
|
|
825
825
|
# Only add if:
|
|
826
826
|
# 1. Alert isn't in removed packages (or we're not ignoring readded alerts)
|
|
827
827
|
# 2. We haven't already recorded this alert
|
|
@@ -832,5 +832,3 @@ class Core:
|
|
|
832
832
|
consolidated_alerts.add(alert_str)
|
|
833
833
|
|
|
834
834
|
return alerts
|
|
835
|
-
|
|
836
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: socketsecurity
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.8
|
|
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>
|
|
@@ -19,7 +19,7 @@ Requires-Dist: prettytable
|
|
|
19
19
|
Requires-Dist: GitPython
|
|
20
20
|
Requires-Dist: packaging
|
|
21
21
|
Requires-Dist: python-dotenv
|
|
22
|
-
Requires-Dist: socket-sdk-python>=2.0.
|
|
22
|
+
Requires-Dist: socket-sdk-python>=2.0.8
|
|
23
23
|
Provides-Extra: test
|
|
24
24
|
Requires-Dist: pytest>=7.4.0; extra == "test"
|
|
25
25
|
Requires-Dist: pytest-cov>=4.1.0; extra == "test"
|
|
@@ -28,6 +28,7 @@ Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
|
|
|
28
28
|
Requires-Dist: pytest-watch>=4.2.0; extra == "test"
|
|
29
29
|
Provides-Extra: dev
|
|
30
30
|
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine; extra == "dev"
|
|
31
32
|
Requires-Dist: pip-tools>=7.4.0; extra == "dev"
|
|
32
33
|
|
|
33
34
|
# Socket Security CLI
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|