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