cycode 0.2.5.dev6__py3-none-any.whl → 0.2.5.dev7__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/main.py +4 -4
- cycode/cli/printers/base_printer.py +3 -1
- cycode/cli/printers/base_table_printer.py +43 -0
- cycode/cli/printers/console_printer.py +10 -6
- cycode/cli/printers/sca_table_printer.py +142 -0
- cycode/cli/printers/table.py +61 -0
- cycode/cli/printers/table_models.py +20 -0
- cycode/cli/printers/table_printer.py +101 -155
- cycode/cli/printers/text_printer.py +3 -10
- cycode/cli/utils/string_utils.py +4 -0
- {cycode-0.2.5.dev6.dist-info → cycode-0.2.5.dev7.dist-info}/METADATA +7 -7
- {cycode-0.2.5.dev6.dist-info → cycode-0.2.5.dev7.dist-info}/RECORD +15 -11
- {cycode-0.2.5.dev6.dist-info → cycode-0.2.5.dev7.dist-info}/WHEEL +0 -0
- {cycode-0.2.5.dev6.dist-info → cycode-0.2.5.dev7.dist-info}/entry_points.txt +0 -0
cycode/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.2.5.
|
|
1
|
+
__version__ = '0.2.5.dev7' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
|
cycode/cli/main.py
CHANGED
|
@@ -65,10 +65,10 @@ NO_ISSUES_STATUS_CODE = 0
|
|
|
65
65
|
@click.option('--output', default=None,
|
|
66
66
|
help="""
|
|
67
67
|
\b
|
|
68
|
-
Specify the results output (text/json),
|
|
68
|
+
Specify the results output (text/json/table),
|
|
69
69
|
the default is text
|
|
70
70
|
""",
|
|
71
|
-
type=click.Choice(['text', 'json']))
|
|
71
|
+
type=click.Choice(['text', 'json', 'table']))
|
|
72
72
|
@click.option('--severity-threshold',
|
|
73
73
|
default=None,
|
|
74
74
|
help='Show only violations at the specified level or higher (supported for SCA scan type only).',
|
|
@@ -142,8 +142,8 @@ def finalize(context: click.Context, *args, **kwargs):
|
|
|
142
142
|
@click.option(
|
|
143
143
|
'--output',
|
|
144
144
|
default='text',
|
|
145
|
-
help='Specify the output (text/json), the default is text',
|
|
146
|
-
type=click.Choice(['text', 'json'])
|
|
145
|
+
help='Specify the output (text/json/table), the default is text',
|
|
146
|
+
type=click.Choice(['text', 'json', 'table'])
|
|
147
147
|
)
|
|
148
148
|
@click.option(
|
|
149
149
|
'--user-agent',
|
|
@@ -7,7 +7,9 @@ from cycode.cli.models import DocumentDetections, CliResult, CliError
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class BasePrinter(ABC):
|
|
10
|
-
|
|
10
|
+
RED_COLOR_NAME = 'red'
|
|
11
|
+
WHITE_COLOR_NAME = 'white'
|
|
12
|
+
GREEN_COLOR_NAME = 'green'
|
|
11
13
|
|
|
12
14
|
def __init__(self, context: click.Context):
|
|
13
15
|
self.context = context
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from cycode.cli.printers.text_printer import TextPrinter
|
|
7
|
+
from cycode.cli.models import DocumentDetections, CliError, CliResult
|
|
8
|
+
from cycode.cli.printers.base_printer import BasePrinter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseTablePrinter(BasePrinter, abc.ABC):
|
|
12
|
+
def __init__(self, context: click.Context):
|
|
13
|
+
super().__init__(context)
|
|
14
|
+
self.context = context
|
|
15
|
+
self.scan_id: str = context.obj.get('scan_id')
|
|
16
|
+
self.scan_type: str = context.obj.get('scan_type')
|
|
17
|
+
self.show_secret: bool = context.obj.get('show_secret', False)
|
|
18
|
+
|
|
19
|
+
def print_result(self, result: CliResult) -> None:
|
|
20
|
+
TextPrinter(self.context).print_result(result)
|
|
21
|
+
|
|
22
|
+
def print_error(self, error: CliError) -> None:
|
|
23
|
+
TextPrinter(self.context).print_error(error)
|
|
24
|
+
|
|
25
|
+
def print_scan_results(self, results: List[DocumentDetections]):
|
|
26
|
+
click.secho(f'Scan Results: (scan_id: {self.scan_id})')
|
|
27
|
+
|
|
28
|
+
if not results:
|
|
29
|
+
click.secho('Good job! No issues were found!!! 👏👏👏', fg=self.GREEN_COLOR_NAME)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
self._print_results(results)
|
|
33
|
+
|
|
34
|
+
report_url = self.context.obj.get('report_url')
|
|
35
|
+
if report_url:
|
|
36
|
+
click.secho(f'Report URL: {report_url}')
|
|
37
|
+
|
|
38
|
+
def _is_git_repository(self) -> bool:
|
|
39
|
+
return self.context.obj.get('remote_url') is not None
|
|
40
|
+
|
|
41
|
+
@abc.abstractmethod
|
|
42
|
+
def _print_results(self, results: List[DocumentDetections]) -> None:
|
|
43
|
+
raise NotImplementedError
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from typing import List, TYPE_CHECKING
|
|
3
3
|
|
|
4
|
-
from cycode.cli.consts import SCA_SCAN_TYPE
|
|
5
4
|
from cycode.cli.exceptions.custom_exceptions import CycodeError
|
|
6
5
|
from cycode.cli.models import DocumentDetections, CliResult, CliError
|
|
7
6
|
from cycode.cli.printers.table_printer import TablePrinter
|
|
7
|
+
from cycode.cli.printers.sca_table_printer import SCATablePrinter
|
|
8
8
|
from cycode.cli.printers.json_printer import JsonPrinter
|
|
9
9
|
from cycode.cli.printers.text_printer import TextPrinter
|
|
10
10
|
|
|
@@ -16,11 +16,15 @@ class ConsolePrinter:
|
|
|
16
16
|
_AVAILABLE_PRINTERS = {
|
|
17
17
|
'text': TextPrinter,
|
|
18
18
|
'json': JsonPrinter,
|
|
19
|
-
'
|
|
19
|
+
'table': TablePrinter,
|
|
20
|
+
# overrides
|
|
21
|
+
'table_sca': SCATablePrinter,
|
|
22
|
+
'text_sca': SCATablePrinter,
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
def __init__(self, context: click.Context):
|
|
23
26
|
self.context = context
|
|
27
|
+
self.scan_type = self.context.obj.get('scan_type')
|
|
24
28
|
self.output_type = self.context.obj.get('output')
|
|
25
29
|
|
|
26
30
|
self._printer_class = self._AVAILABLE_PRINTERS.get(self.output_type)
|
|
@@ -32,11 +36,11 @@ class ConsolePrinter:
|
|
|
32
36
|
printer.print_scan_results(detections_results_list)
|
|
33
37
|
|
|
34
38
|
def _get_scan_printer(self) -> 'BasePrinter':
|
|
35
|
-
scan_type = self.context.obj.get('scan_type')
|
|
36
|
-
|
|
37
39
|
printer_class = self._printer_class
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
|
|
41
|
+
composite_printer = self._AVAILABLE_PRINTERS.get(f'{self.output_type}_{self.scan_type}')
|
|
42
|
+
if composite_printer:
|
|
43
|
+
printer_class = composite_printer
|
|
40
44
|
|
|
41
45
|
return printer_class(self.context)
|
|
42
46
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from typing import List, Dict
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from texttable import Texttable
|
|
6
|
+
|
|
7
|
+
from cycode.cli.consts import LICENSE_COMPLIANCE_POLICY_ID, PACKAGE_VULNERABILITY_POLICY_ID
|
|
8
|
+
from cycode.cli.models import DocumentDetections, Detection
|
|
9
|
+
from cycode.cli.printers.base_table_printer import BaseTablePrinter
|
|
10
|
+
|
|
11
|
+
SEVERITY_COLUMN = 'Severity'
|
|
12
|
+
LICENSE_COLUMN = 'License'
|
|
13
|
+
UPGRADE_COLUMN = 'Upgrade'
|
|
14
|
+
REPOSITORY_COLUMN = 'Repository'
|
|
15
|
+
CVE_COLUMN = 'CVE'
|
|
16
|
+
|
|
17
|
+
PREVIEW_DETECTIONS_COMMON_HEADERS = [
|
|
18
|
+
'File Path',
|
|
19
|
+
'Ecosystem',
|
|
20
|
+
'Dependency Name',
|
|
21
|
+
'Direct Dependency',
|
|
22
|
+
'Development Dependency'
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SCATablePrinter(BaseTablePrinter):
|
|
27
|
+
def _print_results(self, results: List[DocumentDetections]) -> None:
|
|
28
|
+
detections_per_detection_type_id = self._extract_detections_per_detection_type_id(results)
|
|
29
|
+
self._print_detection_per_detection_type_id(detections_per_detection_type_id)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def _extract_detections_per_detection_type_id(results: List[DocumentDetections]) -> Dict[str, List[Detection]]:
|
|
33
|
+
detections_per_detection_type_id = defaultdict(list)
|
|
34
|
+
|
|
35
|
+
for document_detection in results:
|
|
36
|
+
for detection in document_detection.detections:
|
|
37
|
+
detections_per_detection_type_id[detection.detection_type_id].append(detection)
|
|
38
|
+
|
|
39
|
+
return detections_per_detection_type_id
|
|
40
|
+
|
|
41
|
+
def _print_detection_per_detection_type_id(
|
|
42
|
+
self, detections_per_detection_type_id: Dict[str, List[Detection]]
|
|
43
|
+
) -> None:
|
|
44
|
+
for detection_type_id in detections_per_detection_type_id:
|
|
45
|
+
detections = detections_per_detection_type_id[detection_type_id]
|
|
46
|
+
headers = self._get_table_headers()
|
|
47
|
+
|
|
48
|
+
title = None
|
|
49
|
+
rows = []
|
|
50
|
+
|
|
51
|
+
if detection_type_id == PACKAGE_VULNERABILITY_POLICY_ID:
|
|
52
|
+
title = "Dependencies Vulnerabilities"
|
|
53
|
+
|
|
54
|
+
headers = [SEVERITY_COLUMN] + headers
|
|
55
|
+
headers.extend(PREVIEW_DETECTIONS_COMMON_HEADERS)
|
|
56
|
+
headers.append(CVE_COLUMN)
|
|
57
|
+
headers.append(UPGRADE_COLUMN)
|
|
58
|
+
|
|
59
|
+
for detection in detections:
|
|
60
|
+
rows.append(self._get_upgrade_package_vulnerability(detection))
|
|
61
|
+
elif detection_type_id == LICENSE_COMPLIANCE_POLICY_ID:
|
|
62
|
+
title = "License Compliance"
|
|
63
|
+
|
|
64
|
+
headers.extend(PREVIEW_DETECTIONS_COMMON_HEADERS)
|
|
65
|
+
headers.append(LICENSE_COLUMN)
|
|
66
|
+
|
|
67
|
+
for detection in detections:
|
|
68
|
+
rows.append(self._get_license(detection))
|
|
69
|
+
|
|
70
|
+
if rows:
|
|
71
|
+
self._print_table_detections(detections, headers, rows, title)
|
|
72
|
+
|
|
73
|
+
def _get_table_headers(self) -> list:
|
|
74
|
+
if self._is_git_repository():
|
|
75
|
+
return [REPOSITORY_COLUMN]
|
|
76
|
+
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
def _print_table_detections(
|
|
80
|
+
self, detections: List[Detection], headers: List[str], rows, title: str
|
|
81
|
+
) -> None:
|
|
82
|
+
self._print_summary_issues(detections, title)
|
|
83
|
+
text_table = Texttable()
|
|
84
|
+
text_table.header(headers)
|
|
85
|
+
|
|
86
|
+
self.set_table_width(headers, text_table)
|
|
87
|
+
|
|
88
|
+
for row in rows:
|
|
89
|
+
text_table.add_row(row)
|
|
90
|
+
|
|
91
|
+
click.echo(text_table.draw())
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def set_table_width(headers: List[str], text_table: Texttable) -> None:
|
|
95
|
+
header_width_size_cols = []
|
|
96
|
+
for header in headers:
|
|
97
|
+
header_len = len(header)
|
|
98
|
+
if header == CVE_COLUMN:
|
|
99
|
+
header_width_size_cols.append(header_len * 5)
|
|
100
|
+
elif header == UPGRADE_COLUMN:
|
|
101
|
+
header_width_size_cols.append(header_len * 2)
|
|
102
|
+
else:
|
|
103
|
+
header_width_size_cols.append(header_len)
|
|
104
|
+
text_table.set_cols_width(header_width_size_cols)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _print_summary_issues(detections: List, title: str) -> None:
|
|
108
|
+
click.echo(f'⛔ Found {len(detections)} issues of type: {click.style(title, bold=True)}')
|
|
109
|
+
|
|
110
|
+
def _get_common_detection_fields(self, detection: Detection) -> List[str]:
|
|
111
|
+
row = [
|
|
112
|
+
detection.detection_details.get('file_name'),
|
|
113
|
+
detection.detection_details.get('ecosystem'),
|
|
114
|
+
detection.detection_details.get('package_name'),
|
|
115
|
+
detection.detection_details.get('is_direct_dependency_str'),
|
|
116
|
+
detection.detection_details.get('is_dev_dependency_str')
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
if self._is_git_repository():
|
|
120
|
+
row = [detection.detection_details.get('repository_name')] + row
|
|
121
|
+
|
|
122
|
+
return row
|
|
123
|
+
|
|
124
|
+
def _get_upgrade_package_vulnerability(self, detection: Detection) -> List[str]:
|
|
125
|
+
alert = detection.detection_details.get('alert')
|
|
126
|
+
row = [
|
|
127
|
+
detection.detection_details.get('advisory_severity'),
|
|
128
|
+
*self._get_common_detection_fields(detection),
|
|
129
|
+
detection.detection_details.get('vulnerability_id')
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
upgrade = ''
|
|
133
|
+
if alert.get("first_patched_version"):
|
|
134
|
+
upgrade = f'{alert.get("vulnerable_requirements")} -> {alert.get("first_patched_version")}'
|
|
135
|
+
row.append(upgrade)
|
|
136
|
+
|
|
137
|
+
return row
|
|
138
|
+
|
|
139
|
+
def _get_license(self, detection: Detection) -> List[str]:
|
|
140
|
+
row = self._get_common_detection_fields(detection)
|
|
141
|
+
row.append(f'{detection.detection_details.get("license")}')
|
|
142
|
+
return row
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import List, Dict, Optional, TYPE_CHECKING
|
|
2
|
+
from texttable import Texttable
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from cycode.cli.printers.table_models import ColumnInfo, ColumnWidths
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Table:
|
|
9
|
+
"""Helper class to manage columns and their values in the right order and only if the column should be presented."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, column_infos: Optional[List['ColumnInfo']] = None):
|
|
12
|
+
self._column_widths = None
|
|
13
|
+
|
|
14
|
+
self._columns: Dict['ColumnInfo', List[str]] = dict()
|
|
15
|
+
if column_infos:
|
|
16
|
+
self._columns: Dict['ColumnInfo', List[str]] = {columns: list() for columns in column_infos}
|
|
17
|
+
|
|
18
|
+
def add(self, column: 'ColumnInfo') -> None:
|
|
19
|
+
self._columns[column] = list()
|
|
20
|
+
|
|
21
|
+
def set(self, column: 'ColumnInfo', value: str) -> None:
|
|
22
|
+
# we push values only for existing columns what were added before
|
|
23
|
+
if column in self._columns:
|
|
24
|
+
self._columns[column].append(value)
|
|
25
|
+
|
|
26
|
+
def _get_ordered_columns(self) -> List['ColumnInfo']:
|
|
27
|
+
# we are sorting columns by index to make sure that columns will be printed in the right order
|
|
28
|
+
return sorted(self._columns, key=lambda column_info: column_info.index)
|
|
29
|
+
|
|
30
|
+
def get_columns_info(self) -> List['ColumnInfo']:
|
|
31
|
+
return self._get_ordered_columns()
|
|
32
|
+
|
|
33
|
+
def get_headers(self) -> List[str]:
|
|
34
|
+
return [header.name for header in self._get_ordered_columns()]
|
|
35
|
+
|
|
36
|
+
def get_rows(self) -> List[str]:
|
|
37
|
+
column_values = [self._columns[column_info] for column_info in self._get_ordered_columns()]
|
|
38
|
+
return list(zip(*column_values))
|
|
39
|
+
|
|
40
|
+
def set_cols_width(self, column_widths: 'ColumnWidths') -> None:
|
|
41
|
+
header_width_size = []
|
|
42
|
+
for header in self.get_columns_info():
|
|
43
|
+
width_multiplier = 1
|
|
44
|
+
if header in column_widths:
|
|
45
|
+
width_multiplier = column_widths[header]
|
|
46
|
+
|
|
47
|
+
header_width_size.append(len(header.name) * width_multiplier)
|
|
48
|
+
|
|
49
|
+
self._column_widths = header_width_size
|
|
50
|
+
|
|
51
|
+
def get_table(self, max_width: int = 80) -> Texttable:
|
|
52
|
+
table = Texttable(max_width)
|
|
53
|
+
table.header(self.get_headers())
|
|
54
|
+
|
|
55
|
+
for row in self.get_rows():
|
|
56
|
+
table.add_row(row)
|
|
57
|
+
|
|
58
|
+
if self._column_widths:
|
|
59
|
+
table.set_cols_width(self._column_widths)
|
|
60
|
+
|
|
61
|
+
return table
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import NamedTuple, Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ColumnInfoBuilder:
|
|
5
|
+
_index = 0
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def build(name: str) -> 'ColumnInfo':
|
|
9
|
+
column_info = ColumnInfo(name, ColumnInfoBuilder._index)
|
|
10
|
+
ColumnInfoBuilder._index += 1
|
|
11
|
+
return column_info
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ColumnInfo(NamedTuple):
|
|
15
|
+
name: str
|
|
16
|
+
index: int # Represents the order of the columns, starting from the left
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ColumnWidths = Dict[ColumnInfo, int]
|
|
20
|
+
ColumnWidthsConfig = Dict[str, ColumnWidths]
|
|
@@ -1,170 +1,116 @@
|
|
|
1
|
-
from
|
|
2
|
-
from typing import List, Dict
|
|
1
|
+
from typing import List
|
|
3
2
|
|
|
4
3
|
import click
|
|
5
|
-
from texttable import Texttable
|
|
6
4
|
|
|
7
|
-
from cycode.cli.
|
|
8
|
-
from cycode.cli.
|
|
9
|
-
from cycode.cli.printers.
|
|
5
|
+
from cycode.cli.printers.base_table_printer import BaseTablePrinter
|
|
6
|
+
from cycode.cli.printers.table_models import ColumnInfoBuilder, ColumnWidthsConfig
|
|
7
|
+
from cycode.cli.printers.table import Table
|
|
8
|
+
from cycode.cli.utils.string_utils import obfuscate_text, get_position_in_line
|
|
9
|
+
from cycode.cli.consts import SECRET_SCAN_TYPE, INFRA_CONFIGURATION_SCAN_TYPE, SAST_SCAN_TYPE
|
|
10
|
+
from cycode.cli.models import DocumentDetections, Detection, Document
|
|
11
|
+
|
|
12
|
+
# Creation must have strict order. Represents the order of the columns in the table (from left to right)
|
|
13
|
+
ISSUE_TYPE_COLUMN = ColumnInfoBuilder.build(name='Issue Type')
|
|
14
|
+
RULE_ID_COLUMN = ColumnInfoBuilder.build(name='Rule ID')
|
|
15
|
+
FILE_PATH_COLUMN = ColumnInfoBuilder.build(name='File Path')
|
|
16
|
+
SECRET_SHA_COLUMN = ColumnInfoBuilder.build(name='Secret SHA')
|
|
17
|
+
COMMIT_SHA_COLUMN = ColumnInfoBuilder.build(name='Commit SHA')
|
|
18
|
+
LINE_NUMBER_COLUMN = ColumnInfoBuilder.build(name='Line Number')
|
|
19
|
+
COLUMN_NUMBER_COLUMN = ColumnInfoBuilder.build(name='Column Number')
|
|
20
|
+
VIOLATION_LENGTH_COLUMN = ColumnInfoBuilder.build(name='Violation Length')
|
|
21
|
+
VIOLATION_COLUMN = ColumnInfoBuilder.build(name='Violation')
|
|
22
|
+
|
|
23
|
+
COLUMN_WIDTHS_CONFIG: ColumnWidthsConfig = {
|
|
24
|
+
SECRET_SCAN_TYPE: {
|
|
25
|
+
ISSUE_TYPE_COLUMN: 2,
|
|
26
|
+
RULE_ID_COLUMN: 2,
|
|
27
|
+
FILE_PATH_COLUMN: 2,
|
|
28
|
+
SECRET_SHA_COLUMN: 2,
|
|
29
|
+
VIOLATION_COLUMN: 2,
|
|
30
|
+
},
|
|
31
|
+
INFRA_CONFIGURATION_SCAN_TYPE: {
|
|
32
|
+
ISSUE_TYPE_COLUMN: 4,
|
|
33
|
+
RULE_ID_COLUMN: 3,
|
|
34
|
+
FILE_PATH_COLUMN: 3,
|
|
35
|
+
},
|
|
36
|
+
SAST_SCAN_TYPE: {
|
|
37
|
+
ISSUE_TYPE_COLUMN: 7,
|
|
38
|
+
RULE_ID_COLUMN: 2,
|
|
39
|
+
FILE_PATH_COLUMN: 3,
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TablePrinter(BaseTablePrinter):
|
|
45
|
+
def _print_results(self, results: List[DocumentDetections]) -> None:
|
|
46
|
+
table = self._get_table()
|
|
47
|
+
if self.scan_type in COLUMN_WIDTHS_CONFIG:
|
|
48
|
+
table.set_cols_width(COLUMN_WIDTHS_CONFIG[self.scan_type])
|
|
49
|
+
|
|
50
|
+
for result in results:
|
|
51
|
+
for detection in result.detections:
|
|
52
|
+
self._enrich_table_with_values(table, detection, result.document)
|
|
53
|
+
|
|
54
|
+
click.echo(table.get_table().draw())
|
|
55
|
+
|
|
56
|
+
def _get_table(self) -> Table:
|
|
57
|
+
table = Table()
|
|
58
|
+
|
|
59
|
+
table.add(ISSUE_TYPE_COLUMN)
|
|
60
|
+
table.add(RULE_ID_COLUMN)
|
|
61
|
+
table.add(FILE_PATH_COLUMN)
|
|
62
|
+
table.add(LINE_NUMBER_COLUMN)
|
|
63
|
+
table.add(COLUMN_NUMBER_COLUMN)
|
|
10
64
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
UPGRADE_COLUMN = 'Upgrade'
|
|
14
|
-
REPOSITORY_COLUMN = 'Repository'
|
|
15
|
-
CVE_COLUMN = 'CVE'
|
|
16
|
-
|
|
17
|
-
PREVIEW_DETECTIONS_COMMON_HEADERS = [
|
|
18
|
-
'File Path',
|
|
19
|
-
'Ecosystem',
|
|
20
|
-
'Dependency Name',
|
|
21
|
-
'Direct Dependency',
|
|
22
|
-
'Development Dependency'
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TablePrinter(BasePrinter):
|
|
27
|
-
RED_COLOR_NAME = 'red'
|
|
28
|
-
WHITE_COLOR_NAME = 'white'
|
|
29
|
-
GREEN_COLOR_NAME = 'green'
|
|
30
|
-
|
|
31
|
-
def __init__(self, context: click.Context):
|
|
32
|
-
super().__init__(context)
|
|
33
|
-
self.scan_id = context.obj.get('scan_id')
|
|
34
|
-
|
|
35
|
-
def print_result(self, result: CliResult) -> None:
|
|
36
|
-
raise NotImplemented
|
|
37
|
-
|
|
38
|
-
def print_error(self, error: CliError) -> None:
|
|
39
|
-
raise NotImplemented
|
|
40
|
-
|
|
41
|
-
def print_scan_results(self, results: List[DocumentDetections]):
|
|
42
|
-
click.secho(f"Scan Results: (scan_id: {self.scan_id})")
|
|
43
|
-
|
|
44
|
-
if not results:
|
|
45
|
-
click.secho("Good job! No issues were found!!! 👏👏👏", fg=self.GREEN_COLOR_NAME)
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
detections_per_detection_type_id = self._extract_detections_per_detection_type_id(results)
|
|
49
|
-
|
|
50
|
-
self._print_detection_per_detection_type_id(detections_per_detection_type_id)
|
|
51
|
-
|
|
52
|
-
report_url = self.context.obj.get('report_url')
|
|
53
|
-
if report_url:
|
|
54
|
-
click.secho(f'Report URL: {report_url}')
|
|
65
|
+
if self._is_git_repository():
|
|
66
|
+
table.add(COMMIT_SHA_COLUMN)
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
if self.scan_type == SECRET_SCAN_TYPE:
|
|
69
|
+
table.add(SECRET_SHA_COLUMN)
|
|
70
|
+
table.add(VIOLATION_LENGTH_COLUMN)
|
|
71
|
+
table.add(VIOLATION_COLUMN)
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
for detection in document_detection.detections:
|
|
62
|
-
detections_per_detection_type_id[detection.detection_type_id].append(detection)
|
|
73
|
+
return table
|
|
63
74
|
|
|
64
|
-
|
|
75
|
+
def _enrich_table_with_values(self, table: Table, detection: Detection, document: Document) -> None:
|
|
76
|
+
self._enrich_table_with_detection_summary_values(table, detection, document)
|
|
77
|
+
self._enrich_table_with_detection_code_segment_values(table, detection, document)
|
|
65
78
|
|
|
66
|
-
def
|
|
67
|
-
self,
|
|
79
|
+
def _enrich_table_with_detection_summary_values(
|
|
80
|
+
self, table: Table, detection: Detection, document: Document
|
|
68
81
|
) -> None:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
headers.append(CVE_COLUMN)
|
|
82
|
-
headers.append(UPGRADE_COLUMN)
|
|
83
|
-
|
|
84
|
-
for detection in detections:
|
|
85
|
-
rows.append(self._get_upgrade_package_vulnerability(detection))
|
|
86
|
-
elif detection_type_id == LICENSE_COMPLIANCE_POLICY_ID:
|
|
87
|
-
title = "License Compliance"
|
|
88
|
-
|
|
89
|
-
headers.extend(PREVIEW_DETECTIONS_COMMON_HEADERS)
|
|
90
|
-
headers.append(LICENSE_COLUMN)
|
|
91
|
-
|
|
92
|
-
for detection in detections:
|
|
93
|
-
rows.append(self._get_license(detection))
|
|
94
|
-
|
|
95
|
-
if rows:
|
|
96
|
-
self._print_table_detections(detections, headers, rows, title)
|
|
97
|
-
|
|
98
|
-
def _get_table_headers(self) -> list:
|
|
99
|
-
if self._is_git_repository():
|
|
100
|
-
return [REPOSITORY_COLUMN]
|
|
101
|
-
|
|
102
|
-
return []
|
|
103
|
-
|
|
104
|
-
def _print_table_detections(
|
|
105
|
-
self, detections: List[Detection], headers: List[str], rows, title: str
|
|
82
|
+
issue_type = detection.message
|
|
83
|
+
if self.scan_type == SECRET_SCAN_TYPE:
|
|
84
|
+
issue_type = detection.type
|
|
85
|
+
|
|
86
|
+
table.set(ISSUE_TYPE_COLUMN, issue_type)
|
|
87
|
+
table.set(RULE_ID_COLUMN, detection.detection_rule_id)
|
|
88
|
+
table.set(FILE_PATH_COLUMN, click.format_filename(document.path))
|
|
89
|
+
table.set(SECRET_SHA_COLUMN, detection.detection_details.get('sha512', ''))
|
|
90
|
+
table.set(COMMIT_SHA_COLUMN, detection.detection_details.get('commit_id', ''))
|
|
91
|
+
|
|
92
|
+
def _enrich_table_with_detection_code_segment_values(
|
|
93
|
+
self, table: Table, detection: Detection, document: Document
|
|
106
94
|
) -> None:
|
|
107
|
-
|
|
108
|
-
text_table = Texttable()
|
|
109
|
-
text_table.header(headers)
|
|
110
|
-
|
|
111
|
-
self.set_table_width(headers, text_table)
|
|
112
|
-
|
|
113
|
-
for row in rows:
|
|
114
|
-
text_table.add_row(row)
|
|
115
|
-
|
|
116
|
-
click.echo(text_table.draw())
|
|
117
|
-
|
|
118
|
-
@staticmethod
|
|
119
|
-
def set_table_width(headers: List[str], text_table: Texttable) -> None:
|
|
120
|
-
header_width_size_cols = []
|
|
121
|
-
for header in headers:
|
|
122
|
-
header_len = len(header)
|
|
123
|
-
if header == CVE_COLUMN:
|
|
124
|
-
header_width_size_cols.append(header_len * 5)
|
|
125
|
-
elif header == UPGRADE_COLUMN:
|
|
126
|
-
header_width_size_cols.append(header_len * 2)
|
|
127
|
-
else:
|
|
128
|
-
header_width_size_cols.append(header_len)
|
|
129
|
-
text_table.set_cols_width(header_width_size_cols)
|
|
130
|
-
|
|
131
|
-
@staticmethod
|
|
132
|
-
def _print_summary_issues(detections: List, title: str) -> None:
|
|
133
|
-
click.echo(f'⛔ Found {len(detections)} issues of type: {click.style(title, bold=True)}')
|
|
134
|
-
|
|
135
|
-
def _get_common_detection_fields(self, detection: Detection) -> List[str]:
|
|
136
|
-
row = [
|
|
137
|
-
detection.detection_details.get('file_name'),
|
|
138
|
-
detection.detection_details.get('ecosystem'),
|
|
139
|
-
detection.detection_details.get('package_name'),
|
|
140
|
-
detection.detection_details.get('is_direct_dependency_str'),
|
|
141
|
-
detection.detection_details.get('is_dev_dependency_str')
|
|
142
|
-
]
|
|
143
|
-
|
|
144
|
-
if self._is_git_repository():
|
|
145
|
-
row = [detection.detection_details.get('repository_name')] + row
|
|
146
|
-
|
|
147
|
-
return row
|
|
95
|
+
detection_details = detection.detection_details
|
|
148
96
|
|
|
149
|
-
|
|
150
|
-
|
|
97
|
+
detection_line = detection_details.get('line_in_file', -1)
|
|
98
|
+
if self.scan_type == SECRET_SCAN_TYPE:
|
|
99
|
+
detection_line = detection_details.get('line', -1)
|
|
151
100
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
row = [
|
|
155
|
-
detection.detection_details.get('advisory_severity'),
|
|
156
|
-
*self._get_common_detection_fields(detection),
|
|
157
|
-
detection.detection_details.get('vulnerability_id')
|
|
158
|
-
]
|
|
101
|
+
detection_column = get_position_in_line(document.content, detection_details.get('start_position', -1))
|
|
102
|
+
violation_length = detection_details.get('length', -1)
|
|
159
103
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
104
|
+
violation = ''
|
|
105
|
+
file_content_lines = document.content.splitlines()
|
|
106
|
+
if detection_line < len(file_content_lines):
|
|
107
|
+
line = file_content_lines[detection_line]
|
|
108
|
+
violation = line[detection_column: detection_column + violation_length]
|
|
164
109
|
|
|
165
|
-
|
|
110
|
+
if not self.show_secret:
|
|
111
|
+
violation = obfuscate_text(violation)
|
|
166
112
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
113
|
+
table.set(LINE_NUMBER_COLUMN, str(detection_line))
|
|
114
|
+
table.set(COLUMN_NUMBER_COLUMN, str(detection_column))
|
|
115
|
+
table.set(VIOLATION_LENGTH_COLUMN, f'{violation_length} chars')
|
|
116
|
+
table.set(VIOLATION_COLUMN, violation)
|
|
@@ -7,14 +7,10 @@ from cycode.cli.printers.base_printer import BasePrinter
|
|
|
7
7
|
from cycode.cli.models import DocumentDetections, Detection, Document, CliResult, CliError
|
|
8
8
|
from cycode.cli.config import config
|
|
9
9
|
from cycode.cli.consts import SECRET_SCAN_TYPE, COMMIT_RANGE_BASED_COMMAND_SCAN_TYPES
|
|
10
|
-
from cycode.cli.utils.string_utils import obfuscate_text
|
|
10
|
+
from cycode.cli.utils.string_utils import obfuscate_text, get_position_in_line
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class TextPrinter(BasePrinter):
|
|
14
|
-
RED_COLOR_NAME = 'red'
|
|
15
|
-
WHITE_COLOR_NAME = 'white'
|
|
16
|
-
GREEN_COLOR_NAME = 'green'
|
|
17
|
-
|
|
18
14
|
def __init__(self, context: click.Context):
|
|
19
15
|
super().__init__(context)
|
|
20
16
|
self.scan_id: str = context.obj.get('scan_id')
|
|
@@ -132,9 +128,6 @@ class TextPrinter(BasePrinter):
|
|
|
132
128
|
|
|
133
129
|
return self.WHITE_COLOR_NAME
|
|
134
130
|
|
|
135
|
-
def _get_position_in_line(self, text: str, position: int) -> int:
|
|
136
|
-
return position - text.rfind('\n', 0, position) - 1
|
|
137
|
-
|
|
138
131
|
def _get_line_number_style(self, line_number: int):
|
|
139
132
|
return f'{click.style(str(line_number), fg=self.WHITE_COLOR_NAME, bold=False)} ' \
|
|
140
133
|
f'{click.style("|", fg=self.RED_COLOR_NAME, bold=False)}'
|
|
@@ -158,7 +151,7 @@ class TextPrinter(BasePrinter):
|
|
|
158
151
|
file_content = document.content
|
|
159
152
|
file_lines = file_content.splitlines()
|
|
160
153
|
start_line = self._get_code_segment_start_line(detection_line, code_segment_size)
|
|
161
|
-
detection_position_in_line =
|
|
154
|
+
detection_position_in_line = get_position_in_line(file_content, detection_position)
|
|
162
155
|
|
|
163
156
|
click.echo()
|
|
164
157
|
for i in range(code_segment_size):
|
|
@@ -182,7 +175,7 @@ class TextPrinter(BasePrinter):
|
|
|
182
175
|
git_diff_content = document.content
|
|
183
176
|
git_diff_lines = git_diff_content.splitlines()
|
|
184
177
|
detection_line = git_diff_lines[detection_line_number]
|
|
185
|
-
detection_position_in_line =
|
|
178
|
+
detection_position_in_line = get_position_in_line(git_diff_content, detection_position)
|
|
186
179
|
|
|
187
180
|
click.echo()
|
|
188
181
|
self._print_detection_line(document, detection_line, detection_line_number_in_original_file,
|
cycode/cli/utils/string_utils.py
CHANGED
|
@@ -43,3 +43,7 @@ def generate_random_string(string_len: int):
|
|
|
43
43
|
# letters, digits, and symbols
|
|
44
44
|
characters = string.ascii_letters + string.digits + string.punctuation
|
|
45
45
|
return ''.join(random.choice(characters) for _ in range(string_len))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_position_in_line(text: str, position: int) -> int:
|
|
49
|
+
return position - text.rfind('\n', 0, position) - 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cycode
|
|
3
|
-
Version: 0.2.5.
|
|
3
|
+
Version: 0.2.5.dev7
|
|
4
4
|
Summary: Perform secrets/iac scans for your sources using Cycode's engine
|
|
5
5
|
Home-page: https://github.com/cycodehq-public/cycode-cli
|
|
6
6
|
License: MIT
|
|
@@ -248,12 +248,12 @@ repos:
|
|
|
248
248
|
|
|
249
249
|
The following are the options and commands available with the Cycode CLI application:
|
|
250
250
|
|
|
251
|
-
| Option
|
|
252
|
-
|
|
253
|
-
| `--output [text\|json]` | Specify the output (`text`/`json`). The default is `text` |
|
|
254
|
-
| `-v`, `--verbose`
|
|
255
|
-
| `--version`
|
|
256
|
-
| `--help`
|
|
251
|
+
| Option | Description |
|
|
252
|
+
|--------------------------------|-------------------------------------------------------------------|
|
|
253
|
+
| `--output [text\|json\|table]` | Specify the output (`text`/`json`/`table`). The default is `text` |
|
|
254
|
+
| `-v`, `--verbose` | Show detailed logs |
|
|
255
|
+
| `--version` | Show the version and exit. |
|
|
256
|
+
| `--help` | Show options for given command. |
|
|
257
257
|
|
|
258
258
|
| Command | Description |
|
|
259
259
|
|-------------------------------------|-------------|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
cycode/__init__.py,sha256=
|
|
1
|
+
cycode/__init__.py,sha256=1G_PZu1pEa6ExgHfDJrd83gP_4TQvL4xyZUMOLQuQ2E,115
|
|
2
2
|
cycode/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
cycode/cli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
cycode/cli/auth/auth_command.py,sha256=YG3Y0gjIXcyq9k3PNIqPHoMYnMuxG_07kCKVtaXE0p8,2813
|
|
@@ -16,14 +16,18 @@ cycode/cli/helpers/maven/base_restore_maven_dependencies.py,sha256=kcj6HmwmFbG6I
|
|
|
16
16
|
cycode/cli/helpers/maven/restore_gradle_dependencies.py,sha256=BnJcFdkd642iBL48MJ7n7r8LmSCBWCHLeaReOt-yzhc,992
|
|
17
17
|
cycode/cli/helpers/maven/restore_maven_dependencies.py,sha256=vPVb2sfPRTL3GevAe3ibqR-eHwQOP34mslhg_fSUxTg,2993
|
|
18
18
|
cycode/cli/helpers/sca_code_scanner.py,sha256=G7NSNEIeyjFCYPynbae4WOtf7PB4OAiis-kjufNYd8c,5835
|
|
19
|
-
cycode/cli/main.py,sha256=
|
|
19
|
+
cycode/cli/main.py,sha256=jMhKv6PdZYXMFY1GOiwCAar6VKIBPNfQ-J7jCaBjRfs,7243
|
|
20
20
|
cycode/cli/models.py,sha256=-4tVdQXmT4XqIbrXPkHs5FI6FsRzeDsFasFkU_YfZLY,1332
|
|
21
21
|
cycode/cli/printers/__init__.py,sha256=ALwAXSZy2lNXWC3NfCIxf8K0F6eFrbZa9PLZwPINi5E,93
|
|
22
|
-
cycode/cli/printers/base_printer.py,sha256=
|
|
23
|
-
cycode/cli/printers/
|
|
22
|
+
cycode/cli/printers/base_printer.py,sha256=YUNT6uJsPLqcjJmCrCzOELSm1ONgIAHbHIdjfTvgBwY,626
|
|
23
|
+
cycode/cli/printers/base_table_printer.py,sha256=TX2zDL4cRxNUoGRsc-DsZRM3MzBQc8pBKKn2IPx6lCk,1483
|
|
24
|
+
cycode/cli/printers/console_printer.py,sha256=A_T8VQTqwzCMKbwDQmerI8y9W9itAmH5ERDJ8KloVwg,1891
|
|
24
25
|
cycode/cli/printers/json_printer.py,sha256=Gx-aCEGAnStZbRhMZD6nCYfAaRrWImoEpu8XzgYardY,1537
|
|
25
|
-
cycode/cli/printers/
|
|
26
|
-
cycode/cli/printers/
|
|
26
|
+
cycode/cli/printers/sca_table_printer.py,sha256=mP0GGAKUolaXYCyx63R-_rV2guv7PxPAATZBAkNlfns,5272
|
|
27
|
+
cycode/cli/printers/table.py,sha256=KJnDYLUH6PJJdolH8ayky6oVbV1OMFcGOe2RSXJSEQY,2277
|
|
28
|
+
cycode/cli/printers/table_models.py,sha256=DiGwXKZMcgRRyD27jWZjr0nrJ1O9-1gVjnQ_u6NUazY,477
|
|
29
|
+
cycode/cli/printers/table_printer.py,sha256=job1W61U1UIvJ6dAME1idXfJ9oB8g87e5L3EW9WCgFU,4701
|
|
30
|
+
cycode/cli/printers/text_printer.py,sha256=SrcBrgUag93ByHJ9lwpM4QhRwXuzqm4TiNronS4E8NY,8955
|
|
27
31
|
cycode/cli/user_settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
32
|
cycode/cli/user_settings/base_file_manager.py,sha256=4oFLLv7CwaqaMpz6XRA-cz5aG3DES6Mq_7RUWY_b3aY,533
|
|
29
33
|
cycode/cli/user_settings/config_file_manager.py,sha256=1YlWBk30Ep5f3ysQjLYdzWuKuLyiXPC4xbw4ndQoGvw,4821
|
|
@@ -34,7 +38,7 @@ cycode/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
34
38
|
cycode/cli/utils/path_utils.py,sha256=wzUxLKLfjI8vnmDTN-VOjWpvREkMhgDRCOXZu_HbNpc,2162
|
|
35
39
|
cycode/cli/utils/scan_utils.py,sha256=Q-XqLXxvyC0Z2kwZjjw6riRRnovQaYFyhlAhRyr_NDA,195
|
|
36
40
|
cycode/cli/utils/shell_executor.py,sha256=vRA7YqFLFcdWgGToZ1ek2zgQX8p-29oxWpAJt0wqWC4,941
|
|
37
|
-
cycode/cli/utils/string_utils.py,sha256=
|
|
41
|
+
cycode/cli/utils/string_utils.py,sha256=wZh0SEiYTtiqJeYqjKnAmCV_TYglsSWkdLbCrO4PtEs,1346
|
|
38
42
|
cycode/cli/utils/task_timer.py,sha256=r1SLglU3RXHZn2AJuTKY5sFIZIWQPyXwAzjHmrpxxHQ,2700
|
|
39
43
|
cycode/cli/utils/yaml_utils.py,sha256=-mEFULELw-GpR2g0AdafLshHdmM76p4k0FlT7t6-bAI,815
|
|
40
44
|
cycode/cli/zip_file.py,sha256=Uao-iTJMDb039jU04F7tl1lS5_PmWl1Ama9IySRzFNI,929
|
|
@@ -53,7 +57,7 @@ cycode/cyclient/scan_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
|
53
57
|
cycode/cyclient/scan_config/scan_config_base.py,sha256=iX7S-XSSZfkyFfUzGZPc48PkmNNr_D-9rzwlT9B64DM,1090
|
|
54
58
|
cycode/cyclient/scan_config/scan_config_creator.py,sha256=xwVVPCaWVYUNArGAvQ9nwWMD65Hoqi4A5ZMncgiZ1JY,1021
|
|
55
59
|
cycode/cyclient/utils.py,sha256=gYVYaEwXKH01uQqXPmrBhqmCv0sVVvjvGXGbdLxk1jE,197
|
|
56
|
-
cycode-0.2.5.
|
|
57
|
-
cycode-0.2.5.
|
|
58
|
-
cycode-0.2.5.
|
|
59
|
-
cycode-0.2.5.
|
|
60
|
+
cycode-0.2.5.dev7.dist-info/METADATA,sha256=n5NZzsZpWW4Pn0WglYTV3sOzpHzUzK_-OsNchmk99iU,33934
|
|
61
|
+
cycode-0.2.5.dev7.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
|
62
|
+
cycode-0.2.5.dev7.dist-info/entry_points.txt,sha256=GKZlS6LtUdABDPd7-o9bwNSI5gYQnyA3qGrFFQKt3Vc,51
|
|
63
|
+
cycode-0.2.5.dev7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|