socketsecurity 2.0.6__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.6/socketsecurity.egg-info → socketsecurity-2.0.8}/PKG-INFO +5 -2
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/README.md +2 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/pyproject.toml +3 -2
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/config.py +8 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/__init__.py +172 -142
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/classes.py +7 -1
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/scm/github.py +3 -1
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/utils.py +23 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/socketcli.py +9 -4
- {socketsecurity-2.0.6 → socketsecurity-2.0.8/socketsecurity.egg-info}/PKG-INFO +5 -2
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity.egg-info/requires.txt +2 -1
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/LICENSE +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/setup.cfg +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/git_interface.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/issues.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/licenses.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/messages.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/scm/gitlab.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/scm_comments.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity/output.py +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity.egg-info/SOURCES.txt +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity.egg-info/dependency_links.txt +0 -0
- {socketsecurity-2.0.6 → socketsecurity-2.0.8}/socketsecurity.egg-info/entry_points.txt +0 -0
- {socketsecurity-2.0.6 → 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
|
|
@@ -42,6 +43,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--integration {api,github,
|
|
|
42
43
|
[--target-path TARGET_PATH] [--sbom-file SBOM_FILE] [--files FILES] [--default-branch] [--pending-head]
|
|
43
44
|
[--generate-license] [--enable-debug] [--enable-json] [--enable-sarif] [--disable-overview] [--disable-security-issue]
|
|
44
45
|
[--allow-unverified] [--ignore-commit-files] [--disable-blocking] [--scm SCM] [--timeout TIMEOUT]
|
|
46
|
+
[--exclude-license-details]
|
|
45
47
|
````
|
|
46
48
|
|
|
47
49
|
If you don't want to provide the Socket API Token every time then you can use the environment variable `SOCKET_SECURITY_API_KEY`
|
|
@@ -90,6 +92,7 @@ If you don't want to provide the Socket API Token every time then you can use th
|
|
|
90
92
|
| --enable-json | False | False | Output in JSON format |
|
|
91
93
|
| --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format|
|
|
92
94
|
| --disable-overview | False | False | Disable overview output |
|
|
95
|
+
| --exclude-license-details | False | False | Exclude license details from the diff report (boosts performance for large repos) |
|
|
93
96
|
|
|
94
97
|
#### Security Configuration
|
|
95
98
|
| Parameter | Required | Default | Description |
|
|
@@ -10,6 +10,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--integration {api,github,
|
|
|
10
10
|
[--target-path TARGET_PATH] [--sbom-file SBOM_FILE] [--files FILES] [--default-branch] [--pending-head]
|
|
11
11
|
[--generate-license] [--enable-debug] [--enable-json] [--enable-sarif] [--disable-overview] [--disable-security-issue]
|
|
12
12
|
[--allow-unverified] [--ignore-commit-files] [--disable-blocking] [--scm SCM] [--timeout TIMEOUT]
|
|
13
|
+
[--exclude-license-details]
|
|
13
14
|
````
|
|
14
15
|
|
|
15
16
|
If you don't want to provide the Socket API Token every time then you can use the environment variable `SOCKET_SECURITY_API_KEY`
|
|
@@ -58,6 +59,7 @@ If you don't want to provide the Socket API Token every time then you can use th
|
|
|
58
59
|
| --enable-json | False | False | Output in JSON format |
|
|
59
60
|
| --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format|
|
|
60
61
|
| --disable-overview | False | False | Disable overview output |
|
|
62
|
+
| --exclude-license-details | False | False | Exclude license details from the diff report (boosts performance for large repos) |
|
|
61
63
|
|
|
62
64
|
#### Security Configuration
|
|
63
65
|
| Parameter | Required | Default | Description |
|
|
@@ -12,8 +12,8 @@ dependencies = [
|
|
|
12
12
|
'prettytable',
|
|
13
13
|
'GitPython',
|
|
14
14
|
'packaging',
|
|
15
|
-
'python-dotenv',
|
|
16
|
-
'socket-sdk-python>=2.0.
|
|
15
|
+
'python-dotenv',
|
|
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'
|
|
@@ -33,6 +33,7 @@ class CliConfig:
|
|
|
33
33
|
integration_org_slug: Optional[str] = None
|
|
34
34
|
pending_head: bool = False
|
|
35
35
|
timeout: Optional[int] = 1200
|
|
36
|
+
exclude_license_details: bool = False
|
|
36
37
|
@classmethod
|
|
37
38
|
def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
|
|
38
39
|
parser = create_argument_parser()
|
|
@@ -71,6 +72,7 @@ class CliConfig:
|
|
|
71
72
|
'integration_type': args.integration,
|
|
72
73
|
'pending_head': args.pending_head,
|
|
73
74
|
'timeout': args.timeout,
|
|
75
|
+
'exclude_license_details': args.exclude_license_details,
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
if args.owner:
|
|
@@ -283,6 +285,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
283
285
|
action="store_true",
|
|
284
286
|
help=argparse.SUPPRESS
|
|
285
287
|
)
|
|
288
|
+
output_group.add_argument(
|
|
289
|
+
"--exclude-license-details",
|
|
290
|
+
dest="exclude_license_details",
|
|
291
|
+
action="store_true",
|
|
292
|
+
help="Exclude license details from the diff report (boosts performance for large repos)"
|
|
293
|
+
)
|
|
286
294
|
|
|
287
295
|
# Security Configuration
|
|
288
296
|
security_group = parser.add_argument_group('Security Configuration')
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import json
|
|
3
1
|
import logging
|
|
2
|
+
import sys
|
|
4
3
|
import time
|
|
5
4
|
from dataclasses import asdict
|
|
6
5
|
from glob import glob
|
|
7
6
|
from pathlib import PurePath
|
|
8
|
-
from typing import BinaryIO, Dict, List,
|
|
7
|
+
from typing import BinaryIO, Dict, List, Tuple
|
|
9
8
|
|
|
10
9
|
from socketdev import socketdev
|
|
11
|
-
from socketdev.
|
|
12
|
-
|
|
13
|
-
SocketArtifact,
|
|
14
|
-
DiffArtifact,
|
|
15
|
-
)
|
|
10
|
+
from socketdev.exceptions import APIFailure
|
|
11
|
+
from socketdev.fullscans import FullScanParams, SocketArtifact
|
|
16
12
|
from socketdev.org import Organization
|
|
17
13
|
from socketdev.repos import RepositoryInfo
|
|
18
14
|
from socketdev.settings import SecurityPolicyRule
|
|
@@ -26,9 +22,7 @@ from socketsecurity.core.classes import (
|
|
|
26
22
|
Package,
|
|
27
23
|
Purl,
|
|
28
24
|
)
|
|
29
|
-
from socketsecurity.core.exceptions import
|
|
30
|
-
APIResourceNotFound,
|
|
31
|
-
)
|
|
25
|
+
from socketsecurity.core.exceptions import APIResourceNotFound
|
|
32
26
|
from socketsecurity.core.licenses import Licenses
|
|
33
27
|
|
|
34
28
|
from .socket_config import SocketConfig
|
|
@@ -45,11 +39,11 @@ log = logging.getLogger("socketdev")
|
|
|
45
39
|
|
|
46
40
|
class Core:
|
|
47
41
|
"""Main class for interacting with Socket Security API and processing scan results."""
|
|
48
|
-
|
|
42
|
+
|
|
49
43
|
ALERT_TYPE_TO_CAPABILITY = {
|
|
50
44
|
"envVars": "Environment Variables",
|
|
51
45
|
"networkAccess": "Network Access",
|
|
52
|
-
"filesystemAccess": "File System Access",
|
|
46
|
+
"filesystemAccess": "File System Access",
|
|
53
47
|
"shellAccess": "Shell Access",
|
|
54
48
|
"usesEval": "Uses Eval",
|
|
55
49
|
"unsafe": "Unsafe"
|
|
@@ -79,7 +73,7 @@ class Core:
|
|
|
79
73
|
|
|
80
74
|
def get_org_id_slug(self) -> Tuple[str, str]:
|
|
81
75
|
"""Gets the Org ID and Org Slug for the API Token."""
|
|
82
|
-
response = self.sdk.org.get()
|
|
76
|
+
response = self.sdk.org.get(use_types=True)
|
|
83
77
|
organizations: Dict[str, Organization] = response.get("organizations", {})
|
|
84
78
|
|
|
85
79
|
if len(organizations) == 1:
|
|
@@ -89,33 +83,33 @@ class Core:
|
|
|
89
83
|
|
|
90
84
|
def get_sbom_data(self, full_scan_id: str) -> Dict[str, SocketArtifact]:
|
|
91
85
|
"""Returns the list of SBOM artifacts for a full scan."""
|
|
92
|
-
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)
|
|
93
87
|
if not response.success:
|
|
94
88
|
log.debug(f"Failed to get SBOM data for full-scan {full_scan_id}")
|
|
95
89
|
log.debug(response.message)
|
|
96
90
|
return {}
|
|
97
91
|
|
|
98
92
|
return response.artifacts
|
|
99
|
-
|
|
93
|
+
|
|
100
94
|
def get_sbom_data_list(self, artifacts_dict: Dict[str, SocketArtifact]) -> list[SocketArtifact]:
|
|
101
95
|
"""Converts artifacts dictionary to a list."""
|
|
102
96
|
return list(artifacts_dict.values())
|
|
103
97
|
|
|
104
98
|
def get_security_policy(self) -> Dict[str, SecurityPolicyRule]:
|
|
105
99
|
"""Gets the organization's security policy."""
|
|
106
|
-
response = self.sdk.settings.get(self.config.org_slug)
|
|
107
|
-
|
|
100
|
+
response = self.sdk.settings.get(self.config.org_slug, use_types=True)
|
|
101
|
+
|
|
108
102
|
if not response.success:
|
|
109
103
|
log.error(f"Failed to get security policy: {response.status}")
|
|
110
104
|
log.error(response.message)
|
|
111
105
|
raise Exception(f"Failed to get security policy: {response.status}, message: {response.message}")
|
|
112
|
-
|
|
106
|
+
|
|
113
107
|
return response.securityPolicyRules
|
|
114
108
|
|
|
115
109
|
def create_sbom_output(self, diff: Diff) -> dict:
|
|
116
110
|
"""Creates CycloneDX output for a given diff."""
|
|
117
111
|
try:
|
|
118
|
-
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)
|
|
119
113
|
if not result.success:
|
|
120
114
|
log.error(f"Failed to get CycloneDX Output for full-scan {diff.id}")
|
|
121
115
|
log.error(result.message)
|
|
@@ -123,7 +117,7 @@ class Core:
|
|
|
123
117
|
|
|
124
118
|
result.pop("success", None)
|
|
125
119
|
return result
|
|
126
|
-
except Exception
|
|
120
|
+
except Exception:
|
|
127
121
|
log.error(f"Unable to get CycloneDX Output for {diff.id}")
|
|
128
122
|
log.error(result.get("message", "No error message provided"))
|
|
129
123
|
return {}
|
|
@@ -132,23 +126,23 @@ class Core:
|
|
|
132
126
|
def find_files(path: str) -> List[str]:
|
|
133
127
|
"""
|
|
134
128
|
Finds supported manifest files in the given path.
|
|
135
|
-
|
|
129
|
+
|
|
136
130
|
Args:
|
|
137
131
|
path: Path to search for manifest files
|
|
138
|
-
|
|
132
|
+
|
|
139
133
|
Returns:
|
|
140
134
|
List of found manifest file paths
|
|
141
135
|
"""
|
|
142
136
|
log.debug("Starting Find Files")
|
|
143
137
|
start_time = time.time()
|
|
144
138
|
files = set()
|
|
145
|
-
|
|
139
|
+
|
|
146
140
|
for ecosystem in socket_globs:
|
|
147
141
|
patterns = socket_globs[ecosystem]
|
|
148
142
|
for file_name in patterns:
|
|
149
143
|
pattern = Core.to_case_insensitive_regex(patterns[file_name]["pattern"])
|
|
150
144
|
file_path = f"{path}/**/{pattern}"
|
|
151
|
-
log.debug(f"Globbing {file_path}")
|
|
145
|
+
#log.debug(f"Globbing {file_path}")
|
|
152
146
|
glob_start = time.time()
|
|
153
147
|
glob_files = glob(file_path, recursive=True)
|
|
154
148
|
for glob_file in glob_files:
|
|
@@ -156,26 +150,29 @@ class Core:
|
|
|
156
150
|
files.add(glob_file)
|
|
157
151
|
glob_end = time.time()
|
|
158
152
|
glob_total_time = glob_end - glob_start
|
|
159
|
-
log.debug(f"Glob for pattern {file_path} took {glob_total_time:.2f} seconds")
|
|
153
|
+
#log.debug(f"Glob for pattern {file_path} took {glob_total_time:.2f} seconds")
|
|
160
154
|
|
|
161
155
|
log.debug("Finished Find Files")
|
|
162
156
|
end_time = time.time()
|
|
163
157
|
total_time = end_time - start_time
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
files_list = list(files)
|
|
159
|
+
if len(files_list) > 5:
|
|
160
|
+
log.debug(f"{len(files_list)} Files found ({total_time:.2f}s): {', '.join(files_list[:5])}, ...")
|
|
161
|
+
else:
|
|
162
|
+
log.debug(f"{len(files_list)} Files found ({total_time:.2f}s): {', '.join(files_list)}")
|
|
166
163
|
return list(files)
|
|
167
|
-
|
|
164
|
+
|
|
168
165
|
@staticmethod
|
|
169
166
|
def to_case_insensitive_regex(input_string: str) -> str:
|
|
170
167
|
"""
|
|
171
168
|
Converts a string into a case-insensitive regex pattern.
|
|
172
|
-
|
|
169
|
+
|
|
173
170
|
Args:
|
|
174
171
|
input_string: String to convert
|
|
175
|
-
|
|
172
|
+
|
|
176
173
|
Returns:
|
|
177
174
|
Case-insensitive regex pattern
|
|
178
|
-
|
|
175
|
+
|
|
179
176
|
Example:
|
|
180
177
|
"pipfile" -> "[Pp][Ii][Pp][Ff][Ii][Ll][Ee]"
|
|
181
178
|
"""
|
|
@@ -185,79 +182,79 @@ class Core:
|
|
|
185
182
|
def load_files_for_sending(files: List[str], workspace: str) -> List[Tuple[str, Tuple[str, BinaryIO]]]:
|
|
186
183
|
"""
|
|
187
184
|
Prepares files for sending to the Socket API.
|
|
188
|
-
|
|
185
|
+
|
|
189
186
|
Args:
|
|
190
187
|
files: List of file paths from find_files()
|
|
191
188
|
workspace: Base directory path to make paths relative to
|
|
192
|
-
|
|
189
|
+
|
|
193
190
|
Returns:
|
|
194
191
|
List of tuples formatted for requests multipart upload:
|
|
195
192
|
[(field_name, (filename, file_object)), ...]
|
|
196
193
|
"""
|
|
197
194
|
send_files = []
|
|
198
|
-
|
|
195
|
+
|
|
199
196
|
for file_path in files:
|
|
200
197
|
if "/" in file_path:
|
|
201
198
|
_, name = file_path.rsplit("/", 1)
|
|
202
199
|
else:
|
|
203
200
|
name = file_path
|
|
204
|
-
|
|
201
|
+
|
|
205
202
|
if file_path.startswith(workspace):
|
|
206
203
|
key = file_path[len(workspace):]
|
|
207
204
|
else:
|
|
208
205
|
key = file_path
|
|
209
|
-
|
|
206
|
+
|
|
210
207
|
key = key.lstrip("/")
|
|
211
208
|
key = key.lstrip("./")
|
|
212
|
-
|
|
209
|
+
|
|
213
210
|
f = open(file_path, 'rb')
|
|
214
211
|
payload = (key, (name, f))
|
|
215
212
|
send_files.append(payload)
|
|
216
|
-
|
|
213
|
+
|
|
217
214
|
return send_files
|
|
218
215
|
|
|
219
|
-
def create_full_scan(self, files: List[str], params: FullScanParams) -> FullScan:
|
|
216
|
+
def create_full_scan(self, files: List[str], params: FullScanParams, has_head_scan: bool = False) -> FullScan:
|
|
220
217
|
"""
|
|
221
218
|
Creates a new full scan via the Socket API.
|
|
222
|
-
|
|
219
|
+
|
|
223
220
|
Args:
|
|
224
221
|
files: List of files to scan
|
|
225
222
|
params: Parameters for the full scan
|
|
226
|
-
|
|
223
|
+
|
|
227
224
|
Returns:
|
|
228
225
|
FullScan object with scan results
|
|
229
226
|
"""
|
|
230
227
|
log.debug("Creating new full scan")
|
|
231
228
|
create_full_start = time.time()
|
|
232
229
|
|
|
233
|
-
res = self.sdk.fullscans.post(files, params)
|
|
230
|
+
res = self.sdk.fullscans.post(files, params, use_types=True)
|
|
234
231
|
if not res.success:
|
|
235
232
|
log.error(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
236
233
|
raise Exception(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
237
234
|
|
|
238
235
|
full_scan = FullScan(**asdict(res.data))
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
236
|
+
if not has_head_scan:
|
|
237
|
+
full_scan_artifacts_dict = self.get_sbom_data(full_scan.id)
|
|
238
|
+
full_scan.sbom_artifacts = self.get_sbom_data_list(full_scan_artifacts_dict)
|
|
239
|
+
full_scan.packages = self.create_packages_dict(full_scan.sbom_artifacts)
|
|
243
240
|
|
|
244
241
|
create_full_end = time.time()
|
|
245
242
|
total_time = create_full_end - create_full_start
|
|
246
243
|
log.debug(f"New Full Scan created in {total_time:.2f} seconds")
|
|
247
|
-
|
|
244
|
+
|
|
248
245
|
return full_scan
|
|
249
246
|
|
|
250
247
|
def get_full_scan(self, full_scan_id: str) -> FullScan:
|
|
251
248
|
"""
|
|
252
249
|
Get a FullScan object for an existing full scan including sbom_artifacts and packages.
|
|
253
|
-
|
|
250
|
+
|
|
254
251
|
Args:
|
|
255
252
|
full_scan_id: The ID of the full scan to get
|
|
256
|
-
|
|
253
|
+
|
|
257
254
|
Returns:
|
|
258
255
|
The FullScan object with populated artifacts and packages
|
|
259
256
|
"""
|
|
260
|
-
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)
|
|
261
258
|
full_scan = FullScan(**asdict(full_scan_metadata.data))
|
|
262
259
|
full_scan_artifacts_dict = self.get_sbom_data(full_scan_id)
|
|
263
260
|
full_scan.sbom_artifacts = self.get_sbom_data_list(full_scan_artifacts_dict)
|
|
@@ -267,10 +264,10 @@ class Core:
|
|
|
267
264
|
def create_packages_dict(self, sbom_artifacts: list[SocketArtifact]) -> dict[str, Package]:
|
|
268
265
|
"""
|
|
269
266
|
Creates a dictionary of Package objects from SBOM artifacts.
|
|
270
|
-
|
|
267
|
+
|
|
271
268
|
Args:
|
|
272
269
|
sbom_artifacts: List of SBOM artifacts from the scan
|
|
273
|
-
|
|
270
|
+
|
|
274
271
|
Returns:
|
|
275
272
|
Dictionary mapping package IDs to Package objects
|
|
276
273
|
"""
|
|
@@ -288,87 +285,122 @@ class Core:
|
|
|
288
285
|
top_level_count[top_id] = 1
|
|
289
286
|
else:
|
|
290
287
|
top_level_count[top_id] += 1
|
|
291
|
-
|
|
288
|
+
|
|
292
289
|
for package_id, package in packages.items():
|
|
293
290
|
package.transitives = top_level_count.get(package_id, 0)
|
|
294
291
|
|
|
295
292
|
return packages
|
|
296
|
-
|
|
293
|
+
|
|
297
294
|
def get_package_license_text(self, package: Package) -> str:
|
|
298
295
|
"""
|
|
299
296
|
Gets the license text for a package if available.
|
|
300
|
-
|
|
297
|
+
|
|
301
298
|
Args:
|
|
302
299
|
package: Package object to get license text for
|
|
303
|
-
|
|
300
|
+
|
|
304
301
|
Returns:
|
|
305
302
|
License text if found, empty string otherwise
|
|
306
303
|
"""
|
|
307
304
|
if package.license is None:
|
|
308
305
|
return ""
|
|
309
|
-
|
|
306
|
+
|
|
310
307
|
license_raw = package.license
|
|
311
308
|
all_licenses = Licenses()
|
|
312
309
|
license_str = Licenses.make_python_safe(license_raw)
|
|
313
|
-
|
|
310
|
+
|
|
314
311
|
if license_str is not None and hasattr(all_licenses, license_str):
|
|
315
312
|
license_obj = getattr(all_licenses, license_str)
|
|
316
313
|
return license_obj.licenseText
|
|
317
|
-
|
|
314
|
+
|
|
318
315
|
return ""
|
|
319
316
|
|
|
320
|
-
def get_repo_info(self, repo_slug: str) -> RepositoryInfo:
|
|
317
|
+
def get_repo_info(self, repo_slug: str, default_branch: str = "socket-default-branch") -> RepositoryInfo:
|
|
321
318
|
"""
|
|
322
319
|
Gets repository information from the Socket API.
|
|
323
|
-
|
|
320
|
+
|
|
324
321
|
Args:
|
|
325
322
|
repo_slug: Repository slug to get info for
|
|
326
|
-
|
|
323
|
+
default_branch: Default branch string to use if the repo doesn't exist
|
|
324
|
+
|
|
327
325
|
Returns:
|
|
328
326
|
RepositoryInfo object
|
|
329
|
-
|
|
327
|
+
|
|
330
328
|
Raises:
|
|
331
329
|
Exception: If API request fails
|
|
332
330
|
"""
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
331
|
+
try:
|
|
332
|
+
response = self.sdk.repos.repo(self.config.org_slug, repo_slug, use_types=True)
|
|
333
|
+
if not response.success:
|
|
334
|
+
log.error(f"Failed to get repository: {response.status}")
|
|
335
|
+
log.error(response.message)
|
|
336
|
+
# raise Exception(f"Failed to get repository info: {response.status}, message: {response.message}")
|
|
337
|
+
except APIFailure:
|
|
338
|
+
log.warning(f"Failed to get repository {repo_slug}, attempting to create it")
|
|
339
|
+
create_response = self.sdk.repos.post(self.config.org_slug, name=repo_slug, default_branch=default_branch)
|
|
340
|
+
if not create_response.success:
|
|
341
|
+
log.error(f"Failed to create repository: {create_response.status}")
|
|
342
|
+
log.error(create_response.message)
|
|
343
|
+
raise Exception(
|
|
344
|
+
f"Failed to create repository: {create_response.status}, message: {create_response.message}"
|
|
345
|
+
)
|
|
346
|
+
else:
|
|
347
|
+
return create_response.data
|
|
338
348
|
return response.data
|
|
339
349
|
|
|
340
350
|
def get_head_scan_for_repo(self, repo_slug: str) -> str:
|
|
341
351
|
"""
|
|
342
352
|
Gets the head scan ID for a repository.
|
|
343
|
-
|
|
353
|
+
|
|
344
354
|
Args:
|
|
345
355
|
repo_slug: Repository slug to get head scan for
|
|
346
|
-
|
|
356
|
+
|
|
347
357
|
Returns:
|
|
348
358
|
Head scan ID if it exists, None otherwise
|
|
349
359
|
"""
|
|
350
360
|
repo_info = self.get_repo_info(repo_slug)
|
|
351
361
|
return repo_info.head_full_scan_id if repo_info.head_full_scan_id else None
|
|
352
362
|
|
|
353
|
-
|
|
363
|
+
@staticmethod
|
|
364
|
+
def update_package_values(pkg: Package) -> Package:
|
|
365
|
+
pkg.purl = f"{pkg.name}@{pkg.version}"
|
|
366
|
+
pkg.url = f"https://socket.dev/{pkg.type}/package"
|
|
367
|
+
if pkg.namespace:
|
|
368
|
+
pkg.purl = f"{pkg.namespace}/{pkg.purl}"
|
|
369
|
+
pkg.url += f"/{pkg.namespace}"
|
|
370
|
+
pkg.url += f"/{pkg.name}/overview/{pkg.version}"
|
|
371
|
+
return pkg
|
|
372
|
+
|
|
373
|
+
def get_added_and_removed_packages(self, head_full_scan_id: str, new_full_scan: FullScan) -> Tuple[Dict[str, Package], Dict[str, Package]]:
|
|
354
374
|
"""
|
|
355
375
|
Get packages that were added and removed between scans.
|
|
356
|
-
|
|
376
|
+
|
|
357
377
|
Args:
|
|
358
378
|
head_full_scan: Previous scan (may be None if first scan)
|
|
359
|
-
|
|
360
|
-
|
|
379
|
+
head_full_scan_id: New scan just created
|
|
380
|
+
|
|
361
381
|
Returns:
|
|
362
382
|
Tuple of (added_packages, removed_packages) dictionaries
|
|
363
383
|
"""
|
|
364
|
-
if
|
|
384
|
+
if head_full_scan_id is None:
|
|
365
385
|
log.info(f"No head scan found. New scan ID: {new_full_scan.id}")
|
|
366
386
|
return new_full_scan.packages, {}
|
|
367
|
-
|
|
368
|
-
log.info(f"Comparing scans - Head scan ID: {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
387
|
+
|
|
388
|
+
log.info(f"Comparing scans - Head scan ID: {head_full_scan_id}, New scan ID: {new_full_scan.id}")
|
|
389
|
+
diff_start = time.time()
|
|
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
|
+
|
|
401
|
+
diff_end = time.time()
|
|
402
|
+
log.info(f"Diff Report Gathered in {diff_end - diff_start:.2f} seconds")
|
|
403
|
+
log.info("Diff report artifact counts:")
|
|
372
404
|
log.info(f"Added: {len(diff_report.artifacts.added)}")
|
|
373
405
|
log.info(f"Removed: {len(diff_report.artifacts.removed)}")
|
|
374
406
|
log.info(f"Unchanged: {len(diff_report.artifacts.unchanged)}")
|
|
@@ -384,32 +416,24 @@ class Core:
|
|
|
384
416
|
for artifact in added_artifacts:
|
|
385
417
|
try:
|
|
386
418
|
pkg = Package.from_diff_artifact(asdict(artifact))
|
|
419
|
+
pkg = Core.update_package_values(pkg)
|
|
387
420
|
added_packages[artifact.id] = pkg
|
|
388
421
|
except KeyError:
|
|
389
422
|
log.error(f"KeyError: Could not create package from added artifact {artifact.id}")
|
|
390
423
|
log.error(f"Artifact details - name: {artifact.name}, version: {artifact.version}")
|
|
391
|
-
|
|
392
|
-
if matches:
|
|
393
|
-
log.error(f"Found {len(matches)} packages with matching name/version:")
|
|
394
|
-
for m in matches:
|
|
395
|
-
log.error(f" ID: {m.id}, name: {m.name}, version: {m.version}")
|
|
396
|
-
else:
|
|
397
|
-
log.error("No matching packages found in new_full_scan")
|
|
424
|
+
log.error("No matching packages found in new_full_scan")
|
|
398
425
|
|
|
399
426
|
for artifact in removed_artifacts:
|
|
400
427
|
try:
|
|
401
428
|
pkg = Package.from_diff_artifact(asdict(artifact))
|
|
429
|
+
pkg = Core.update_package_values(pkg)
|
|
430
|
+
if pkg.namespace:
|
|
431
|
+
pkg.purl += f"{pkg.namespace}/{pkg.purl}"
|
|
402
432
|
removed_packages[artifact.id] = pkg
|
|
403
433
|
except KeyError:
|
|
404
434
|
log.error(f"KeyError: Could not create package from removed artifact {artifact.id}")
|
|
405
435
|
log.error(f"Artifact details - name: {artifact.name}, version: {artifact.version}")
|
|
406
|
-
|
|
407
|
-
if matches:
|
|
408
|
-
log.error(f"Found {len(matches)} packages with matching name/version:")
|
|
409
|
-
for m in matches:
|
|
410
|
-
log.error(f" ID: {m.id}, name: {m.name}, version: {m.version}")
|
|
411
|
-
else:
|
|
412
|
-
log.error("No matching packages found in head_full_scan")
|
|
436
|
+
log.error("No matching packages found in head_full_scan")
|
|
413
437
|
|
|
414
438
|
return added_packages, removed_packages
|
|
415
439
|
|
|
@@ -424,7 +448,6 @@ class Core:
|
|
|
424
448
|
Args:
|
|
425
449
|
path: Path to look for manifest files
|
|
426
450
|
params: Query params for the Full Scan endpoint
|
|
427
|
-
|
|
428
451
|
no_change: If True, return empty diff
|
|
429
452
|
"""
|
|
430
453
|
log.debug(f"starting create_new_diff with no_change: {no_change}")
|
|
@@ -435,36 +458,44 @@ class Core:
|
|
|
435
458
|
files = self.find_files(path)
|
|
436
459
|
files_for_sending = self.load_files_for_sending(files, path)
|
|
437
460
|
|
|
438
|
-
log.debug(f"files: {files} found at path {path}")
|
|
439
461
|
if not files:
|
|
440
462
|
return Diff(id="no_diff_id")
|
|
441
463
|
|
|
442
|
-
head_full_scan_id = None
|
|
443
|
-
|
|
444
464
|
try:
|
|
445
465
|
# Get head scan ID
|
|
446
466
|
head_full_scan_id = self.get_head_scan_for_repo(params.repo)
|
|
467
|
+
has_head_scan = True
|
|
447
468
|
except APIResourceNotFound:
|
|
448
469
|
head_full_scan_id = None
|
|
470
|
+
has_head_scan = False
|
|
449
471
|
|
|
450
472
|
# Create new scan
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
473
|
+
try:
|
|
474
|
+
new_scan_start = time.time()
|
|
475
|
+
new_full_scan = self.create_full_scan(files_for_sending, params, has_head_scan)
|
|
476
|
+
new_scan_end = time.time()
|
|
477
|
+
log.info(f"Total time to create new full scan: {new_scan_end - new_scan_start:.2f}")
|
|
478
|
+
except APIFailure as e:
|
|
479
|
+
log.error(f"API Error: {e}")
|
|
480
|
+
sys.exit(1)
|
|
481
|
+
except Exception as e:
|
|
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
|
|
460
486
|
|
|
461
|
-
added_packages, removed_packages = self.get_added_and_removed_packages(
|
|
487
|
+
added_packages, removed_packages = self.get_added_and_removed_packages(head_full_scan_id, new_full_scan)
|
|
462
488
|
|
|
463
489
|
diff = self.create_diff_report(added_packages, removed_packages)
|
|
464
490
|
|
|
465
491
|
base_socket = "https://socket.dev/dashboard/org"
|
|
466
492
|
diff.id = new_full_scan.id
|
|
467
|
-
|
|
493
|
+
|
|
494
|
+
report_url = f"{base_socket}/{self.config.org_slug}/sbom/{diff.id}"
|
|
495
|
+
if not params.include_license_details:
|
|
496
|
+
report_url += "?include_license_details=false"
|
|
497
|
+
diff.report_url = report_url
|
|
498
|
+
|
|
468
499
|
if head_full_scan_id is not None:
|
|
469
500
|
diff.diff_url = f"{base_socket}/{self.config.org_slug}/diff/{diff.id}/{head_full_scan_id}"
|
|
470
501
|
else:
|
|
@@ -473,25 +504,25 @@ class Core:
|
|
|
473
504
|
return diff
|
|
474
505
|
|
|
475
506
|
def create_diff_report(
|
|
476
|
-
self,
|
|
477
|
-
added_packages: Dict[str, Package],
|
|
507
|
+
self,
|
|
508
|
+
added_packages: Dict[str, Package],
|
|
478
509
|
removed_packages: Dict[str, Package],
|
|
479
510
|
direct_only: bool = True
|
|
480
511
|
) -> Diff:
|
|
481
512
|
"""
|
|
482
513
|
Creates a diff report comparing two sets of packages.
|
|
483
|
-
|
|
514
|
+
|
|
484
515
|
Takes packages that were added and removed between two scans and:
|
|
485
516
|
1. Records new/removed packages (direct only by default)
|
|
486
517
|
2. Collects alerts from both sets of packages
|
|
487
518
|
3. Determines new capabilities introduced
|
|
488
|
-
|
|
519
|
+
|
|
489
520
|
Args:
|
|
490
521
|
added_packages: Dict of packages added in new scan
|
|
491
522
|
removed_packages: Dict of packages removed in new scan
|
|
492
523
|
direct_only: If True, only direct dependencies are included in new/removed lists
|
|
493
524
|
(but alerts are still processed for all packages)
|
|
494
|
-
|
|
525
|
+
|
|
495
526
|
Returns:
|
|
496
527
|
Diff object containing the comparison results
|
|
497
528
|
"""
|
|
@@ -546,11 +577,11 @@ class Core:
|
|
|
546
577
|
def create_purl(package_id: str, packages: dict[str, Package]) -> Purl:
|
|
547
578
|
"""
|
|
548
579
|
Creates the extended PURL data for package identification and tracking.
|
|
549
|
-
|
|
580
|
+
|
|
550
581
|
Args:
|
|
551
582
|
package_id: Package ID to create PURL data for
|
|
552
583
|
packages: Dictionary of all packages for transitive dependency lookup
|
|
553
|
-
|
|
584
|
+
|
|
554
585
|
Returns:
|
|
555
586
|
Purl object containing package metadata and dependency information
|
|
556
587
|
"""
|
|
@@ -575,14 +606,14 @@ class Core:
|
|
|
575
606
|
def get_source_data(package: Package, packages: dict) -> list:
|
|
576
607
|
"""
|
|
577
608
|
Determines how a package was introduced into the dependency tree.
|
|
578
|
-
|
|
609
|
+
|
|
579
610
|
For direct dependencies, records the manifest file.
|
|
580
611
|
For transitive dependencies, records the top-level package that introduced it.
|
|
581
|
-
|
|
612
|
+
|
|
582
613
|
Args:
|
|
583
614
|
package: Package to analyze
|
|
584
615
|
packages: Dictionary of all packages for ancestor lookup
|
|
585
|
-
|
|
616
|
+
|
|
586
617
|
Returns:
|
|
587
618
|
List of tuples containing (source, manifest_file) information
|
|
588
619
|
"""
|
|
@@ -609,14 +640,15 @@ class Core:
|
|
|
609
640
|
source = (top_purl, manifests)
|
|
610
641
|
introduced_by.append(source)
|
|
611
642
|
else:
|
|
612
|
-
|
|
643
|
+
pass
|
|
644
|
+
# log.debug(f"Unable to get top level package info for {top_id}")
|
|
613
645
|
return introduced_by
|
|
614
646
|
|
|
615
647
|
@staticmethod
|
|
616
648
|
def add_purl_capabilities(diff: Diff) -> None:
|
|
617
649
|
"""
|
|
618
650
|
Adds capability information to each package in the diff's new_packages list.
|
|
619
|
-
|
|
651
|
+
|
|
620
652
|
Args:
|
|
621
653
|
diff: Diff object to update with capability information
|
|
622
654
|
"""
|
|
@@ -630,18 +662,18 @@ class Core:
|
|
|
630
662
|
new_packages.append(new_purl)
|
|
631
663
|
else:
|
|
632
664
|
new_packages.append(purl)
|
|
633
|
-
|
|
665
|
+
|
|
634
666
|
diff.new_packages = new_packages
|
|
635
667
|
|
|
636
668
|
def add_package_alerts_to_collection(self, package: Package, alerts_collection: dict, packages: dict) -> dict:
|
|
637
669
|
"""
|
|
638
670
|
Processes alerts from a package and adds them to a shared alerts collection.
|
|
639
|
-
|
|
671
|
+
|
|
640
672
|
Args:
|
|
641
673
|
package: Package to process alerts from
|
|
642
674
|
alerts_collection: Dictionary to store processed alerts
|
|
643
675
|
packages: Dictionary of all packages for dependency lookup
|
|
644
|
-
|
|
676
|
+
|
|
645
677
|
Returns:
|
|
646
678
|
Updated alerts collection dictionary
|
|
647
679
|
"""
|
|
@@ -691,11 +723,11 @@ class Core:
|
|
|
691
723
|
def save_file(file_name: str, content: str) -> None:
|
|
692
724
|
"""
|
|
693
725
|
Saves content to a file, raising an error if the save fails.
|
|
694
|
-
|
|
726
|
+
|
|
695
727
|
Args:
|
|
696
728
|
file_name: Path to save the file
|
|
697
729
|
content: Content to write to the file
|
|
698
|
-
|
|
730
|
+
|
|
699
731
|
Raises:
|
|
700
732
|
IOError: If file cannot be written
|
|
701
733
|
"""
|
|
@@ -710,10 +742,10 @@ class Core:
|
|
|
710
742
|
def has_manifest_files(files: list) -> bool:
|
|
711
743
|
"""
|
|
712
744
|
Checks if any files in the list are supported manifest files.
|
|
713
|
-
|
|
745
|
+
|
|
714
746
|
Args:
|
|
715
747
|
files: List of file paths to check
|
|
716
|
-
|
|
748
|
+
|
|
717
749
|
Returns:
|
|
718
750
|
True if any files match manifest patterns, False otherwise
|
|
719
751
|
"""
|
|
@@ -732,41 +764,41 @@ class Core:
|
|
|
732
764
|
def get_capabilities_for_added_packages(added_packages: Dict[str, Package]) -> Dict[str, List[str]]:
|
|
733
765
|
"""
|
|
734
766
|
Maps added packages to their capabilities based on their alerts.
|
|
735
|
-
|
|
767
|
+
|
|
736
768
|
Args:
|
|
737
769
|
added_packages: Dictionary of packages added in new scan
|
|
738
|
-
|
|
770
|
+
|
|
739
771
|
Returns:
|
|
740
772
|
Dictionary mapping package IDs to their capability lists
|
|
741
773
|
"""
|
|
742
774
|
capabilities: Dict[str, List[str]] = {}
|
|
743
|
-
|
|
775
|
+
|
|
744
776
|
for package_id, package in added_packages.items():
|
|
745
777
|
for alert in package.alerts:
|
|
746
778
|
if alert["type"] in Core.ALERT_TYPE_TO_CAPABILITY:
|
|
747
779
|
value = Core.ALERT_TYPE_TO_CAPABILITY[alert["type"]]
|
|
748
|
-
|
|
780
|
+
|
|
749
781
|
if package_id not in capabilities:
|
|
750
782
|
capabilities[package_id] = [value]
|
|
751
783
|
elif value not in capabilities[package_id]:
|
|
752
784
|
capabilities[package_id].append(value)
|
|
753
|
-
|
|
785
|
+
|
|
754
786
|
return capabilities
|
|
755
787
|
|
|
756
788
|
@staticmethod
|
|
757
789
|
def get_new_alerts(
|
|
758
|
-
added_package_alerts: Dict[str, List[Issue]],
|
|
790
|
+
added_package_alerts: Dict[str, List[Issue]],
|
|
759
791
|
removed_package_alerts: Dict[str, List[Issue]],
|
|
760
792
|
ignore_readded: bool = True
|
|
761
793
|
) -> List[Issue]:
|
|
762
794
|
"""
|
|
763
795
|
Find alerts that are new or changed between added and removed packages.
|
|
764
|
-
|
|
796
|
+
|
|
765
797
|
Args:
|
|
766
798
|
added_package_alerts: Dictionary of alerts from packages that were added
|
|
767
799
|
removed_package_alerts: Dictionary of alerts from packages that were removed
|
|
768
800
|
ignore_readded: If True, don't report alerts that were both removed and added
|
|
769
|
-
|
|
801
|
+
|
|
770
802
|
Returns:
|
|
771
803
|
List of newly found alerts
|
|
772
804
|
"""
|
|
@@ -778,7 +810,7 @@ class Core:
|
|
|
778
810
|
new_alerts = added_package_alerts[alert_key]
|
|
779
811
|
for alert in new_alerts:
|
|
780
812
|
alert_str = f"{alert.purl},{alert.manifests},{alert.type}"
|
|
781
|
-
|
|
813
|
+
|
|
782
814
|
if alert.error or alert.warn:
|
|
783
815
|
if alert_str not in consolidated_alerts:
|
|
784
816
|
alerts.append(alert)
|
|
@@ -786,10 +818,10 @@ class Core:
|
|
|
786
818
|
else:
|
|
787
819
|
new_alerts = added_package_alerts[alert_key]
|
|
788
820
|
removed_alerts = removed_package_alerts[alert_key]
|
|
789
|
-
|
|
821
|
+
|
|
790
822
|
for alert in new_alerts:
|
|
791
823
|
alert_str = f"{alert.purl},{alert.manifests},{alert.type}"
|
|
792
|
-
|
|
824
|
+
|
|
793
825
|
# Only add if:
|
|
794
826
|
# 1. Alert isn't in removed packages (or we're not ignoring readded alerts)
|
|
795
827
|
# 2. We haven't already recorded this alert
|
|
@@ -800,5 +832,3 @@ class Core:
|
|
|
800
832
|
consolidated_alerts.add(alert_str)
|
|
801
833
|
|
|
802
834
|
return alerts
|
|
803
|
-
|
|
804
|
-
|
|
@@ -115,6 +115,7 @@ class Package(SocketArtifactLink):
|
|
|
115
115
|
author: List[str] = field(default_factory=list)
|
|
116
116
|
size: Optional[int] = None
|
|
117
117
|
license: Optional[str] = None
|
|
118
|
+
namespace: Optional[str] = None
|
|
118
119
|
|
|
119
120
|
# Package-specific fields
|
|
120
121
|
license_text: str = ""
|
|
@@ -122,6 +123,10 @@ class Package(SocketArtifactLink):
|
|
|
122
123
|
transitives: int = 0
|
|
123
124
|
url: str = ""
|
|
124
125
|
|
|
126
|
+
# Artifact-specific fields
|
|
127
|
+
licenseDetails: Optional[list] = None
|
|
128
|
+
|
|
129
|
+
|
|
125
130
|
@classmethod
|
|
126
131
|
def from_socket_artifact(cls, data: dict) -> "Package":
|
|
127
132
|
"""
|
|
@@ -187,7 +192,8 @@ class Package(SocketArtifactLink):
|
|
|
187
192
|
direct=ref.get("direct", False),
|
|
188
193
|
manifestFiles=ref.get("manifestFiles", []),
|
|
189
194
|
dependencies=ref.get("dependencies"),
|
|
190
|
-
artifact=ref.get("artifact")
|
|
195
|
+
artifact=ref.get("artifact"),
|
|
196
|
+
namespace=data.get('namespace', None)
|
|
191
197
|
)
|
|
192
198
|
|
|
193
199
|
class Issue:
|
|
@@ -54,7 +54,9 @@ class GithubConfig:
|
|
|
54
54
|
owner = repository.split('/')[0]
|
|
55
55
|
repository = repository.split('/')[1]
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
default_branch_env = os.getenv('DEFAULT_BRANCH')
|
|
58
|
+
# Consider the variable truthy if it exists and isn't explicitly 'false'
|
|
59
|
+
is_default = default_branch_env is not None and default_branch_env.lower() != 'false'
|
|
58
60
|
return cls(
|
|
59
61
|
sha=os.getenv('GITHUB_SHA', ''),
|
|
60
62
|
api_url=os.getenv('GITHUB_API_URL', ''),
|
|
@@ -81,5 +81,28 @@ socket_globs = {
|
|
|
81
81
|
"pom.xml": {
|
|
82
82
|
"pattern": "pom.xml"
|
|
83
83
|
}
|
|
84
|
+
},
|
|
85
|
+
".net": {
|
|
86
|
+
"proj": {
|
|
87
|
+
"pattern": "*.*proj"
|
|
88
|
+
},
|
|
89
|
+
"props": {
|
|
90
|
+
"pattern": "*.props"
|
|
91
|
+
},
|
|
92
|
+
"targets": {
|
|
93
|
+
"pattern": "*.targets"
|
|
94
|
+
},
|
|
95
|
+
"nuspec": {
|
|
96
|
+
"pattern": "*.nuspec"
|
|
97
|
+
},
|
|
98
|
+
"nugetConfig": {
|
|
99
|
+
"pattern": "nuget.config"
|
|
100
|
+
},
|
|
101
|
+
"packagesConfig": {
|
|
102
|
+
"pattern": "packages.config"
|
|
103
|
+
},
|
|
104
|
+
"packagesLock": {
|
|
105
|
+
"pattern": "packages.lock.json"
|
|
106
|
+
}
|
|
84
107
|
}
|
|
85
108
|
}
|
|
@@ -48,6 +48,13 @@ def main_code():
|
|
|
48
48
|
log.debug(f"config: {config.to_dict()}")
|
|
49
49
|
output_handler = OutputHandler(config)
|
|
50
50
|
|
|
51
|
+
# Validate API token
|
|
52
|
+
if not config.api_token:
|
|
53
|
+
log.info("Socket API Token not found. Please set it using either:\n"
|
|
54
|
+
"1. Command line: --api-token YOUR_TOKEN\n"
|
|
55
|
+
"2. Environment variable: SOCKET_SECURITY_API_KEY")
|
|
56
|
+
sys.exit(3)
|
|
57
|
+
|
|
51
58
|
sdk = socketdev(token=config.api_token)
|
|
52
59
|
log.debug("sdk loaded")
|
|
53
60
|
|
|
@@ -55,10 +62,6 @@ def main_code():
|
|
|
55
62
|
set_debug_mode(True)
|
|
56
63
|
log.debug("Debug logging enabled")
|
|
57
64
|
|
|
58
|
-
# Validate API token
|
|
59
|
-
if not config.api_token:
|
|
60
|
-
log.info("Unable to find Socket API Token")
|
|
61
|
-
sys.exit(3)
|
|
62
65
|
|
|
63
66
|
# Initialize Socket core components
|
|
64
67
|
socket_config = SocketConfig(
|
|
@@ -160,6 +163,8 @@ def main_code():
|
|
|
160
163
|
set_as_pending_head=True
|
|
161
164
|
)
|
|
162
165
|
|
|
166
|
+
params.include_license_details = not config.exclude_license_details
|
|
167
|
+
|
|
163
168
|
# Initialize diff
|
|
164
169
|
diff = Diff()
|
|
165
170
|
diff.id = "NO_DIFF_RAN"
|
|
@@ -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
|
|
@@ -42,6 +43,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--integration {api,github,
|
|
|
42
43
|
[--target-path TARGET_PATH] [--sbom-file SBOM_FILE] [--files FILES] [--default-branch] [--pending-head]
|
|
43
44
|
[--generate-license] [--enable-debug] [--enable-json] [--enable-sarif] [--disable-overview] [--disable-security-issue]
|
|
44
45
|
[--allow-unverified] [--ignore-commit-files] [--disable-blocking] [--scm SCM] [--timeout TIMEOUT]
|
|
46
|
+
[--exclude-license-details]
|
|
45
47
|
````
|
|
46
48
|
|
|
47
49
|
If you don't want to provide the Socket API Token every time then you can use the environment variable `SOCKET_SECURITY_API_KEY`
|
|
@@ -90,6 +92,7 @@ If you don't want to provide the Socket API Token every time then you can use th
|
|
|
90
92
|
| --enable-json | False | False | Output in JSON format |
|
|
91
93
|
| --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format|
|
|
92
94
|
| --disable-overview | False | False | Disable overview output |
|
|
95
|
+
| --exclude-license-details | False | False | Exclude license details from the diff report (boosts performance for large repos) |
|
|
93
96
|
|
|
94
97
|
#### Security Configuration
|
|
95
98
|
| Parameter | Required | Default | Description |
|
|
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
|