cycode 3.25.1.dev6__py3-none-any.whl → 3.25.2.dev2__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/__init__.py +8 -9
- cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +11 -10
- cycode/cli/apps/ai_guardrails/ides/_skill_utils.py +1 -2
- cycode/cli/apps/ai_guardrails/ides/base.py +19 -21
- cycode/cli/apps/ai_guardrails/ides/claude_code.py +54 -16
- cycode/cli/apps/ai_guardrails/ides/codex.py +54 -20
- cycode/cli/apps/ai_guardrails/ides/copilot.py +23 -25
- cycode/cli/apps/ai_guardrails/ides/cursor.py +48 -10
- cycode/cli/apps/ai_guardrails/install_command.py +3 -3
- cycode/cli/apps/ai_guardrails/scan/guardrail_config.py +44 -22
- cycode/cli/apps/ai_guardrails/scan/handlers.py +79 -14
- cycode/cli/apps/ai_guardrails/scan/mcp_server_status.py +103 -0
- 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/scan/types.py +1 -0
- cycode/cli/apps/ai_guardrails/session_start_command.py +46 -21
- 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/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 +27 -21
- 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 +88 -30
- cycode/cyclient/report_client.py +1 -2
- cycode/cyclient/scan_client.py +8 -7
- cycode/logger.py +4 -4
- {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/METADATA +12 -19
- {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/RECORD +125 -124
- {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/WHEEL +1 -1
- {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/entry_points.txt +0 -0
- {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/licenses/LICENCE +0 -0
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
from cycode.cli.utils.path_utils import get_absolute_path
|
|
9
8
|
from cycode.logger import get_logger
|
|
@@ -17,7 +16,7 @@ def resolved_path(path: object) -> str:
|
|
|
17
16
|
return os.path.realpath(get_absolute_path(str(path)))
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
def file_stamp(path: Path) ->
|
|
19
|
+
def file_stamp(path: Path) -> FileStamp | None:
|
|
21
20
|
try:
|
|
22
21
|
stat_result = path.stat()
|
|
23
22
|
except OSError:
|
|
@@ -26,7 +25,7 @@ def file_stamp(path: Path) -> Optional[FileStamp]:
|
|
|
26
25
|
return str(path), stat_result.st_mtime_ns, stat_result.st_size
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
def read_json_object(path: Path) ->
|
|
28
|
+
def read_json_object(path: Path) -> dict | None:
|
|
30
29
|
try:
|
|
31
30
|
content = json.loads(path.read_text(encoding='UTF-8'))
|
|
32
31
|
except FileNotFoundError:
|
|
@@ -72,8 +72,7 @@ def _split_workspace_patterns(declared: list) -> _WorkspacePatterns:
|
|
|
72
72
|
stripped = entry.strip()
|
|
73
73
|
is_excluded = stripped.startswith(_NEGATION_PREFIX)
|
|
74
74
|
normalized = (stripped[1:] if is_excluded else stripped).strip()
|
|
75
|
-
|
|
76
|
-
normalized = normalized[2:]
|
|
75
|
+
normalized = normalized.removeprefix('./')
|
|
77
76
|
|
|
78
77
|
normalized = normalized.rstrip('/')
|
|
79
78
|
if not normalized:
|
|
@@ -9,7 +9,6 @@ import json
|
|
|
9
9
|
import re
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Optional
|
|
13
12
|
|
|
14
13
|
import yaml
|
|
15
14
|
|
|
@@ -38,17 +37,16 @@ _YARN_WORKSPACE_PROTOCOL = re.compile(r'@workspace:([^"\',\s]+)')
|
|
|
38
37
|
_BUN_LOCKFILE_WORKSPACES_SECTION = 'workspaces'
|
|
39
38
|
_TRAILING_COMMA = re.compile(r',(\s*[}\]])')
|
|
40
39
|
|
|
41
|
-
_member_names_cache: dict[FileStamp,
|
|
40
|
+
_member_names_cache: dict[FileStamp, frozenset[str] | None] = {}
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
def clear_cache() -> None:
|
|
45
44
|
_member_names_cache.clear()
|
|
46
45
|
|
|
47
46
|
|
|
48
|
-
def _normalize_member_path(member_path: str) ->
|
|
47
|
+
def _normalize_member_path(member_path: str) -> str | None:
|
|
49
48
|
normalized = member_path.strip()
|
|
50
|
-
|
|
51
|
-
normalized = normalized[2:]
|
|
49
|
+
normalized = normalized.removeprefix('./')
|
|
52
50
|
|
|
53
51
|
normalized = normalized.rstrip('/')
|
|
54
52
|
if not normalized or normalized == _PNPM_LOCKFILE_ROOT_IMPORTER:
|
|
@@ -57,7 +55,7 @@ def _normalize_member_path(member_path: str) -> Optional[str]:
|
|
|
57
55
|
return normalized
|
|
58
56
|
|
|
59
57
|
|
|
60
|
-
def _npm_lockfile_member_names(lock_file: Path) ->
|
|
58
|
+
def _npm_lockfile_member_names(lock_file: Path) -> frozenset | None:
|
|
61
59
|
content = read_json_object(lock_file)
|
|
62
60
|
packages = content.get(_LOCKFILE_PACKAGES_SECTION) if content is not None else None
|
|
63
61
|
if not isinstance(packages, dict):
|
|
@@ -96,7 +94,7 @@ def _read_pnpm_importers_section(lock_file: Path) -> str:
|
|
|
96
94
|
return '\n'.join(section)
|
|
97
95
|
|
|
98
96
|
|
|
99
|
-
def _pnpm_lockfile_member_names(lock_file: Path) ->
|
|
97
|
+
def _pnpm_lockfile_member_names(lock_file: Path) -> frozenset | None:
|
|
100
98
|
section = _read_pnpm_importers_section(lock_file)
|
|
101
99
|
if not section:
|
|
102
100
|
return None
|
|
@@ -124,7 +122,7 @@ def _pnpm_lockfile_member_names(lock_file: Path) -> Optional[frozenset]:
|
|
|
124
122
|
return frozenset(member_names) or None
|
|
125
123
|
|
|
126
124
|
|
|
127
|
-
def _bun_lockfile_member_names(lock_file: Path) ->
|
|
125
|
+
def _bun_lockfile_member_names(lock_file: Path) -> frozenset | None:
|
|
128
126
|
"""bun.lock is JSON with trailing commas, and names its members under "workspaces"."""
|
|
129
127
|
try:
|
|
130
128
|
text = lock_file.read_text(encoding='UTF-8')
|
|
@@ -151,7 +149,7 @@ def _bun_lockfile_member_names(lock_file: Path) -> Optional[frozenset]:
|
|
|
151
149
|
return frozenset(member_names)
|
|
152
150
|
|
|
153
151
|
|
|
154
|
-
def _yarn_lockfile_member_names(lock_file: Path) ->
|
|
152
|
+
def _yarn_lockfile_member_names(lock_file: Path) -> frozenset | None:
|
|
155
153
|
"""Yarn berry records every member as "<name>@workspace:<path>"; classic yarn records nothing."""
|
|
156
154
|
try:
|
|
157
155
|
text = lock_file.read_text(encoding='UTF-8', errors='replace')
|
|
@@ -201,7 +199,7 @@ class WorkspaceMemberResolver(ABC):
|
|
|
201
199
|
"""
|
|
202
200
|
return False
|
|
203
201
|
|
|
204
|
-
def resolve(self, lock_file: Path) ->
|
|
202
|
+
def resolve(self, lock_file: Path) -> frozenset | None:
|
|
205
203
|
"""Member paths this lockfile resolves, or None when it cannot name them.
|
|
206
204
|
|
|
207
205
|
Caches on the file's identity so one scan parses each root lockfile once.
|
|
@@ -218,14 +216,14 @@ class WorkspaceMemberResolver(ABC):
|
|
|
218
216
|
return member_names
|
|
219
217
|
|
|
220
218
|
@abstractmethod
|
|
221
|
-
def _read_member_names(self, lock_file: Path) ->
|
|
219
|
+
def _read_member_names(self, lock_file: Path) -> frozenset | None: ...
|
|
222
220
|
|
|
223
221
|
|
|
224
222
|
class NpmLockfileResolver(WorkspaceMemberResolver):
|
|
225
223
|
package_manager = NPM_PACKAGE_MANAGER
|
|
226
224
|
lock_file_names = (NPM_LOCK_FILE_NAME, NPM_SHRINKWRAP_FILE_NAME)
|
|
227
225
|
|
|
228
|
-
def _read_member_names(self, lock_file: Path) ->
|
|
226
|
+
def _read_member_names(self, lock_file: Path) -> frozenset | None:
|
|
229
227
|
return _npm_lockfile_member_names(lock_file)
|
|
230
228
|
|
|
231
229
|
|
|
@@ -233,7 +231,7 @@ class PnpmLockfileResolver(WorkspaceMemberResolver):
|
|
|
233
231
|
package_manager = PNPM_PACKAGE_MANAGER
|
|
234
232
|
lock_file_names = (PNPM_LOCK_FILE_NAME,)
|
|
235
233
|
|
|
236
|
-
def _read_member_names(self, lock_file: Path) ->
|
|
234
|
+
def _read_member_names(self, lock_file: Path) -> frozenset | None:
|
|
237
235
|
return _pnpm_lockfile_member_names(lock_file)
|
|
238
236
|
|
|
239
237
|
|
|
@@ -244,7 +242,7 @@ class YarnLockfileResolver(WorkspaceMemberResolver):
|
|
|
244
242
|
lock_file_names = (YARN_LOCK_FILE_NAME,)
|
|
245
243
|
may_use_workspace_globs = True
|
|
246
244
|
|
|
247
|
-
def _read_member_names(self, lock_file: Path) ->
|
|
245
|
+
def _read_member_names(self, lock_file: Path) -> frozenset | None:
|
|
248
246
|
return _yarn_lockfile_member_names(lock_file)
|
|
249
247
|
|
|
250
248
|
|
|
@@ -254,7 +252,7 @@ class BunLockfileResolver(WorkspaceMemberResolver):
|
|
|
254
252
|
package_manager = BUN_PACKAGE_MANAGER
|
|
255
253
|
lock_file_names = (BUN_LOCK_FILE_NAME,)
|
|
256
254
|
|
|
257
|
-
def _read_member_names(self, lock_file: Path) ->
|
|
255
|
+
def _read_member_names(self, lock_file: Path) -> frozenset | None:
|
|
258
256
|
return _bun_lockfile_member_names(lock_file)
|
|
259
257
|
|
|
260
258
|
|
|
@@ -275,7 +273,7 @@ class OpaqueLockfileResolver(WorkspaceMemberResolver):
|
|
|
275
273
|
def lock_file_names(self) -> tuple:
|
|
276
274
|
return self._lock_file_names
|
|
277
275
|
|
|
278
|
-
def _read_member_names(self, lock_file: Path) ->
|
|
276
|
+
def _read_member_names(self, lock_file: Path) -> frozenset | None:
|
|
279
277
|
return None
|
|
280
278
|
|
|
281
279
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
import typer
|
|
5
4
|
|
|
@@ -21,7 +20,7 @@ class RestoreComposerDependencies(BaseRestoreDependencies):
|
|
|
21
20
|
def is_project(self, document: Document) -> bool:
|
|
22
21
|
return Path(document.path).name == COMPOSER_MANIFEST_FILE_NAME
|
|
23
22
|
|
|
24
|
-
def try_restore_dependencies(self, document: Document) ->
|
|
23
|
+
def try_restore_dependencies(self, document: Document) -> Document | None:
|
|
25
24
|
manifest_dir = self.get_manifest_dir(document)
|
|
26
25
|
lockfile_path = Path(manifest_dir) / COMPOSER_LOCK_FILE_NAME if manifest_dir else None
|
|
27
26
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
import typer
|
|
5
4
|
|
|
@@ -18,7 +17,7 @@ _POETRY_TOOL_SECTION = '[tool.poetry]'
|
|
|
18
17
|
_UV_TOOL_SECTION = '[tool.uv]'
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
def _indicates_plain_pip(pyproject_content:
|
|
20
|
+
def _indicates_plain_pip(pyproject_content: str | None) -> bool:
|
|
22
21
|
"""Return True if pyproject.toml content signals a plain-pip project (no Poetry, no uv)."""
|
|
23
22
|
if not pyproject_content:
|
|
24
23
|
return False
|
|
@@ -44,7 +43,7 @@ class RestorePipDependencies(BaseRestoreDependencies):
|
|
|
44
43
|
|
|
45
44
|
return _indicates_plain_pip(document.content)
|
|
46
45
|
|
|
47
|
-
def try_restore_dependencies(self, document: Document) ->
|
|
46
|
+
def try_restore_dependencies(self, document: Document) -> Document | None:
|
|
48
47
|
manifest_dir = self.get_manifest_dir(document)
|
|
49
48
|
lockfile_path = Path(manifest_dir) / PIP_LOCK_FILE_NAME if manifest_dir else None
|
|
50
49
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
import typer
|
|
5
4
|
|
|
@@ -21,7 +20,7 @@ class RestorePipenvDependencies(BaseRestoreDependencies):
|
|
|
21
20
|
def is_project(self, document: Document) -> bool:
|
|
22
21
|
return Path(document.path).name == PIPENV_MANIFEST_FILE_NAME
|
|
23
22
|
|
|
24
|
-
def try_restore_dependencies(self, document: Document) ->
|
|
23
|
+
def try_restore_dependencies(self, document: Document) -> Document | None:
|
|
25
24
|
manifest_dir = self.get_manifest_dir(document)
|
|
26
25
|
lockfile_path = Path(manifest_dir) / PIPENV_LOCK_FILE_NAME if manifest_dir else None
|
|
27
26
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
import typer
|
|
5
4
|
|
|
@@ -17,7 +16,7 @@ POETRY_LOCK_FILE_NAME = 'poetry.lock'
|
|
|
17
16
|
_POETRY_TOOL_SECTION = '[tool.poetry]'
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
def _indicates_poetry(pyproject_content:
|
|
19
|
+
def _indicates_poetry(pyproject_content: str | None) -> bool:
|
|
21
20
|
"""Return True if pyproject.toml content signals that this project uses Poetry."""
|
|
22
21
|
if not pyproject_content:
|
|
23
22
|
return False
|
|
@@ -38,7 +37,7 @@ class RestorePoetryDependencies(BaseRestoreDependencies):
|
|
|
38
37
|
|
|
39
38
|
return _indicates_poetry(document.content)
|
|
40
39
|
|
|
41
|
-
def try_restore_dependencies(self, document: Document) ->
|
|
40
|
+
def try_restore_dependencies(self, document: Document) -> Document | None:
|
|
42
41
|
manifest_dir = self.get_manifest_dir(document)
|
|
43
42
|
lockfile_path = Path(manifest_dir) / POETRY_LOCK_FILE_NAME if manifest_dir else None
|
|
44
43
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
import typer
|
|
5
4
|
|
|
@@ -16,7 +15,7 @@ UV_LOCK_FILE_NAME = 'uv.lock'
|
|
|
16
15
|
_UV_TOOL_SECTION = '[tool.uv]'
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
def _indicates_uv(pyproject_content:
|
|
18
|
+
def _indicates_uv(pyproject_content: str | None) -> bool:
|
|
20
19
|
"""Return True if pyproject.toml content signals that this project uses UV."""
|
|
21
20
|
if not pyproject_content:
|
|
22
21
|
return False
|
|
@@ -37,7 +36,7 @@ class RestoreUvDependencies(BaseRestoreDependencies):
|
|
|
37
36
|
|
|
38
37
|
return _indicates_uv(document.content)
|
|
39
38
|
|
|
40
|
-
def try_restore_dependencies(self, document: Document) ->
|
|
39
|
+
def try_restore_dependencies(self, document: Document) -> Document | None:
|
|
41
40
|
manifest_dir = self.get_manifest_dir(document)
|
|
42
41
|
lockfile_path = Path(manifest_dir) / UV_LOCK_FILE_NAME if manifest_dir else None
|
|
43
42
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
import typer
|
|
5
5
|
|
|
@@ -39,7 +39,7 @@ logger = get_logger('SCA File Collector')
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def _add_ecosystem_related_files_if_exists(
|
|
42
|
-
documents: list[Document], repo:
|
|
42
|
+
documents: list[Document], repo: 'Repo | None' = None, commit_rev: str | None = None
|
|
43
43
|
) -> None:
|
|
44
44
|
documents_to_add: list[Document] = []
|
|
45
45
|
for doc in documents:
|
|
@@ -81,7 +81,7 @@ def perform_sca_pre_commit_scan_actions(
|
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def _get_doc_ecosystem_related_project_files(
|
|
84
|
-
doc: Document, documents: list[Document], ecosystem: str, commit_rev:
|
|
84
|
+
doc: Document, documents: list[Document], ecosystem: str, commit_rev: str | None, repo: 'Repo | None'
|
|
85
85
|
) -> list[Document]:
|
|
86
86
|
documents_to_add: list[Document] = []
|
|
87
87
|
for ecosystem_project_file in consts.PROJECT_FILES_BY_ECOSYSTEM_MAP.get(ecosystem):
|
|
@@ -102,7 +102,7 @@ def _is_project_file_exists_in_documents(documents: list[Document], file: str) -
|
|
|
102
102
|
return any(doc for doc in documents if file == doc.path)
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
def _get_project_file_ecosystem(document: Document) ->
|
|
105
|
+
def _get_project_file_ecosystem(document: Document) -> str | None:
|
|
106
106
|
for ecosystem, project_files in consts.PROJECT_FILES_BY_ECOSYSTEM_MAP.items():
|
|
107
107
|
for project_file in project_files:
|
|
108
108
|
if document.path.endswith(project_file):
|
|
@@ -118,7 +118,7 @@ def _try_restore_dependencies(
|
|
|
118
118
|
ctx: typer.Context,
|
|
119
119
|
restore_dependencies: 'BaseRestoreDependencies',
|
|
120
120
|
document: Document,
|
|
121
|
-
) ->
|
|
121
|
+
) -> Document | None:
|
|
122
122
|
if not restore_dependencies.is_project(document):
|
|
123
123
|
return None
|
|
124
124
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import timeit
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Optional
|
|
4
3
|
|
|
5
4
|
from cycode.cli import consts
|
|
6
5
|
from cycode.cli.exceptions import custom_exceptions
|
|
@@ -20,7 +19,7 @@ def _validate_zip_file_size(scan_type: str, zip_file_size: int) -> None:
|
|
|
20
19
|
def zip_documents(
|
|
21
20
|
scan_type: str,
|
|
22
21
|
documents: list[Document],
|
|
23
|
-
zip_file:
|
|
22
|
+
zip_file: InMemoryZip | None = None,
|
|
24
23
|
) -> InMemoryZip:
|
|
25
24
|
if zip_file is None:
|
|
26
25
|
zip_file = InMemoryZip()
|
cycode/cli/models.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import NamedTuple
|
|
2
|
+
from typing import NamedTuple
|
|
3
3
|
|
|
4
4
|
from cycode.cyclient.models import Detection
|
|
5
5
|
|
|
@@ -10,8 +10,8 @@ class Document:
|
|
|
10
10
|
path: str,
|
|
11
11
|
content: str,
|
|
12
12
|
is_git_diff_format: bool = False,
|
|
13
|
-
unique_id:
|
|
14
|
-
absolute_path:
|
|
13
|
+
unique_id: str | None = None,
|
|
14
|
+
absolute_path: str | None = None,
|
|
15
15
|
) -> None:
|
|
16
16
|
self.path = path
|
|
17
17
|
self.content = content
|
|
@@ -48,25 +48,25 @@ CliErrors = dict[type[BaseException], CliError]
|
|
|
48
48
|
class CliResult(NamedTuple):
|
|
49
49
|
success: bool
|
|
50
50
|
message: str
|
|
51
|
-
data:
|
|
51
|
+
data: dict[str, any] | None = None
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class LocalScanResult(NamedTuple):
|
|
55
55
|
scan_id: str
|
|
56
|
-
report_url:
|
|
56
|
+
report_url: str | None
|
|
57
57
|
document_detections: list[DocumentDetections]
|
|
58
58
|
issue_detected: bool
|
|
59
59
|
detections_count: int
|
|
60
60
|
relevant_detections_count: int
|
|
61
|
-
verdict:
|
|
61
|
+
verdict: str | None = None
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
@dataclass
|
|
65
65
|
class ResourceChange:
|
|
66
|
-
module_address:
|
|
66
|
+
module_address: str | None
|
|
67
67
|
resource_type: str
|
|
68
68
|
name: str
|
|
69
|
-
index:
|
|
69
|
+
index: int | None
|
|
70
70
|
actions: list[str]
|
|
71
71
|
values: dict[str, str]
|
|
72
72
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import io
|
|
2
|
-
from typing import TYPE_CHECKING, ClassVar
|
|
2
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
3
3
|
|
|
4
4
|
import typer
|
|
5
5
|
from rich.console import Console
|
|
@@ -35,18 +35,18 @@ class ConsolePrinter:
|
|
|
35
35
|
def __init__(
|
|
36
36
|
self,
|
|
37
37
|
ctx: typer.Context,
|
|
38
|
-
console_override:
|
|
39
|
-
console_err_override:
|
|
40
|
-
output_type_override:
|
|
38
|
+
console_override: 'Console | None' = None,
|
|
39
|
+
console_err_override: 'Console | None' = None,
|
|
40
|
+
output_type_override: str | None = None,
|
|
41
41
|
) -> None:
|
|
42
42
|
self.ctx = ctx
|
|
43
43
|
self.console = console_override or console
|
|
44
44
|
self.console_err = console_err_override or console_err
|
|
45
45
|
self.output_type = output_type_override or self.ctx.obj.get('output')
|
|
46
46
|
|
|
47
|
-
self.export_type:
|
|
48
|
-
self.export_file:
|
|
49
|
-
self.console_record:
|
|
47
|
+
self.export_type: str | None = None
|
|
48
|
+
self.export_file: Path | None = None
|
|
49
|
+
self.console_record: ConsolePrinter | None = None
|
|
50
50
|
|
|
51
51
|
@property
|
|
52
52
|
def scan_type(self) -> str:
|
|
@@ -87,7 +87,7 @@ class ConsolePrinter:
|
|
|
87
87
|
def print_scan_results(
|
|
88
88
|
self,
|
|
89
89
|
local_scan_results: list['LocalScanResult'],
|
|
90
|
-
errors:
|
|
90
|
+
errors: dict[str, 'CliError'] | None = None,
|
|
91
91
|
) -> None:
|
|
92
92
|
if self.console_record:
|
|
93
93
|
self.console_record.print_scan_results(local_scan_results, errors)
|
|
@@ -103,7 +103,7 @@ class ConsolePrinter:
|
|
|
103
103
|
self.console_record.print_error(error)
|
|
104
104
|
self.printer.print_error(error)
|
|
105
105
|
|
|
106
|
-
def print_exception(self, e:
|
|
106
|
+
def print_exception(self, e: BaseException | None = None, force_print: bool = False) -> None:
|
|
107
107
|
"""Print traceback message in stderr if verbose mode is set."""
|
|
108
108
|
if force_print or self.ctx.obj.get('verbose', False):
|
|
109
109
|
if self.console_record:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from cycode.cli.models import CliError, CliResult
|
|
5
5
|
from cycode.cli.printers.printer_base import PrinterBase
|
|
@@ -21,7 +21,7 @@ class JsonPrinter(PrinterBase):
|
|
|
21
21
|
self.console.print_json(self.get_data_json(result))
|
|
22
22
|
|
|
23
23
|
def print_scan_results(
|
|
24
|
-
self, local_scan_results: list['LocalScanResult'], errors:
|
|
24
|
+
self, local_scan_results: list['LocalScanResult'], errors: dict[str, 'CliError'] | None = None
|
|
25
25
|
) -> None:
|
|
26
26
|
scan_ids = []
|
|
27
27
|
report_urls = []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
from collections import defaultdict
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
|
|
@@ -51,7 +51,7 @@ class PrinterBase(ABC):
|
|
|
51
51
|
|
|
52
52
|
@abstractmethod
|
|
53
53
|
def print_scan_results(
|
|
54
|
-
self, local_scan_results: list['LocalScanResult'], errors:
|
|
54
|
+
self, local_scan_results: list['LocalScanResult'], errors: dict[str, 'CliError'] | None = None
|
|
55
55
|
) -> None:
|
|
56
56
|
pass
|
|
57
57
|
|
|
@@ -63,7 +63,7 @@ class PrinterBase(ABC):
|
|
|
63
63
|
def print_error(self, error: CliError) -> None:
|
|
64
64
|
pass
|
|
65
65
|
|
|
66
|
-
def print_exception(self, e:
|
|
66
|
+
def print_exception(self, e: BaseException | None = None) -> None:
|
|
67
67
|
"""We are printing it in stderr so, we don't care about supporting JSON and TABLE outputs.
|
|
68
68
|
|
|
69
69
|
Note:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
from rich.console import Group
|
|
4
4
|
from rich.panel import Panel
|
|
@@ -26,7 +26,7 @@ class RichPrinter(TextPrinter):
|
|
|
26
26
|
MAX_PATH_LENGTH = 60
|
|
27
27
|
|
|
28
28
|
def print_scan_results(
|
|
29
|
-
self, local_scan_results: list['LocalScanResult'], errors:
|
|
29
|
+
self, local_scan_results: list['LocalScanResult'], errors: dict[str, 'CliError'] | None = None
|
|
30
30
|
) -> None:
|
|
31
31
|
if not errors and all(result.issue_detected == 0 for result in local_scan_results):
|
|
32
32
|
self.console.print(self.NO_DETECTIONS_MESSAGE)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import urllib.parse
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from rich.markup import escape
|
|
5
5
|
from rich.table import Table as RichTable
|
|
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|
|
11
11
|
class Table:
|
|
12
12
|
"""Helper class to manage columns and their values in the right order and only if the column should be presented."""
|
|
13
13
|
|
|
14
|
-
def __init__(self, column_infos:
|
|
14
|
+
def __init__(self, column_infos: list['ColumnInfo'] | None = None) -> None:
|
|
15
15
|
self._group_separator_indexes: set[int] = set()
|
|
16
16
|
|
|
17
17
|
self._columns: dict[ColumnInfo, list[str]] = {}
|
|
@@ -26,7 +26,7 @@ class Table:
|
|
|
26
26
|
if column in self._columns:
|
|
27
27
|
self._columns[column].append(value)
|
|
28
28
|
|
|
29
|
-
def add_cell(self, column: 'ColumnInfo', value: str, color:
|
|
29
|
+
def add_cell(self, column: 'ColumnInfo', value: str, color: str | None = None) -> None:
|
|
30
30
|
if color:
|
|
31
31
|
value = f'[{color}]{value}[/]'
|
|
32
32
|
|
|
@@ -49,7 +49,7 @@ class Table:
|
|
|
49
49
|
|
|
50
50
|
def get_rows(self) -> list[str]:
|
|
51
51
|
column_values = [self._columns[column_info] for column_info in self._get_ordered_columns()]
|
|
52
|
-
return list(zip(*column_values))
|
|
52
|
+
return list(zip(*column_values, strict=False))
|
|
53
53
|
|
|
54
54
|
def get_table(self) -> 'RichTable':
|
|
55
55
|
table = RichTable(expand=True, highlight=True)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import NamedTuple
|
|
1
|
+
from typing import NamedTuple
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class ColumnInfoBuilder:
|
|
@@ -14,7 +14,7 @@ class ColumnInfoBuilder:
|
|
|
14
14
|
class ColumnInfo(NamedTuple):
|
|
15
15
|
name: str
|
|
16
16
|
index: int # Represents the order of the columns, starting from the left
|
|
17
|
-
column_opts:
|
|
17
|
+
column_opts: dict | None = None
|
|
18
18
|
|
|
19
19
|
def __hash__(self) -> int:
|
|
20
20
|
return hash((self.name, self.index))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from cycode.cli.models import CliError, CliResult
|
|
5
5
|
from cycode.cli.printers.printer_base import PrinterBase
|
|
@@ -22,7 +22,7 @@ class TablePrinterBase(PrinterBase, abc.ABC):
|
|
|
22
22
|
self.text_printer.print_error(error)
|
|
23
23
|
|
|
24
24
|
def print_scan_results(
|
|
25
|
-
self, local_scan_results: list['LocalScanResult'], errors:
|
|
25
|
+
self, local_scan_results: list['LocalScanResult'], errors: dict[str, 'CliError'] | None = None
|
|
26
26
|
) -> None:
|
|
27
27
|
if not errors and all(result.issue_detected == 0 for result in local_scan_results):
|
|
28
28
|
self.console.print(self.NO_DETECTIONS_MESSAGE)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
from cycode.cli import consts
|
|
4
4
|
from cycode.cli.cli_types import SeverityOption
|
|
@@ -32,7 +32,7 @@ class TextPrinter(PrinterBase):
|
|
|
32
32
|
self.console.print(f'[red]Error: {error.message}[/]', highlight=False)
|
|
33
33
|
|
|
34
34
|
def print_scan_results(
|
|
35
|
-
self, local_scan_results: list['LocalScanResult'], errors:
|
|
35
|
+
self, local_scan_results: list['LocalScanResult'], errors: dict[str, 'CliError'] | None = None
|
|
36
36
|
) -> None:
|
|
37
37
|
if not errors and all(result.issue_detected == 0 for result in local_scan_results):
|
|
38
38
|
self.console.print(self.NO_DETECTIONS_MESSAGE)
|
|
@@ -97,7 +97,7 @@ class TextPrinter(PrinterBase):
|
|
|
97
97
|
)
|
|
98
98
|
|
|
99
99
|
def print_report_urls_and_errors(
|
|
100
|
-
self, local_scan_results: list['LocalScanResult'], errors:
|
|
100
|
+
self, local_scan_results: list['LocalScanResult'], errors: dict[str, 'CliError'] | None = None
|
|
101
101
|
) -> None:
|
|
102
102
|
report_urls = [scan_result.report_url for scan_result in local_scan_results if scan_result.report_url]
|
|
103
103
|
|
|
@@ -110,7 +110,7 @@ class TextPrinter(PrinterBase):
|
|
|
110
110
|
self.console.print(f'- {scan_id}: ', end='')
|
|
111
111
|
self.print_error(error)
|
|
112
112
|
|
|
113
|
-
def print_report_urls(self, report_urls: list[str], aggregation_report_url:
|
|
113
|
+
def print_report_urls(self, report_urls: list[str], aggregation_report_url: str | None = None) -> None:
|
|
114
114
|
if not report_urls and not aggregation_report_url:
|
|
115
115
|
return
|
|
116
116
|
|
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
|
|
15
15
|
def _get_code_segment_start_line(detection_line: int, lines_to_display_before: int) -> int:
|
|
16
16
|
start_line = detection_line - lines_to_display_before
|
|
17
|
-
return
|
|
17
|
+
return max(start_line, 0)
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def get_detection_line(scan_type: str, detection: 'Detection') -> int:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from cycode.cli import consts
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ if TYPE_CHECKING:
|
|
|
7
7
|
from cycode.cyclient.models import Detection
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def get_cwe_cve_link(cwe_cve:
|
|
10
|
+
def get_cwe_cve_link(cwe_cve: str | None) -> str | None:
|
|
11
11
|
if not cwe_cve:
|
|
12
12
|
return None
|
|
13
13
|
|
|
@@ -63,7 +63,7 @@ def get_detection_clickable_cwe_cve(scan_type: str, detection: 'Detection') -> s
|
|
|
63
63
|
return ''
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
def get_detection_cwe_cve(scan_type: str, detection: 'Detection') ->
|
|
66
|
+
def get_detection_cwe_cve(scan_type: str, detection: 'Detection') -> str | None:
|
|
67
67
|
if scan_type == consts.SCA_SCAN_TYPE:
|
|
68
68
|
return detection.detection_details.get('vulnerability_id')
|
|
69
69
|
if scan_type == consts.SAST_SCAN_TYPE:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
_MAINTAINED_CHECK_NAME = 'maintained'
|
|
4
4
|
|
|
@@ -7,15 +7,15 @@ def _get_ossf_details(detection_details: dict) -> dict:
|
|
|
7
7
|
return detection_details.get('ossf') or {}
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def get_ossf_score(detection_details: dict) ->
|
|
10
|
+
def get_ossf_score(detection_details: dict) -> Any | None:
|
|
11
11
|
return _get_ossf_details(detection_details).get('score')
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def get_ossf_report_url(detection_details: dict) ->
|
|
14
|
+
def get_ossf_report_url(detection_details: dict) -> str | None:
|
|
15
15
|
return _get_ossf_details(detection_details).get('scorecard_report_url')
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def get_maintained_score(detection_details: dict) ->
|
|
18
|
+
def get_maintained_score(detection_details: dict) -> Any | None:
|
|
19
19
|
for check in _get_ossf_details(detection_details).get('checks') or []:
|
|
20
20
|
if str(check.get('name', '')).lower() == _MAINTAINED_CHECK_NAME:
|
|
21
21
|
return check.get('score')
|