cycode 3.25.2.dev1__py3-none-any.whl → 3.25.2.dev3__py3-none-any.whl
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.
- cycode/__init__.py +1 -1
- cycode/cli/app.py +9 -10
- cycode/cli/apps/activation_manager.py +6 -6
- cycode/cli/apps/ai_guardrails/command_utils.py +1 -2
- cycode/cli/apps/ai_guardrails/hooks_manager.py +6 -7
- cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +6 -5
- cycode/cli/apps/ai_guardrails/ides/_skill_utils.py +1 -2
- cycode/cli/apps/ai_guardrails/ides/base.py +9 -11
- cycode/cli/apps/ai_guardrails/ides/claude_code.py +14 -14
- cycode/cli/apps/ai_guardrails/ides/codex.py +11 -11
- cycode/cli/apps/ai_guardrails/ides/copilot.py +7 -7
- cycode/cli/apps/ai_guardrails/ides/cursor.py +5 -5
- cycode/cli/apps/ai_guardrails/install_command.py +3 -3
- cycode/cli/apps/ai_guardrails/scan/guardrail_config.py +10 -11
- cycode/cli/apps/ai_guardrails/scan/handlers.py +15 -14
- cycode/cli/apps/ai_guardrails/scan/mcp_server_status.py +6 -6
- cycode/cli/apps/ai_guardrails/scan/payload.py +13 -14
- cycode/cli/apps/ai_guardrails/scan/policy.py +3 -3
- cycode/cli/apps/ai_guardrails/scan/scan_command.py +4 -4
- cycode/cli/apps/ai_guardrails/session_start_command.py +8 -8
- cycode/cli/apps/ai_guardrails/status_command.py +2 -2
- cycode/cli/apps/ai_guardrails/uninstall_command.py +2 -2
- cycode/cli/apps/api/__init__.py +3 -3
- cycode/cli/apps/api/api_command.py +6 -6
- cycode/cli/apps/api/openapi_spec.py +4 -5
- cycode/cli/apps/auth/auth_common.py +4 -4
- cycode/cli/apps/configure/configure_command.py +2 -4
- cycode/cli/apps/configure/messages.py +2 -4
- cycode/cli/apps/configure/prompts.py +5 -7
- cycode/cli/apps/ignore/ignore_command.py +7 -7
- cycode/cli/apps/mcp/mcp_command.py +11 -11
- cycode/cli/apps/report/sbom/common.py +5 -5
- cycode/cli/apps/report/sbom/sbom_command.py +2 -2
- cycode/cli/apps/report/sbom/sbom_report_file.py +1 -2
- cycode/cli/apps/report_import/sbom/sbom_command.py +3 -3
- cycode/cli/apps/sca_options.py +3 -3
- cycode/cli/apps/scan/aggregation_report.py +4 -4
- cycode/cli/apps/scan/code_scanner.py +8 -7
- cycode/cli/apps/scan/commit_range_scanner.py +10 -10
- cycode/cli/apps/scan/detection_excluder.py +2 -4
- cycode/cli/apps/scan/pre_commit/pre_commit_command.py +3 -3
- cycode/cli/apps/scan/pre_push/pre_push_command.py +3 -3
- cycode/cli/apps/scan/pre_receive/pre_receive_command.py +2 -2
- cycode/cli/apps/scan/remote_url_resolver.py +6 -8
- cycode/cli/apps/scan/repository/repository_command.py +2 -2
- cycode/cli/apps/scan/scan_command.py +2 -2
- cycode/cli/apps/scan/scan_parameters.py +1 -3
- cycode/cli/apps/scan/scan_result.py +5 -5
- cycode/cli/console.py +2 -2
- cycode/cli/consts.py +2 -0
- cycode/cli/exceptions/custom_exceptions.py +1 -1
- cycode/cli/exceptions/handle_errors.py +1 -3
- cycode/cli/exceptions/handle_scan_errors.py +1 -2
- cycode/cli/files_collector/commit_range_documents.py +14 -14
- cycode/cli/files_collector/models/in_memory_zip.py +3 -3
- cycode/cli/files_collector/repository_documents.py +3 -3
- cycode/cli/files_collector/sca/base_restore_dependencies.py +7 -8
- cycode/cli/files_collector/sca/go/restore_go_dependencies.py +1 -2
- cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +2 -3
- cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +3 -4
- cycode/cli/files_collector/sca/npm/restore_bun_dependencies.py +3 -4
- cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +1 -2
- cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +2 -3
- cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +2 -3
- cycode/cli/files_collector/sca/npm/workspace/coverage.py +5 -5
- cycode/cli/files_collector/sca/npm/workspace/files.py +2 -3
- cycode/cli/files_collector/sca/npm/workspace/globs.py +1 -2
- cycode/cli/files_collector/sca/npm/workspace/resolvers.py +14 -16
- cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +1 -2
- cycode/cli/files_collector/sca/python/restore_pip_dependencies.py +2 -3
- cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +1 -2
- cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +2 -3
- cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +2 -3
- cycode/cli/files_collector/sca/sca_file_collector.py +5 -5
- cycode/cli/files_collector/zip_documents.py +1 -2
- cycode/cli/models.py +8 -8
- cycode/cli/printers/console_printer.py +9 -9
- cycode/cli/printers/json_printer.py +2 -2
- cycode/cli/printers/printer_base.py +3 -3
- cycode/cli/printers/rich_printer.py +2 -2
- cycode/cli/printers/tables/table.py +4 -4
- cycode/cli/printers/tables/table_models.py +2 -2
- cycode/cli/printers/tables/table_printer_base.py +2 -2
- cycode/cli/printers/text_printer.py +4 -4
- cycode/cli/printers/utils/code_snippet_syntax.py +1 -1
- cycode/cli/printers/utils/detection_data.py +3 -3
- cycode/cli/printers/utils/sca_ossf.py +4 -4
- cycode/cli/printers/utils/sca_policy_details.py +2 -1
- cycode/cli/user_settings/config_file_manager.py +10 -10
- cycode/cli/user_settings/configuration_manager.py +6 -6
- cycode/cli/user_settings/credentials_manager.py +5 -6
- cycode/cli/utils/get_api_client.py +5 -5
- cycode/cli/utils/git_proxy.py +8 -8
- cycode/cli/utils/host_info.py +21 -38
- cycode/cli/utils/ignore_utils.py +13 -15
- cycode/cli/utils/jwt_utils.py +2 -4
- cycode/cli/utils/path_utils.py +6 -6
- cycode/cli/utils/progress_bar.py +4 -4
- cycode/cli/utils/scan_batch.py +2 -1
- cycode/cli/utils/scan_utils.py +2 -2
- cycode/cli/utils/shell_executor.py +3 -4
- cycode/cli/utils/task_timer.py +4 -4
- cycode/cli/utils/trust_store.py +0 -17
- cycode/cli/utils/url_utils.py +1 -2
- cycode/cli/utils/version_checker.py +4 -5
- cycode/cli/utils/yaml_utils.py +1 -1
- cycode/config.py +1 -2
- cycode/cyclient/ai_security_manager_client.py +17 -17
- cycode/cyclient/auth_client.py +2 -4
- cycode/cyclient/base_token_auth_client.py +2 -2
- cycode/cyclient/client_creator.py +4 -6
- cycode/cyclient/cycode_client_base.py +10 -9
- cycode/cyclient/cycode_dev_based_client.py +1 -3
- cycode/cyclient/headers.py +1 -2
- cycode/cyclient/import_sbom_client.py +3 -4
- cycode/cyclient/models.py +33 -33
- cycode/cyclient/report_client.py +1 -2
- cycode/cyclient/scan_client.py +8 -7
- cycode/logger.py +4 -4
- {cycode-3.25.2.dev1.dist-info → cycode-3.25.2.dev3.dist-info}/METADATA +12 -19
- {cycode-3.25.2.dev1.dist-info → cycode-3.25.2.dev3.dist-info}/RECORD +124 -124
- {cycode-3.25.2.dev1.dist-info → cycode-3.25.2.dev3.dist-info}/WHEEL +1 -1
- {cycode-3.25.2.dev1.dist-info → cycode-3.25.2.dev3.dist-info}/entry_points.txt +0 -0
- {cycode-3.25.2.dev1.dist-info → cycode-3.25.2.dev3.dist-info}/licenses/LICENCE +0 -0
cycode/cli/utils/task_timer.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from _thread import interrupt_main
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from threading import Event, Thread
|
|
3
4
|
from types import TracebackType
|
|
4
|
-
from typing import Callable, Optional
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class FunctionContext:
|
|
8
|
-
def __init__(self, function: Callable, args:
|
|
8
|
+
def __init__(self, function: Callable, args: list | None = None, kwargs: dict | None = None) -> None:
|
|
9
9
|
self.function = function
|
|
10
10
|
self.args = args or []
|
|
11
11
|
self.kwargs = kwargs or {}
|
|
@@ -54,7 +54,7 @@ class TimeoutAfter:
|
|
|
54
54
|
the default option is to interrupt main thread
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
-
def __init__(self, timeout: int, quit_function:
|
|
57
|
+
def __init__(self, timeout: int, quit_function: FunctionContext | None = None) -> None:
|
|
58
58
|
self.timeout = timeout
|
|
59
59
|
self._quit_function = quit_function or FunctionContext(function=self.timeout_function)
|
|
60
60
|
self.timer = TimerThread(timeout, quit_function=self._quit_function)
|
|
@@ -64,7 +64,7 @@ class TimeoutAfter:
|
|
|
64
64
|
self.timer.start()
|
|
65
65
|
|
|
66
66
|
def __exit__(
|
|
67
|
-
self, exc_type:
|
|
67
|
+
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
|
68
68
|
) -> None:
|
|
69
69
|
if self.timeout:
|
|
70
70
|
self.timer.stop()
|
cycode/cli/utils/trust_store.py
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
1
|
from cycode import config
|
|
4
2
|
from cycode.cli import consts
|
|
5
3
|
from cycode.logger import get_logger
|
|
6
4
|
|
|
7
5
|
logger = get_logger('Trust Store')
|
|
8
6
|
|
|
9
|
-
# truststore requires Python 3.10+, so on 3.9 the OS trust store is unavailable
|
|
10
|
-
_MIN_PYTHON_VERSION = (3, 10)
|
|
11
|
-
|
|
12
7
|
_installed = False
|
|
13
8
|
|
|
14
9
|
|
|
15
|
-
def is_supported() -> bool:
|
|
16
|
-
return sys.version_info >= _MIN_PYTHON_VERSION
|
|
17
|
-
|
|
18
|
-
|
|
19
10
|
def is_enabled() -> bool:
|
|
20
11
|
return config.get_val_as_bool(consts.ENABLE_TRUSTSTORE_ENV_VAR_NAME)
|
|
21
12
|
|
|
@@ -45,14 +36,6 @@ def install() -> bool:
|
|
|
45
36
|
)
|
|
46
37
|
return False
|
|
47
38
|
|
|
48
|
-
if not is_supported():
|
|
49
|
-
logger.warning(
|
|
50
|
-
'OS trust store requires Python %s+, using the bundled CA store (certifi). Current version: %s',
|
|
51
|
-
'.'.join(map(str, _MIN_PYTHON_VERSION)),
|
|
52
|
-
'.'.join(map(str, sys.version_info[:3])),
|
|
53
|
-
)
|
|
54
|
-
return False
|
|
55
|
-
|
|
56
39
|
try:
|
|
57
40
|
import truststore
|
|
58
41
|
|
cycode/cli/utils/url_utils.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
1
|
from urllib.parse import urlparse, urlunparse
|
|
3
2
|
|
|
4
3
|
from cycode.logger import get_logger
|
|
@@ -6,7 +5,7 @@ from cycode.logger import get_logger
|
|
|
6
5
|
logger = get_logger('URL Utils')
|
|
7
6
|
|
|
8
7
|
|
|
9
|
-
def sanitize_repository_url(url:
|
|
8
|
+
def sanitize_repository_url(url: str | None) -> str | None:
|
|
10
9
|
"""Remove credentials (username, password, tokens) from repository URL.
|
|
11
10
|
|
|
12
11
|
This function sanitizes repository URLs to prevent sending PAT tokens or other
|
|
@@ -2,7 +2,6 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import time
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
5
|
|
|
7
6
|
from cycode.cli.console import console
|
|
8
7
|
from cycode.cli.user_settings.configuration_manager import ConfigurationManager
|
|
@@ -19,7 +18,7 @@ def _compare_versions(
|
|
|
19
18
|
current_is_pre: bool,
|
|
20
19
|
latest_is_pre: bool,
|
|
21
20
|
latest_version: str,
|
|
22
|
-
) ->
|
|
21
|
+
) -> str | None:
|
|
23
22
|
"""Compare version numbers and determine if an update is needed.
|
|
24
23
|
|
|
25
24
|
Implements version comparison logic with special handling for pre-release versions:
|
|
@@ -43,7 +42,7 @@ def _compare_versions(
|
|
|
43
42
|
return None
|
|
44
43
|
|
|
45
44
|
# Compare version numbers
|
|
46
|
-
for current, latest in zip(current_parts, latest_parts):
|
|
45
|
+
for current, latest in zip(current_parts, latest_parts, strict=False):
|
|
47
46
|
if latest > current:
|
|
48
47
|
return latest_version
|
|
49
48
|
if current > latest:
|
|
@@ -78,7 +77,7 @@ class VersionChecker(CycodeClientBase):
|
|
|
78
77
|
config_dir = configuration_manager.global_config_file_manager.get_config_directory_path()
|
|
79
78
|
self.cache_file = Path(config_dir) / '.version_check'
|
|
80
79
|
|
|
81
|
-
def get_latest_version(self) ->
|
|
80
|
+
def get_latest_version(self) -> str | None:
|
|
82
81
|
"""Fetch the latest version of the package from PyPI.
|
|
83
82
|
|
|
84
83
|
Makes an HTTP request to PyPI's JSON API to get the latest version information.
|
|
@@ -160,7 +159,7 @@ class VersionChecker(CycodeClientBase):
|
|
|
160
159
|
except Exception as e:
|
|
161
160
|
logger.debug('Failed to update version check cache file: %s', {'file': self.cache_file}, exc_info=e)
|
|
162
161
|
|
|
163
|
-
def check_for_update(self, current_version: str, use_cache: bool = True) ->
|
|
162
|
+
def check_for_update(self, current_version: str, use_cache: bool = True) -> str | None:
|
|
164
163
|
"""Check if an update is available for the current version.
|
|
165
164
|
|
|
166
165
|
Respects the update check frequency (daily/weekly) based on the version type
|
cycode/cli/utils/yaml_utils.py
CHANGED
|
@@ -15,7 +15,7 @@ def _deep_update(source: dict[Hashable, Any], overrides: dict[Hashable, Any]) ->
|
|
|
15
15
|
if isinstance(value, dict) and value:
|
|
16
16
|
source[key] = _deep_update(source.get(key, {}), value)
|
|
17
17
|
else:
|
|
18
|
-
source[key] =
|
|
18
|
+
source[key] = value
|
|
19
19
|
|
|
20
20
|
return source
|
|
21
21
|
|
cycode/config.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
from typing import Optional
|
|
4
3
|
from urllib.parse import urlparse
|
|
5
4
|
|
|
6
5
|
from cycode.cli import consts
|
|
@@ -26,7 +25,7 @@ def get_val_as_bool(key: str, default: bool = False) -> bool:
|
|
|
26
25
|
return configuration[key].lower() in {'true', '1', 'yes', 'y', 'on', 'enabled'}
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
def get_val_as_int(key: str) ->
|
|
28
|
+
def get_val_as_int(key: str) -> int | None:
|
|
30
29
|
val = configuration.get(key)
|
|
31
30
|
if not val:
|
|
32
31
|
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Client for AI Security Manager service."""
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError
|
|
6
6
|
from cycode.cyclient.cycode_client_base import CycodeClientBase
|
|
@@ -32,7 +32,7 @@ class AISecurityManagerClient:
|
|
|
32
32
|
return f'{service_name}/{path}'
|
|
33
33
|
return path
|
|
34
34
|
|
|
35
|
-
def create_conversation(self, payload: 'AIHookPayload') ->
|
|
35
|
+
def create_conversation(self, payload: 'AIHookPayload') -> str | None:
|
|
36
36
|
"""Creates an AI conversation from hook payload."""
|
|
37
37
|
conversation_id = payload.conversation_id
|
|
38
38
|
if not conversation_id:
|
|
@@ -63,10 +63,10 @@ class AISecurityManagerClient:
|
|
|
63
63
|
payload: 'AIHookPayload',
|
|
64
64
|
event_type: 'AiHookEventType',
|
|
65
65
|
outcome: 'AIHookOutcome',
|
|
66
|
-
scan_id:
|
|
67
|
-
block_reason:
|
|
68
|
-
error_message:
|
|
69
|
-
file_path:
|
|
66
|
+
scan_id: str | None = None,
|
|
67
|
+
block_reason: 'BlockReason | None' = None,
|
|
68
|
+
error_message: str | None = None,
|
|
69
|
+
file_path: str | None = None,
|
|
70
70
|
) -> None:
|
|
71
71
|
"""Create an AI hook event from hook payload."""
|
|
72
72
|
conversation_id = payload.conversation_id
|
|
@@ -95,7 +95,7 @@ class AISecurityManagerClient:
|
|
|
95
95
|
logger.debug('Failed to create AI hook event', exc_info=e)
|
|
96
96
|
# Don't fail the hook if tracking fails
|
|
97
97
|
|
|
98
|
-
def get_resolved_guardrails(self) ->
|
|
98
|
+
def get_resolved_guardrails(self) -> dict | None:
|
|
99
99
|
"""Fetch the tenant's resolved guardrail config (per-agent modes + sensitive-path globs)."""
|
|
100
100
|
try:
|
|
101
101
|
response = self.client.get(self._build_endpoint_path(self._RESOLVED_GUARDRAILS_PATH))
|
|
@@ -106,16 +106,16 @@ class AISecurityManagerClient:
|
|
|
106
106
|
|
|
107
107
|
def report_session_context(
|
|
108
108
|
self,
|
|
109
|
-
hostname:
|
|
110
|
-
platform_name:
|
|
111
|
-
os_version:
|
|
112
|
-
serial_number:
|
|
113
|
-
last_login_user:
|
|
114
|
-
config_files:
|
|
115
|
-
enabled_plugins:
|
|
116
|
-
skill_files:
|
|
117
|
-
user_email:
|
|
118
|
-
) ->
|
|
109
|
+
hostname: str | None = None,
|
|
110
|
+
platform_name: str | None = None,
|
|
111
|
+
os_version: str | None = None,
|
|
112
|
+
serial_number: str | None = None,
|
|
113
|
+
last_login_user: str | None = None,
|
|
114
|
+
config_files: list[dict] | None = None,
|
|
115
|
+
enabled_plugins: dict | None = None,
|
|
116
|
+
skill_files: list[dict] | None = None,
|
|
117
|
+
user_email: str | None = None,
|
|
118
|
+
) -> SessionContextResponse | None:
|
|
119
119
|
"""Report session context to the backend. Returns None when the report was not accepted."""
|
|
120
120
|
body: dict = {
|
|
121
121
|
'hostname': hostname,
|
cycode/cyclient/auth_client.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from requests import Request, Response
|
|
4
2
|
|
|
5
3
|
from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError, RequestHttpError
|
|
@@ -24,7 +22,7 @@ class AuthClient:
|
|
|
24
22
|
response = self.cycode_client.post(url_path=path, body=body)
|
|
25
23
|
return self.parse_start_session_response(response)
|
|
26
24
|
|
|
27
|
-
def get_api_token(self, session_id: str, code_verifier: str) ->
|
|
25
|
+
def get_api_token(self, session_id: str, code_verifier: str) -> models.ApiTokenGenerationPollingResponse | None:
|
|
28
26
|
path = f'{self.AUTH_CONTROLLER_PATH}/token'
|
|
29
27
|
body = {'session_id': session_id, 'code_verifier': code_verifier}
|
|
30
28
|
try:
|
|
@@ -40,7 +38,7 @@ class AuthClient:
|
|
|
40
38
|
return models.AuthenticationSessionSchema().load(response.json())
|
|
41
39
|
|
|
42
40
|
@staticmethod
|
|
43
|
-
def parse_api_token_polling_response(response: Response) ->
|
|
41
|
+
def parse_api_token_polling_response(response: Response) -> models.ApiTokenGenerationPollingResponse | None:
|
|
44
42
|
try:
|
|
45
43
|
return models.ApiTokenGenerationPollingResponseSchema().load(response.json())
|
|
46
44
|
except Exception:
|
|
@@ -2,7 +2,7 @@ import secrets
|
|
|
2
2
|
import time
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from threading import Lock
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
import arrow
|
|
8
8
|
from requests import Response
|
|
@@ -97,7 +97,7 @@ class BaseTokenAuthClient(CycodeClient, ABC):
|
|
|
97
97
|
jwt_creator = self._create_jwt_creator()
|
|
98
98
|
self._credentials_manager.update_access_token(self._access_token, self._expires_in.timestamp(), jwt_creator)
|
|
99
99
|
|
|
100
|
-
def get_request_headers(self, additional_headers:
|
|
100
|
+
def get_request_headers(self, additional_headers: dict | None = None, without_auth: bool = False) -> dict:
|
|
101
101
|
headers = super().get_request_headers(additional_headers=additional_headers)
|
|
102
102
|
|
|
103
103
|
if not without_auth:
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from cycode.cyclient.ai_security_manager_client import AISecurityManagerClient
|
|
4
2
|
from cycode.cyclient.ai_security_manager_service_config import (
|
|
5
3
|
DefaultAISecurityManagerServiceConfig,
|
|
@@ -17,7 +15,7 @@ from cycode.cyclient.scan_config_base import DefaultScanConfig, DevScanConfig
|
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
def create_scan_client(
|
|
20
|
-
client_id: str, client_secret:
|
|
18
|
+
client_id: str, client_secret: str | None = None, hide_response_log: bool = False, id_token: str | None = None
|
|
21
19
|
) -> ScanClient:
|
|
22
20
|
if dev_mode:
|
|
23
21
|
client = CycodeDevBasedClient(DEV_CYCODE_API_URL)
|
|
@@ -33,7 +31,7 @@ def create_scan_client(
|
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
def create_report_client(
|
|
36
|
-
client_id: str, client_secret:
|
|
34
|
+
client_id: str, client_secret: str | None = None, _: bool = False, id_token: str | None = None
|
|
37
35
|
) -> ReportClient:
|
|
38
36
|
if dev_mode:
|
|
39
37
|
client = CycodeDevBasedClient(DEV_CYCODE_API_URL)
|
|
@@ -45,7 +43,7 @@ def create_report_client(
|
|
|
45
43
|
|
|
46
44
|
|
|
47
45
|
def create_import_sbom_client(
|
|
48
|
-
client_id: str, client_secret:
|
|
46
|
+
client_id: str, client_secret: str | None = None, _: bool = False, id_token: str | None = None
|
|
49
47
|
) -> ImportSbomClient:
|
|
50
48
|
if dev_mode:
|
|
51
49
|
client = CycodeDevBasedClient(DEV_CYCODE_API_URL)
|
|
@@ -57,7 +55,7 @@ def create_import_sbom_client(
|
|
|
57
55
|
|
|
58
56
|
|
|
59
57
|
def create_ai_security_manager_client(
|
|
60
|
-
client_id: str, client_secret:
|
|
58
|
+
client_id: str, client_secret: str | None = None, _: bool = False, id_token: str | None = None
|
|
61
59
|
) -> AISecurityManagerClient:
|
|
62
60
|
if dev_mode:
|
|
63
61
|
client = CycodeDevBasedClient(DEV_CYCODE_API_URL)
|
|
@@ -2,8 +2,9 @@ import functools
|
|
|
2
2
|
import os
|
|
3
3
|
import platform
|
|
4
4
|
import ssl
|
|
5
|
+
from collections.abc import Callable
|
|
5
6
|
from io import BytesIO
|
|
6
|
-
from typing import TYPE_CHECKING,
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
7
8
|
|
|
8
9
|
import requests
|
|
9
10
|
from requests import Response, exceptions
|
|
@@ -114,7 +115,7 @@ def _should_retry_exception(exception: BaseException) -> bool:
|
|
|
114
115
|
class UploadProgressTracker:
|
|
115
116
|
"""File-like wrapper that tracks bytes read during upload and fires a progress callback."""
|
|
116
117
|
|
|
117
|
-
def __init__(self, data: bytes, callback:
|
|
118
|
+
def __init__(self, data: bytes, callback: Callable[[int, int], None] | None) -> None:
|
|
118
119
|
self._io = BytesIO(data)
|
|
119
120
|
self._callback = callback
|
|
120
121
|
self.bytes_read = 0
|
|
@@ -146,13 +147,13 @@ class CycodeClientBase:
|
|
|
146
147
|
def enrich_user_agent(user_agent_suffix: str) -> None:
|
|
147
148
|
CycodeClientBase.MANDATORY_HEADERS['User-Agent'] += f' {user_agent_suffix}'
|
|
148
149
|
|
|
149
|
-
def post(self, url_path: str, body:
|
|
150
|
+
def post(self, url_path: str, body: dict | None = None, headers: dict | None = None, **kwargs) -> Response:
|
|
150
151
|
return self._execute(method='post', endpoint=url_path, json=body, headers=headers, **kwargs)
|
|
151
152
|
|
|
152
|
-
def put(self, url_path: str, body:
|
|
153
|
+
def put(self, url_path: str, body: dict | None = None, headers: dict | None = None, **kwargs) -> Response:
|
|
153
154
|
return self._execute(method='put', endpoint=url_path, json=body, headers=headers, **kwargs)
|
|
154
155
|
|
|
155
|
-
def get(self, url_path: str, headers:
|
|
156
|
+
def get(self, url_path: str, headers: dict | None = None, **kwargs) -> Response:
|
|
156
157
|
return self._execute(method='get', endpoint=url_path, headers=headers, **kwargs)
|
|
157
158
|
|
|
158
159
|
def post_multipart(
|
|
@@ -160,7 +161,7 @@ class CycodeClientBase:
|
|
|
160
161
|
url_path: str,
|
|
161
162
|
form_fields: dict,
|
|
162
163
|
files: dict,
|
|
163
|
-
on_upload_progress:
|
|
164
|
+
on_upload_progress: Callable[[int, int], None] | None = None,
|
|
164
165
|
hide_response_content_log: bool = False,
|
|
165
166
|
) -> Response:
|
|
166
167
|
"""POST a multipart form body with optional upload progress tracking and retry."""
|
|
@@ -191,7 +192,7 @@ class CycodeClientBase:
|
|
|
191
192
|
url: str,
|
|
192
193
|
body: bytes,
|
|
193
194
|
content_type: str,
|
|
194
|
-
on_upload_progress:
|
|
195
|
+
on_upload_progress: Callable[[int, int], None] | None,
|
|
195
196
|
hide_response_content_log: bool,
|
|
196
197
|
) -> Response:
|
|
197
198
|
# Wrap the body in a fresh tracker each attempt so bytes_read starts from zero.
|
|
@@ -227,7 +228,7 @@ class CycodeClientBase:
|
|
|
227
228
|
self,
|
|
228
229
|
method: str,
|
|
229
230
|
endpoint: str,
|
|
230
|
-
headers:
|
|
231
|
+
headers: dict | None = None,
|
|
231
232
|
without_auth: bool = False,
|
|
232
233
|
hide_response_content_log: bool = False,
|
|
233
234
|
**kwargs,
|
|
@@ -253,7 +254,7 @@ class CycodeClientBase:
|
|
|
253
254
|
except Exception as e:
|
|
254
255
|
self._handle_exception(e)
|
|
255
256
|
|
|
256
|
-
def get_request_headers(self, additional_headers:
|
|
257
|
+
def get_request_headers(self, additional_headers: dict | None = None, **kwargs) -> dict[str, str]:
|
|
257
258
|
if additional_headers is None:
|
|
258
259
|
return self.MANDATORY_HEADERS.copy()
|
|
259
260
|
return {**self.MANDATORY_HEADERS, **additional_headers}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from cycode.cyclient.config import dev_tenant_id
|
|
4
2
|
from cycode.cyclient.cycode_client_base import CycodeClientBase
|
|
5
3
|
|
|
@@ -12,7 +10,7 @@ class CycodeDevBasedClient(CycodeClientBase):
|
|
|
12
10
|
def __init__(self, api_url: str) -> None:
|
|
13
11
|
super().__init__(api_url)
|
|
14
12
|
|
|
15
|
-
def get_request_headers(self, additional_headers:
|
|
13
|
+
def get_request_headers(self, additional_headers: dict | None = None, **_) -> dict[str, str]:
|
|
16
14
|
headers = super().get_request_headers(additional_headers=additional_headers)
|
|
17
15
|
headers['X-Tenant-Id'] = dev_tenant_id
|
|
18
16
|
|
cycode/cyclient/headers.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import platform
|
|
2
|
-
from typing import Optional
|
|
3
2
|
from uuid import uuid4
|
|
4
3
|
|
|
5
4
|
from cycode import __version__
|
|
@@ -25,7 +24,7 @@ def get_cli_user_agent() -> str:
|
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
class _CorrelationId:
|
|
28
|
-
_id:
|
|
27
|
+
_id: str | None = None
|
|
29
28
|
|
|
30
29
|
def get_correlation_id(self) -> str:
|
|
31
30
|
"""Get correlation ID.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
4
|
from requests import Response
|
|
6
5
|
|
|
@@ -15,8 +14,8 @@ class ImportSbomParameters:
|
|
|
15
14
|
Name: str
|
|
16
15
|
Vendor: str
|
|
17
16
|
BusinessImpact: BusinessImpactOption
|
|
18
|
-
Labels:
|
|
19
|
-
Owners:
|
|
17
|
+
Labels: list[str] | None
|
|
18
|
+
Owners: list[str] | None
|
|
20
19
|
|
|
21
20
|
def _owners_to_ids(self) -> list[str]:
|
|
22
21
|
return []
|
|
@@ -71,7 +70,7 @@ class ImportSbomClient:
|
|
|
71
70
|
member_details = self.parse_requested_member_details_response(response)
|
|
72
71
|
|
|
73
72
|
if not member_details.items:
|
|
74
|
-
raise Exception(
|
|
73
|
+
raise Exception( # noqa: TRY002
|
|
75
74
|
f"Failed to find user with email '{email}'. Verify this email is registered to Cycode platform"
|
|
76
75
|
)
|
|
77
76
|
return member_details.items.pop(0).external_id
|
cycode/cyclient/models.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from marshmallow import EXCLUDE, Schema, fields, missing, post_load
|
|
6
6
|
|
|
@@ -13,8 +13,8 @@ class Detection(Schema):
|
|
|
13
13
|
message: str,
|
|
14
14
|
detection_details: dict,
|
|
15
15
|
detection_rule_id: str,
|
|
16
|
-
severity:
|
|
17
|
-
id:
|
|
16
|
+
severity: str | None = None,
|
|
17
|
+
id: str | None = None,
|
|
18
18
|
) -> None:
|
|
19
19
|
super().__init__()
|
|
20
20
|
self.id = id
|
|
@@ -62,7 +62,7 @@ class DetectionSchema(Schema):
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class DetectionsPerFile(Schema):
|
|
65
|
-
def __init__(self, file_name: str, detections: list[Detection], commit_id:
|
|
65
|
+
def __init__(self, file_name: str, detections: list[Detection], commit_id: str | None = None) -> None:
|
|
66
66
|
super().__init__()
|
|
67
67
|
self.file_name = file_name
|
|
68
68
|
self.detections = detections
|
|
@@ -74,10 +74,10 @@ class ZippedFileScanResult(Schema):
|
|
|
74
74
|
self,
|
|
75
75
|
did_detect: bool,
|
|
76
76
|
detections_per_file: list[DetectionsPerFile],
|
|
77
|
-
report_url:
|
|
78
|
-
scan_id:
|
|
79
|
-
err:
|
|
80
|
-
verdict:
|
|
77
|
+
report_url: str | None = None,
|
|
78
|
+
scan_id: str | None = None,
|
|
79
|
+
err: str | None = None,
|
|
80
|
+
verdict: str | None = None,
|
|
81
81
|
) -> None:
|
|
82
82
|
super().__init__()
|
|
83
83
|
self.did_detect = did_detect
|
|
@@ -92,9 +92,9 @@ class ScanResult(Schema):
|
|
|
92
92
|
def __init__(
|
|
93
93
|
self,
|
|
94
94
|
did_detect: bool,
|
|
95
|
-
scan_id:
|
|
96
|
-
detections:
|
|
97
|
-
err:
|
|
95
|
+
scan_id: str | None = None,
|
|
96
|
+
detections: list[Detection] | None = None,
|
|
97
|
+
err: str | None = None,
|
|
98
98
|
) -> None:
|
|
99
99
|
super().__init__()
|
|
100
100
|
self.did_detect = did_detect
|
|
@@ -138,7 +138,7 @@ class UploadLinkResponseSchema(Schema):
|
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
class ScanInitializationResponse(Schema):
|
|
141
|
-
def __init__(self, scan_id:
|
|
141
|
+
def __init__(self, scan_id: str | None = None, err: str | None = None) -> None:
|
|
142
142
|
super().__init__()
|
|
143
143
|
self.scan_id = scan_id
|
|
144
144
|
self.err = err
|
|
@@ -159,13 +159,13 @@ class ScanInitializationResponseSchema(Schema):
|
|
|
159
159
|
class ScanDetailsResponse(Schema):
|
|
160
160
|
def __init__(
|
|
161
161
|
self,
|
|
162
|
-
id:
|
|
163
|
-
scan_status:
|
|
164
|
-
results_count:
|
|
165
|
-
metadata:
|
|
166
|
-
message:
|
|
167
|
-
scan_update_at:
|
|
168
|
-
err:
|
|
162
|
+
id: str | None = None,
|
|
163
|
+
scan_status: str | None = None,
|
|
164
|
+
results_count: int | None = None,
|
|
165
|
+
metadata: str | None = None,
|
|
166
|
+
message: str | None = None,
|
|
167
|
+
scan_update_at: str | None = None,
|
|
168
|
+
err: str | None = None,
|
|
169
169
|
) -> None:
|
|
170
170
|
super().__init__()
|
|
171
171
|
self.id = id
|
|
@@ -389,9 +389,9 @@ class SbomReportStorageDetailsSchema(Schema):
|
|
|
389
389
|
class ReportExecution:
|
|
390
390
|
id: int
|
|
391
391
|
status: str
|
|
392
|
-
error_message:
|
|
393
|
-
status_message:
|
|
394
|
-
storage_details:
|
|
392
|
+
error_message: str | None = None
|
|
393
|
+
status_message: str | None = None
|
|
394
|
+
storage_details: SbomReportStorageDetails | None = None
|
|
395
395
|
|
|
396
396
|
|
|
397
397
|
class ReportExecutionSchema(Schema):
|
|
@@ -445,7 +445,7 @@ class MemberSchema(Schema):
|
|
|
445
445
|
class MemberDetails:
|
|
446
446
|
items: list[Member]
|
|
447
447
|
page_size: int
|
|
448
|
-
next_page_token:
|
|
448
|
+
next_page_token: str | None
|
|
449
449
|
|
|
450
450
|
|
|
451
451
|
class RequestedMemberDetailsResultSchema(Schema):
|
|
@@ -481,11 +481,11 @@ class ClassificationDataSchema(Schema):
|
|
|
481
481
|
class DetectionRule:
|
|
482
482
|
classification_data: list[ClassificationData]
|
|
483
483
|
detection_rule_id: str
|
|
484
|
-
custom_remediation_guidelines:
|
|
485
|
-
remediation_guidelines:
|
|
486
|
-
description:
|
|
487
|
-
policy_name:
|
|
488
|
-
display_name:
|
|
484
|
+
custom_remediation_guidelines: str | None = None
|
|
485
|
+
remediation_guidelines: str | None = None
|
|
486
|
+
description: str | None = None
|
|
487
|
+
policy_name: str | None = None
|
|
488
|
+
display_name: str | None = None
|
|
489
489
|
|
|
490
490
|
|
|
491
491
|
class DetectionRuleSchema(Schema):
|
|
@@ -509,7 +509,7 @@ class DetectionRuleSchema(Schema):
|
|
|
509
509
|
class ScanResultsSyncFlow:
|
|
510
510
|
id: str
|
|
511
511
|
detection_messages: list[dict]
|
|
512
|
-
verdict:
|
|
512
|
+
verdict: str | None = None
|
|
513
513
|
|
|
514
514
|
|
|
515
515
|
class ScanResultsSyncFlowSchema(Schema):
|
|
@@ -594,7 +594,7 @@ class McpServerAuthorizationStatus(str, Enum):
|
|
|
594
594
|
|
|
595
595
|
|
|
596
596
|
class McpServerAuthorizationStatusField(fields.Field):
|
|
597
|
-
def _serialize(self, value:
|
|
597
|
+
def _serialize(self, value: McpServerAuthorizationStatus | None, *_, **__) -> str | None:
|
|
598
598
|
return value.value if value is not None else None
|
|
599
599
|
|
|
600
600
|
def deserialize(self, value: object, *_, **__) -> McpServerAuthorizationStatus:
|
|
@@ -603,8 +603,8 @@ class McpServerAuthorizationStatusField(fields.Field):
|
|
|
603
603
|
|
|
604
604
|
@dataclass
|
|
605
605
|
class McpServerStatus:
|
|
606
|
-
alias:
|
|
607
|
-
normalized_id:
|
|
606
|
+
alias: str | None = None
|
|
607
|
+
normalized_id: str | None = None
|
|
608
608
|
status: McpServerAuthorizationStatus = McpServerAuthorizationStatus.UNREVIEWED
|
|
609
609
|
|
|
610
610
|
|
|
@@ -624,7 +624,7 @@ class McpServerStatusSchema(Schema):
|
|
|
624
624
|
@dataclass
|
|
625
625
|
class SessionContextResponse:
|
|
626
626
|
# None when the response carries no (or malformed) MCP server statuses.
|
|
627
|
-
mcp_servers:
|
|
627
|
+
mcp_servers: list[McpServerStatus] | None = None
|
|
628
628
|
|
|
629
629
|
|
|
630
630
|
class SessionContextResponseSchema(Schema):
|
cycode/cyclient/report_client.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import json
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
4
|
from requests import Response
|
|
6
5
|
|
|
@@ -45,7 +44,7 @@ class ReportClient:
|
|
|
45
44
|
self.client = client
|
|
46
45
|
|
|
47
46
|
def request_sbom_report_execution(
|
|
48
|
-
self, params: ReportParameters, zip_file: InMemoryZip = None, repository_url:
|
|
47
|
+
self, params: ReportParameters, zip_file: InMemoryZip = None, repository_url: str | None = None
|
|
49
48
|
) -> models.ReportExecution:
|
|
50
49
|
report_type = 'zipped-file' if zip_file else 'repository-url'
|
|
51
50
|
url_path = f'{self.SERVICE_NAME}/{self.CREATE_SBOM_REPORT_REQUEST_PATH}'.format(report_type=report_type)
|
cycode/cyclient/scan_client.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from copy import deepcopy
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
from uuid import UUID
|
|
5
6
|
|
|
6
7
|
import requests
|
|
@@ -119,7 +120,7 @@ class ScanClient:
|
|
|
119
120
|
scan_parameters: dict,
|
|
120
121
|
is_git_diff: bool = False,
|
|
121
122
|
is_commit_range: bool = False,
|
|
122
|
-
on_upload_progress:
|
|
123
|
+
on_upload_progress: Callable[[int, int], None] | None = None,
|
|
123
124
|
) -> models.ScanInitializationResponse:
|
|
124
125
|
response = self.scan_cycode_client.post_multipart(
|
|
125
126
|
url_path=self.get_zipped_file_scan_async_url_path(scan_type),
|
|
@@ -145,7 +146,7 @@ class ScanClient:
|
|
|
145
146
|
url: str,
|
|
146
147
|
fields: dict[str, str],
|
|
147
148
|
zip_file: 'InMemoryZip',
|
|
148
|
-
on_upload_progress:
|
|
149
|
+
on_upload_progress: Callable[[int, int], None] | None = None,
|
|
149
150
|
) -> None:
|
|
150
151
|
all_files = {key: (None, value) for key, value in fields.items()}
|
|
151
152
|
all_files['file'] = ('multiple_files_scan.zip', zip_file.read(), 'application/octet-stream')
|
|
@@ -315,7 +316,7 @@ class ScanClient:
|
|
|
315
316
|
def parse_detection_rules_response(response: Response) -> list[models.DetectionRule]:
|
|
316
317
|
return models.DetectionRuleSchema().load(response.json(), many=True)
|
|
317
318
|
|
|
318
|
-
def get_detection_rules(self, detection_rules_ids:
|
|
319
|
+
def get_detection_rules(self, detection_rules_ids: set[str] | list[str]) -> list[models.DetectionRule]:
|
|
319
320
|
response = self.scan_cycode_client.get(
|
|
320
321
|
url_path=self.get_detection_rules_path(),
|
|
321
322
|
params={'ids': detection_rules_ids},
|
|
@@ -372,7 +373,7 @@ class ScanClient:
|
|
|
372
373
|
correct_scan_type = self.scan_config.get_async_scan_type(scan_type)
|
|
373
374
|
return f'{self.get_scan_service_url_path(scan_type)}/{correct_scan_type}/configuration'
|
|
374
375
|
|
|
375
|
-
def get_scan_configuration(self, scan_type: str, remote_url:
|
|
376
|
+
def get_scan_configuration(self, scan_type: str, remote_url: str | None = None) -> models.ScanConfiguration:
|
|
376
377
|
params = {}
|
|
377
378
|
if remote_url:
|
|
378
379
|
params['remote_url'] = remote_url
|
|
@@ -385,8 +386,8 @@ class ScanClient:
|
|
|
385
386
|
return models.ScanConfigurationSchema().load(response.json())
|
|
386
387
|
|
|
387
388
|
def get_scan_configuration_safe(
|
|
388
|
-
self, scan_type: str, remote_url:
|
|
389
|
-
) ->
|
|
389
|
+
self, scan_type: str, remote_url: str | None = None
|
|
390
|
+
) -> 'models.ScanConfiguration | None':
|
|
390
391
|
try:
|
|
391
392
|
return self.get_scan_configuration(scan_type, remote_url)
|
|
392
393
|
except RequestHttpError as e:
|