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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from collections.abc import Hashable
|
|
3
|
-
from typing import TYPE_CHECKING, Any,
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Union
|
|
4
4
|
|
|
5
5
|
from cycode.cli.consts import CYCODE_CONFIGURATION_DIRECTORY
|
|
6
6
|
from cycode.cli.user_settings.base_file_manager import BaseFileManager
|
|
@@ -30,26 +30,26 @@ class ConfigFileManager(BaseFileManager):
|
|
|
30
30
|
def __init__(self, path: Union['Path', str]) -> None:
|
|
31
31
|
self.path = path
|
|
32
32
|
|
|
33
|
-
def get_api_url(self) ->
|
|
33
|
+
def get_api_url(self) -> Any | None:
|
|
34
34
|
return self._get_value_from_environment_section(self.API_URL_FIELD_NAME)
|
|
35
35
|
|
|
36
|
-
def get_app_url(self) ->
|
|
36
|
+
def get_app_url(self) -> Any | None:
|
|
37
37
|
return self._get_value_from_environment_section(self.APP_URL_FIELD_NAME)
|
|
38
38
|
|
|
39
|
-
def get_verbose_flag(self) ->
|
|
39
|
+
def get_verbose_flag(self) -> Any | None:
|
|
40
40
|
return self._get_value_from_environment_section(self.VERBOSE_FIELD_NAME)
|
|
41
41
|
|
|
42
42
|
def get_exclusions_by_scan_type(self, scan_type: str) -> dict[Hashable, Any]:
|
|
43
43
|
exclusions_section = self._get_section(self.EXCLUSIONS_SECTION_NAME)
|
|
44
44
|
return exclusions_section.get(scan_type, {})
|
|
45
45
|
|
|
46
|
-
def get_max_commits(self, command_scan_type: str) ->
|
|
46
|
+
def get_max_commits(self, command_scan_type: str) -> Any | None:
|
|
47
47
|
return self._get_value_from_command_scan_type_configuration(command_scan_type, self.MAX_COMMITS_FIELD_NAME)
|
|
48
48
|
|
|
49
|
-
def get_command_timeout(self, command_scan_type: str) ->
|
|
49
|
+
def get_command_timeout(self, command_scan_type: str) -> Any | None:
|
|
50
50
|
return self._get_value_from_command_scan_type_configuration(command_scan_type, self.COMMAND_TIMEOUT_FIELD_NAME)
|
|
51
51
|
|
|
52
|
-
def get_exclude_detections_in_deleted_lines(self, command_scan_type: str) ->
|
|
52
|
+
def get_exclude_detections_in_deleted_lines(self, command_scan_type: str) -> Any | None:
|
|
53
53
|
return self._get_value_from_command_scan_type_configuration(
|
|
54
54
|
command_scan_type, self.EXCLUDE_DETECTIONS_IN_DELETED_LINES
|
|
55
55
|
)
|
|
@@ -62,7 +62,7 @@ class ConfigFileManager(BaseFileManager):
|
|
|
62
62
|
update_data = {self.ENVIRONMENT_SECTION_NAME: {self.APP_URL_FIELD_NAME: app_url}}
|
|
63
63
|
self.write_content_to_file(update_data)
|
|
64
64
|
|
|
65
|
-
def get_installation_id(self) ->
|
|
65
|
+
def get_installation_id(self) -> str | None:
|
|
66
66
|
return self._get_value_from_environment_section(self.INSTALLATION_ID_FIELD_NAME)
|
|
67
67
|
|
|
68
68
|
def update_installation_id(self, installation_id: str) -> None:
|
|
@@ -103,7 +103,7 @@ class ConfigFileManager(BaseFileManager):
|
|
|
103
103
|
scan_type_exclusions = self.get_exclusions_by_scan_type(scan_type)
|
|
104
104
|
return scan_type_exclusions.get(exclusion_type, [])
|
|
105
105
|
|
|
106
|
-
def _get_value_from_environment_section(self, field_name: str) ->
|
|
106
|
+
def _get_value_from_environment_section(self, field_name: str) -> Any | None:
|
|
107
107
|
environment_section = self._get_section(self.ENVIRONMENT_SECTION_NAME)
|
|
108
108
|
return environment_section.get(field_name)
|
|
109
109
|
|
|
@@ -111,7 +111,7 @@ class ConfigFileManager(BaseFileManager):
|
|
|
111
111
|
scan_section = self._get_section(self.SCAN_SECTION_NAME)
|
|
112
112
|
return scan_section.get(command_scan_type, {})
|
|
113
113
|
|
|
114
|
-
def _get_value_from_command_scan_type_configuration(self, command_scan_type: str, field_name: str) ->
|
|
114
|
+
def _get_value_from_command_scan_type_configuration(self, command_scan_type: str, field_name: str) -> Any | None:
|
|
115
115
|
command_scan_type_configuration = self._get_scan_configuration_by_scan_type(command_scan_type)
|
|
116
116
|
return command_scan_type_configuration.get(field_name)
|
|
117
117
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from functools import cache
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
from cycode.cli import consts
|
|
@@ -59,10 +59,10 @@ class ConfigurationManager:
|
|
|
59
59
|
verbose_flag_global_config = self.global_config_file_manager.get_verbose_flag()
|
|
60
60
|
return verbose_flag_env_var or verbose_flag_local_config or verbose_flag_global_config
|
|
61
61
|
|
|
62
|
-
def get_api_url_from_environment_variables(self) ->
|
|
62
|
+
def get_api_url_from_environment_variables(self) -> str | None:
|
|
63
63
|
return self._get_value_from_environment_variables(consts.CYCODE_API_URL_ENV_VAR_NAME)
|
|
64
64
|
|
|
65
|
-
def get_app_url_from_environment_variables(self) ->
|
|
65
|
+
def get_app_url_from_environment_variables(self) -> str | None:
|
|
66
66
|
return self._get_value_from_environment_variables(consts.CYCODE_APP_URL_ENV_VAR_NAME)
|
|
67
67
|
|
|
68
68
|
def get_verbose_flag_from_environment_variables(self) -> bool:
|
|
@@ -94,13 +94,13 @@ class ConfigurationManager:
|
|
|
94
94
|
|
|
95
95
|
return installation_id
|
|
96
96
|
|
|
97
|
-
def get_last_reported_activation_version(self, client: str) ->
|
|
97
|
+
def get_last_reported_activation_version(self, client: str) -> str | None:
|
|
98
98
|
return self.global_config_file_manager.get_last_reported_activation_versions().get(client)
|
|
99
99
|
|
|
100
100
|
def update_last_reported_activation_version(self, client: str, version: str) -> None:
|
|
101
101
|
self.global_config_file_manager.update_last_reported_activation_version(client, version)
|
|
102
102
|
|
|
103
|
-
def get_config_file_manager(self, scope:
|
|
103
|
+
def get_config_file_manager(self, scope: str | None = None) -> ConfigFileManager:
|
|
104
104
|
if scope == 'local':
|
|
105
105
|
return self.local_config_file_manager
|
|
106
106
|
|
|
@@ -223,5 +223,5 @@ class ConfigurationManager:
|
|
|
223
223
|
return consts.DEFAULT_EXCLUDE_DETECTIONS_IN_DELETED_LINES
|
|
224
224
|
|
|
225
225
|
@staticmethod
|
|
226
|
-
def _get_value_from_environment_variables(env_var_name: str, default:
|
|
226
|
+
def _get_value_from_environment_variables(env_var_name: str, default: Any | None = None) -> Any | None:
|
|
227
227
|
return os.getenv(env_var_name, default)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
4
|
from cycode.cli.config import (
|
|
6
5
|
CYCODE_CLIENT_ID_ENV_VAR_NAME,
|
|
@@ -36,19 +35,19 @@ class CredentialsManager(BaseFileManager):
|
|
|
36
35
|
client_secret = os.getenv(CYCODE_CLIENT_SECRET_ENV_VAR_NAME)
|
|
37
36
|
return client_id, client_secret
|
|
38
37
|
|
|
39
|
-
def get_credentials_from_file(self) -> tuple[
|
|
38
|
+
def get_credentials_from_file(self) -> tuple[str | None, str | None]:
|
|
40
39
|
file_content = self.read_file()
|
|
41
40
|
client_id = file_content.get(self.CLIENT_ID_FIELD_NAME)
|
|
42
41
|
client_secret = file_content.get(self.CLIENT_SECRET_FIELD_NAME)
|
|
43
42
|
return client_id, client_secret
|
|
44
43
|
|
|
45
|
-
def get_oidc_credentials_from_file(self) -> tuple[
|
|
44
|
+
def get_oidc_credentials_from_file(self) -> tuple[str | None, str | None]:
|
|
46
45
|
file_content = self.read_file()
|
|
47
46
|
client_id = file_content.get(self.CLIENT_ID_FIELD_NAME)
|
|
48
47
|
id_token = file_content.get(self.ID_TOKEN_FIELD_NAME)
|
|
49
48
|
return client_id, id_token
|
|
50
49
|
|
|
51
|
-
def get_oidc_credentials(self) -> tuple[
|
|
50
|
+
def get_oidc_credentials(self) -> tuple[str | None, str | None]:
|
|
52
51
|
client_id = os.getenv(CYCODE_CLIENT_ID_ENV_VAR_NAME)
|
|
53
52
|
id_token = os.getenv(CYCODE_ID_TOKEN_ENV_VAR_NAME)
|
|
54
53
|
|
|
@@ -65,7 +64,7 @@ class CredentialsManager(BaseFileManager):
|
|
|
65
64
|
file_content_to_update = {self.CLIENT_ID_FIELD_NAME: client_id, self.CLIENT_SECRET_FIELD_NAME: client_secret}
|
|
66
65
|
self.write_content_to_file(file_content_to_update)
|
|
67
66
|
|
|
68
|
-
def get_access_token(self) -> tuple[
|
|
67
|
+
def get_access_token(self) -> tuple[str | None, float | None, JwtCreator | None]:
|
|
69
68
|
file_content = self.read_file()
|
|
70
69
|
|
|
71
70
|
access_token = file_content.get(self.ACCESS_TOKEN_FIELD_NAME)
|
|
@@ -79,7 +78,7 @@ class CredentialsManager(BaseFileManager):
|
|
|
79
78
|
return access_token, expires_in, creator
|
|
80
79
|
|
|
81
80
|
def update_access_token(
|
|
82
|
-
self, access_token:
|
|
81
|
+
self, access_token: str | None, expires_in: float | None, creator: JwtCreator | None
|
|
83
82
|
) -> None:
|
|
84
83
|
file_content_to_update = {
|
|
85
84
|
self.ACCESS_TOKEN_FIELD_NAME: access_token,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING,
|
|
1
|
+
from typing import TYPE_CHECKING, Union
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
@@ -21,10 +21,10 @@ if TYPE_CHECKING:
|
|
|
21
21
|
|
|
22
22
|
def _get_cycode_client(
|
|
23
23
|
create_client_func: callable,
|
|
24
|
-
client_id:
|
|
25
|
-
client_secret:
|
|
24
|
+
client_id: str | None,
|
|
25
|
+
client_secret: str | None,
|
|
26
26
|
hide_response_log: bool,
|
|
27
|
-
id_token:
|
|
27
|
+
id_token: str | None = None,
|
|
28
28
|
) -> Union['ScanClient', 'ReportClient', 'ImportSbomClient', 'AISecurityManagerClient']:
|
|
29
29
|
if client_id and id_token:
|
|
30
30
|
return create_client_func(client_id, None, hide_response_log, id_token)
|
|
@@ -80,6 +80,6 @@ def _get_configured_credentials() -> tuple[str, str]:
|
|
|
80
80
|
return credentials_manager.get_credentials()
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def _get_configured_oidc_credentials() -> tuple[
|
|
83
|
+
def _get_configured_oidc_credentials() -> tuple[str | None, str | None]:
|
|
84
84
|
credentials_manager = CredentialsManager()
|
|
85
85
|
return credentials_manager.get_oidc_credentials()
|
cycode/cli/utils/git_proxy.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import types
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
from functools import cache
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
_GIT_ERROR_MESSAGE = """
|
|
7
7
|
Cycode CLI needs the Git executable to be installed on the system.
|
|
@@ -21,7 +21,7 @@ class GitProxyError(Exception):
|
|
|
21
21
|
|
|
22
22
|
# GitPython runs `git version` on import, so it is imported on first use rather than at CLI startup
|
|
23
23
|
@cache
|
|
24
|
-
def _import_git() ->
|
|
24
|
+
def _import_git() -> types.ModuleType | None:
|
|
25
25
|
try:
|
|
26
26
|
import git
|
|
27
27
|
except ImportError:
|
|
@@ -31,7 +31,7 @@ def _import_git() -> Optional[types.ModuleType]:
|
|
|
31
31
|
|
|
32
32
|
class _AbstractGitProxy(ABC):
|
|
33
33
|
@abstractmethod
|
|
34
|
-
def get_repo(self, path:
|
|
34
|
+
def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo': ...
|
|
35
35
|
|
|
36
36
|
@abstractmethod
|
|
37
37
|
def get_null_tree(self) -> object: ...
|
|
@@ -44,7 +44,7 @@ class _AbstractGitProxy(ABC):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class _DummyGitProxy(_AbstractGitProxy):
|
|
47
|
-
def get_repo(self, path:
|
|
47
|
+
def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo':
|
|
48
48
|
raise RuntimeError(_GIT_ERROR_MESSAGE)
|
|
49
49
|
|
|
50
50
|
def get_null_tree(self) -> object:
|
|
@@ -61,7 +61,7 @@ class _GitProxy(_AbstractGitProxy):
|
|
|
61
61
|
def __init__(self, git_module: types.ModuleType) -> None:
|
|
62
62
|
self._git = git_module
|
|
63
63
|
|
|
64
|
-
def get_repo(self, path:
|
|
64
|
+
def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo':
|
|
65
65
|
return self._git.Repo(path, *args, **kwargs)
|
|
66
66
|
|
|
67
67
|
def get_null_tree(self) -> object:
|
|
@@ -74,7 +74,7 @@ class _GitProxy(_AbstractGitProxy):
|
|
|
74
74
|
return self._git.GitCommandError
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
def get_git_proxy(git_module:
|
|
77
|
+
def get_git_proxy(git_module: types.ModuleType | None) -> _AbstractGitProxy:
|
|
78
78
|
return _GitProxy(git_module) if git_module else _DummyGitProxy()
|
|
79
79
|
|
|
80
80
|
|
|
@@ -82,7 +82,7 @@ class GitProxyManager(_AbstractGitProxy):
|
|
|
82
82
|
"""We are using this manager for easy unit testing and mocking of the git module."""
|
|
83
83
|
|
|
84
84
|
def __init__(self) -> None:
|
|
85
|
-
self._git_proxy:
|
|
85
|
+
self._git_proxy: _AbstractGitProxy | None = None
|
|
86
86
|
|
|
87
87
|
def _get_git_proxy(self) -> _AbstractGitProxy:
|
|
88
88
|
if self._git_proxy is None:
|
|
@@ -95,7 +95,7 @@ class GitProxyManager(_AbstractGitProxy):
|
|
|
95
95
|
def _set_git_proxy(self) -> None:
|
|
96
96
|
self._git_proxy = _GitProxy(_import_git())
|
|
97
97
|
|
|
98
|
-
def get_repo(self, path:
|
|
98
|
+
def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo':
|
|
99
99
|
return self._get_git_proxy().get_repo(path, *args, **kwargs)
|
|
100
100
|
|
|
101
101
|
def get_null_tree(self) -> object:
|
cycode/cli/utils/host_info.py
CHANGED
|
@@ -7,15 +7,15 @@ import subprocess
|
|
|
7
7
|
import sys
|
|
8
8
|
import tempfile
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
from cycode.cli.consts import CYCODE_CONFIGURATION_DIRECTORY
|
|
13
13
|
from cycode.logger import get_logger
|
|
14
14
|
|
|
15
15
|
logger = get_logger('HOST INFO')
|
|
16
16
|
|
|
17
|
-
pythoncom:
|
|
18
|
-
win32com_client:
|
|
17
|
+
pythoncom: Any | None = None
|
|
18
|
+
win32com_client: Any | None = None
|
|
19
19
|
if sys.platform == 'win32':
|
|
20
20
|
try:
|
|
21
21
|
import pythoncom
|
|
@@ -30,31 +30,22 @@ _DEVICE_ID_CACHE_FILE_NAME = 'device-id'
|
|
|
30
30
|
_PLATFORM_NAMES = {'Darwin': 'macOS', 'Windows': 'Windows', 'Linux': 'Linux'}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def _run(command: list, timeout: int = _SUBPROCESS_TIMEOUT_SEC) ->
|
|
33
|
+
def _run(command: list, timeout: int = _SUBPROCESS_TIMEOUT_SEC) -> str | None:
|
|
34
34
|
"""Run a command and return its stripped stdout. Never raises; returns None on any error."""
|
|
35
35
|
try:
|
|
36
|
-
result = subprocess.run(command, capture_output=True, text=True, timeout=timeout) # noqa: S603
|
|
36
|
+
result = subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False) # noqa: S603
|
|
37
37
|
return result.stdout.strip() or None
|
|
38
38
|
except Exception as e:
|
|
39
39
|
logger.debug('Failed to run command %s', command, exc_info=e)
|
|
40
40
|
return None
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def _read_text_file(path: str) -> Optional[str]:
|
|
44
|
-
"""Read and strip a text file. Never raises; returns None if it can't be read."""
|
|
45
|
-
try:
|
|
46
|
-
with open(path) as text_file:
|
|
47
|
-
return text_file.read().strip() or None
|
|
48
|
-
except OSError:
|
|
49
|
-
return None
|
|
50
|
-
|
|
51
|
-
|
|
52
43
|
def is_64bit() -> bool:
|
|
53
44
|
"""Whether the running Python interpreter is 64-bit (not the OS)."""
|
|
54
45
|
return sys.maxsize > 2**32
|
|
55
46
|
|
|
56
47
|
|
|
57
|
-
def get_hostname() ->
|
|
48
|
+
def get_hostname() -> str | None:
|
|
58
49
|
try:
|
|
59
50
|
return socket.gethostname() or None
|
|
60
51
|
except Exception as e:
|
|
@@ -62,7 +53,7 @@ def get_hostname() -> Optional[str]:
|
|
|
62
53
|
return None
|
|
63
54
|
|
|
64
55
|
|
|
65
|
-
def get_platform_name() ->
|
|
56
|
+
def get_platform_name() -> str | None:
|
|
66
57
|
try:
|
|
67
58
|
system = platform.system()
|
|
68
59
|
return _PLATFORM_NAMES.get(system, system or None)
|
|
@@ -71,7 +62,7 @@ def get_platform_name() -> Optional[str]:
|
|
|
71
62
|
return None
|
|
72
63
|
|
|
73
64
|
|
|
74
|
-
def get_os_version() ->
|
|
65
|
+
def get_os_version() -> str | None:
|
|
75
66
|
try:
|
|
76
67
|
system = platform.system()
|
|
77
68
|
if system == 'Darwin':
|
|
@@ -86,26 +77,18 @@ def get_os_version() -> Optional[str]:
|
|
|
86
77
|
return None
|
|
87
78
|
|
|
88
79
|
|
|
89
|
-
def _get_linux_os_version() ->
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
version_id
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
except OSError:
|
|
97
|
-
pass
|
|
98
|
-
|
|
99
|
-
os_release = _read_text_file('/etc/os-release') # Python 3.9 fallback: parse manually
|
|
100
|
-
if os_release:
|
|
101
|
-
for line in os_release.splitlines():
|
|
102
|
-
if line.startswith('VERSION_ID='):
|
|
103
|
-
return line.split('=', 1)[1].strip().strip('"') or None
|
|
80
|
+
def _get_linux_os_version() -> str | None:
|
|
81
|
+
try:
|
|
82
|
+
version_id = platform.freedesktop_os_release().get('VERSION_ID')
|
|
83
|
+
if version_id:
|
|
84
|
+
return version_id
|
|
85
|
+
except OSError:
|
|
86
|
+
pass
|
|
104
87
|
|
|
105
88
|
return platform.release() or None
|
|
106
89
|
|
|
107
90
|
|
|
108
|
-
def get_last_login_user() ->
|
|
91
|
+
def get_last_login_user() -> str | None:
|
|
109
92
|
try:
|
|
110
93
|
return getpass.getuser() or None
|
|
111
94
|
except Exception as e:
|
|
@@ -113,7 +96,7 @@ def get_last_login_user() -> Optional[str]:
|
|
|
113
96
|
return None
|
|
114
97
|
|
|
115
98
|
|
|
116
|
-
def get_serial_number() ->
|
|
99
|
+
def get_serial_number() -> str | None:
|
|
117
100
|
# The serial is immutable hardware info, but resolving it shells out (ioreg/WMI)
|
|
118
101
|
# and this runs in a fresh process per AI hook event - cache it on disk.
|
|
119
102
|
cached = _read_serial_number_cache()
|
|
@@ -126,7 +109,7 @@ def get_serial_number() -> Optional[str]:
|
|
|
126
109
|
return serial
|
|
127
110
|
|
|
128
111
|
|
|
129
|
-
def _resolve_serial_number() ->
|
|
112
|
+
def _resolve_serial_number() -> str | None:
|
|
130
113
|
try:
|
|
131
114
|
system = platform.system()
|
|
132
115
|
if system == 'Darwin':
|
|
@@ -142,7 +125,7 @@ def _serial_number_cache_path() -> Path:
|
|
|
142
125
|
return Path.home() / CYCODE_CONFIGURATION_DIRECTORY / _DEVICE_ID_CACHE_FILE_NAME
|
|
143
126
|
|
|
144
127
|
|
|
145
|
-
def _read_serial_number_cache() ->
|
|
128
|
+
def _read_serial_number_cache() -> str | None:
|
|
146
129
|
try:
|
|
147
130
|
return _serial_number_cache_path().read_text(encoding='utf-8').strip() or None
|
|
148
131
|
except Exception:
|
|
@@ -170,7 +153,7 @@ def _write_serial_number_cache(serial: str) -> None:
|
|
|
170
153
|
logger.debug('Failed to cache serial number', exc_info=e)
|
|
171
154
|
|
|
172
155
|
|
|
173
|
-
def _get_macos_serial_number() ->
|
|
156
|
+
def _get_macos_serial_number() -> str | None:
|
|
174
157
|
output = _run(['ioreg', '-c', 'IOPlatformExpertDevice', '-d', '2'])
|
|
175
158
|
if not output:
|
|
176
159
|
return None
|
|
@@ -178,7 +161,7 @@ def _get_macos_serial_number() -> Optional[str]:
|
|
|
178
161
|
return match.group(1) if match else None
|
|
179
162
|
|
|
180
163
|
|
|
181
|
-
def _get_windows_serial_number() ->
|
|
164
|
+
def _get_windows_serial_number() -> str | None:
|
|
182
165
|
"""Read the OEM serial over WMI."""
|
|
183
166
|
if pythoncom is None or win32com_client is None:
|
|
184
167
|
return None
|
cycode/cli/utils/ignore_utils.py
CHANGED
|
@@ -43,8 +43,6 @@ from os import PathLike
|
|
|
43
43
|
from typing import (
|
|
44
44
|
Any,
|
|
45
45
|
BinaryIO,
|
|
46
|
-
Optional,
|
|
47
|
-
Union,
|
|
48
46
|
)
|
|
49
47
|
|
|
50
48
|
|
|
@@ -205,9 +203,9 @@ class Pattern:
|
|
|
205
203
|
class IgnoreFilter:
|
|
206
204
|
def __init__(
|
|
207
205
|
self,
|
|
208
|
-
patterns: Iterable[
|
|
206
|
+
patterns: Iterable[str | bytes],
|
|
209
207
|
ignore_case: bool = False,
|
|
210
|
-
path:
|
|
208
|
+
path: PathLike | str | None = None,
|
|
211
209
|
) -> None:
|
|
212
210
|
if hasattr(path, '__fspath__'):
|
|
213
211
|
path = path.__fspath__()
|
|
@@ -227,13 +225,13 @@ class IgnoreFilter:
|
|
|
227
225
|
d['path'] = path
|
|
228
226
|
return d
|
|
229
227
|
|
|
230
|
-
def append_pattern(self, pattern:
|
|
228
|
+
def append_pattern(self, pattern: str | bytes) -> None:
|
|
231
229
|
"""Add a pattern to the set."""
|
|
232
230
|
if isinstance(pattern, str):
|
|
233
231
|
pattern = bytes(pattern, 'utf-8')
|
|
234
232
|
self._patterns.append(Pattern(pattern, self._ignore_case))
|
|
235
233
|
|
|
236
|
-
def find_matching(self, path:
|
|
234
|
+
def find_matching(self, path: bytes | str) -> Iterable[Pattern]:
|
|
237
235
|
"""Yield all matching patterns for path.
|
|
238
236
|
|
|
239
237
|
Args:
|
|
@@ -248,7 +246,7 @@ class IgnoreFilter:
|
|
|
248
246
|
if pattern.match(path):
|
|
249
247
|
yield pattern
|
|
250
248
|
|
|
251
|
-
def is_ignored(self, path:
|
|
249
|
+
def is_ignored(self, path: bytes | str) -> bool | None:
|
|
252
250
|
"""Check whether a path is ignored.
|
|
253
251
|
|
|
254
252
|
For directories, include a trailing slash.
|
|
@@ -264,7 +262,7 @@ class IgnoreFilter:
|
|
|
264
262
|
return status
|
|
265
263
|
|
|
266
264
|
@classmethod
|
|
267
|
-
def from_path(cls, path:
|
|
265
|
+
def from_path(cls, path: PathLike | str, ignore_case: bool = False) -> 'IgnoreFilter':
|
|
268
266
|
if hasattr(path, '__fspath__'):
|
|
269
267
|
path = path.__fspath__()
|
|
270
268
|
with open(path, 'rb') as f:
|
|
@@ -284,7 +282,7 @@ class IgnoreFilterManager:
|
|
|
284
282
|
self,
|
|
285
283
|
path: str,
|
|
286
284
|
global_filters: list[IgnoreFilter],
|
|
287
|
-
ignore_file_name:
|
|
285
|
+
ignore_file_name: str | None = None,
|
|
288
286
|
ignore_case: bool = False,
|
|
289
287
|
) -> None:
|
|
290
288
|
if hasattr(path, '__fspath__'):
|
|
@@ -317,14 +315,14 @@ class IgnoreFilterManager:
|
|
|
317
315
|
return self._top_path
|
|
318
316
|
|
|
319
317
|
@property
|
|
320
|
-
def ignore_file_name(self) ->
|
|
318
|
+
def ignore_file_name(self) -> str | None:
|
|
321
319
|
return self._ignore_file_name
|
|
322
320
|
|
|
323
321
|
@property
|
|
324
322
|
def ignore_case(self) -> bool:
|
|
325
323
|
return self._ignore_case
|
|
326
324
|
|
|
327
|
-
def _load_path(self, path: str) ->
|
|
325
|
+
def _load_path(self, path: str) -> IgnoreFilter | None:
|
|
328
326
|
try:
|
|
329
327
|
return self._path_filters[path]
|
|
330
328
|
except KeyError:
|
|
@@ -370,7 +368,7 @@ class IgnoreFilterManager:
|
|
|
370
368
|
filters.insert(0, (i, ignore_filter))
|
|
371
369
|
return iter(matches)
|
|
372
370
|
|
|
373
|
-
def is_ignored(self, path: str) ->
|
|
371
|
+
def is_ignored(self, path: str) -> bool | None:
|
|
374
372
|
"""Check whether a path is ignored.
|
|
375
373
|
|
|
376
374
|
Args:
|
|
@@ -425,9 +423,9 @@ class IgnoreFilterManager:
|
|
|
425
423
|
def build(
|
|
426
424
|
cls,
|
|
427
425
|
path: str,
|
|
428
|
-
global_ignore_file_paths:
|
|
429
|
-
global_patterns:
|
|
430
|
-
ignore_file_name:
|
|
426
|
+
global_ignore_file_paths: Iterable[str] | None = None,
|
|
427
|
+
global_patterns: Iterable[str | bytes] | None = None,
|
|
428
|
+
ignore_file_name: str | None = None,
|
|
431
429
|
ignore_case: bool = False,
|
|
432
430
|
) -> 'IgnoreFilterManager':
|
|
433
431
|
"""Create a IgnoreFilterManager from patterns and paths.
|
cycode/cli/utils/jwt_utils.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
import jwt
|
|
4
2
|
|
|
5
3
|
_JWT_PAYLOAD_POSSIBLE_USER_ID_FIELD_NAMES = ('userId', 'internalId', 'token-user-id')
|
|
6
4
|
|
|
7
5
|
|
|
8
|
-
def decode_jwt_unverified(token: str) ->
|
|
6
|
+
def decode_jwt_unverified(token: str) -> dict | None:
|
|
9
7
|
"""Return JWT claims without signature verification, or None if the token is unreadable."""
|
|
10
8
|
try:
|
|
11
9
|
return jwt.decode(token, options={'verify_signature': False})
|
|
@@ -13,7 +11,7 @@ def decode_jwt_unverified(token: str) -> Optional[dict]:
|
|
|
13
11
|
return None
|
|
14
12
|
|
|
15
13
|
|
|
16
|
-
def get_user_and_tenant_ids_from_access_token(access_token: str) -> tuple[
|
|
14
|
+
def get_user_and_tenant_ids_from_access_token(access_token: str) -> tuple[str | None, str | None]:
|
|
17
15
|
payload = jwt.decode(access_token, options={'verify_signature': False})
|
|
18
16
|
|
|
19
17
|
user_id = None
|
cycode/cli/utils/path_utils.py
CHANGED
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import tempfile
|
|
4
4
|
from functools import cache
|
|
5
5
|
from pathlib import PurePath, PurePosixPath, PureWindowsPath
|
|
6
|
-
from typing import TYPE_CHECKING, AnyStr,
|
|
6
|
+
from typing import TYPE_CHECKING, AnyStr, Union
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
@@ -31,7 +31,7 @@ def get_absolute_path(path: str) -> str:
|
|
|
31
31
|
return os.path.abspath(path)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def _get_starting_chunk(filename: str, length: int = 1024) ->
|
|
34
|
+
def _get_starting_chunk(filename: str, length: int = 1024) -> bytes | None:
|
|
35
35
|
# We are using our own implementation of get_starting_chunk
|
|
36
36
|
# because the original one from binaryornot uses print()...
|
|
37
37
|
|
|
@@ -79,7 +79,7 @@ def join_paths(path: str, filename: str) -> str:
|
|
|
79
79
|
return os.path.join(path, filename)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def get_file_content(file_path: Union[str, 'PathLike']) ->
|
|
82
|
+
def get_file_content(file_path: Union[str, 'PathLike']) -> AnyStr | None:
|
|
83
83
|
try:
|
|
84
84
|
with open(file_path, encoding='UTF-8') as f:
|
|
85
85
|
return f.read()
|
|
@@ -115,7 +115,7 @@ def quarantine_corrupt_file(filename: str) -> None:
|
|
|
115
115
|
logger.warning('Failed to quarantine corrupt file, %s', {'filename': filename}, exc_info=e)
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
def load_json(txt: str) ->
|
|
118
|
+
def load_json(txt: str) -> dict | None:
|
|
119
119
|
try:
|
|
120
120
|
return json.loads(txt)
|
|
121
121
|
except json.JSONDecodeError:
|
|
@@ -152,7 +152,7 @@ def concat_unique_id(filename: str, unique_id: str) -> str:
|
|
|
152
152
|
return str(PurePosixPath(unique_id, *_to_relative_posix_parts(filename)))
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
def _as_scan_root(value: object) ->
|
|
155
|
+
def _as_scan_root(value: object) -> str | None:
|
|
156
156
|
if isinstance(value, (str, os.PathLike)):
|
|
157
157
|
return os.fspath(value) or None
|
|
158
158
|
|
|
@@ -181,7 +181,7 @@ def get_scan_roots_from_context(ctx: typer.Context) -> tuple[str, ...]:
|
|
|
181
181
|
return tuple(dict.fromkeys(scan_roots))
|
|
182
182
|
|
|
183
183
|
|
|
184
|
-
def get_path_from_context(ctx: typer.Context) ->
|
|
184
|
+
def get_path_from_context(ctx: typer.Context) -> str | None:
|
|
185
185
|
scan_roots = get_scan_roots_from_context(ctx)
|
|
186
186
|
return scan_roots[0] if scan_roots else None
|
|
187
187
|
|
cycode/cli/utils/progress_bar.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from enum import auto
|
|
3
|
-
from typing import NamedTuple
|
|
3
|
+
from typing import NamedTuple
|
|
4
4
|
|
|
5
5
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
|
|
6
6
|
|
|
@@ -109,7 +109,7 @@ class BaseProgressBar(ABC):
|
|
|
109
109
|
def update(self, section: 'ProgressBarSection') -> None: ...
|
|
110
110
|
|
|
111
111
|
@abstractmethod
|
|
112
|
-
def update_right_side_label(self, label:
|
|
112
|
+
def update_right_side_label(self, label: str | None = None) -> None: ...
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
class DummyProgressBar(BaseProgressBar):
|
|
@@ -128,7 +128,7 @@ class DummyProgressBar(BaseProgressBar):
|
|
|
128
128
|
def update(self, section: 'ProgressBarSection') -> None:
|
|
129
129
|
pass
|
|
130
130
|
|
|
131
|
-
def update_right_side_label(self, label:
|
|
131
|
+
def update_right_side_label(self, label: str | None = None) -> None:
|
|
132
132
|
pass
|
|
133
133
|
|
|
134
134
|
|
|
@@ -240,7 +240,7 @@ class CompositeProgressBar(BaseProgressBar):
|
|
|
240
240
|
self._increment_progress(section)
|
|
241
241
|
self._maybe_update_current_section()
|
|
242
242
|
|
|
243
|
-
def update_right_side_label(self, label:
|
|
243
|
+
def update_right_side_label(self, label: str | None = None) -> None:
|
|
244
244
|
self._current_right_side_label = f'({label})' if label else ''
|
|
245
245
|
self._progress_bar_update()
|
|
246
246
|
|
cycode/cli/utils/scan_batch.py
CHANGED
cycode/cli/utils/scan_utils.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
3
|
from uuid import UUID, uuid4
|
|
4
4
|
|
|
5
5
|
import typer
|
|
@@ -26,7 +26,7 @@ def is_scan_failed(ctx: typer.Context) -> bool:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def is_cycodeignore_allowed_by_scan_config(ctx: typer.Context) -> bool:
|
|
29
|
-
scan_config:
|
|
29
|
+
scan_config: ScanConfiguration | None = ctx.obj.get('scan_config')
|
|
30
30
|
return scan_config.is_cycode_ignore_allowed if scan_config else True
|
|
31
31
|
|
|
32
32
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
import time
|
|
3
|
-
from typing import Optional, Union
|
|
4
3
|
|
|
5
4
|
import click
|
|
6
5
|
import typer
|
|
@@ -14,11 +13,11 @@ logger = get_logger('SHELL')
|
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def shell(
|
|
17
|
-
command:
|
|
16
|
+
command: str | list[str],
|
|
18
17
|
timeout: int = _SUBPROCESS_DEFAULT_TIMEOUT_SEC,
|
|
19
|
-
working_directory:
|
|
18
|
+
working_directory: str | None = None,
|
|
20
19
|
silent_exc_info: bool = False,
|
|
21
|
-
) ->
|
|
20
|
+
) -> str | None:
|
|
22
21
|
logger.debug('Executing shell command: %s', command)
|
|
23
22
|
|
|
24
23
|
try:
|