Skip to content

Commit 34b86d5

Browse files
MarshalXelsapet
andauthored
CM-42904 - Add --by-cve option for cycode ignore command (#274)
Co-authored-by: elsapet <elizabeth@cycode.com>
1 parent aa534b3 commit 34b86d5

File tree

4 files changed

+93
-51
lines changed

4 files changed

+93
-51
lines changed

cycode/cli/commands/ignore/ignore_command.py

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
import os
21
import re
2+
from typing import Optional
33

44
import click
55

66
from cycode.cli import consts
77
from cycode.cli.config import config, configuration_manager
88
from cycode.cli.sentry import add_breadcrumb
9-
from cycode.cli.utils.path_utils import get_absolute_path
9+
from cycode.cli.utils.path_utils import get_absolute_path, is_path_exists
1010
from cycode.cli.utils.string_utils import hash_string_to_sha256
1111
from cycode.cyclient import logger
1212

1313

14-
def _is_path_to_ignore_exists(path: str) -> bool:
15-
return os.path.exists(path)
16-
17-
1814
def _is_package_pattern_valid(package: str) -> bool:
1915
return re.search('^[^@]+@[^@]+$', package) is not None
2016

@@ -47,10 +43,16 @@ def _is_package_pattern_valid(package: str) -> bool:
4743
required=False,
4844
help='Ignore scanning a specific package version while running an SCA scan. Expected pattern: name@version.',
4945
)
46+
@click.option(
47+
'--by-cve',
48+
type=click.STRING,
49+
required=False,
50+
help='Ignore scanning a specific CVE while running an SCA scan. Expected pattern: CVE-YYYY-NNN.',
51+
)
5052
@click.option(
5153
'--scan-type',
5254
'-t',
53-
default='secret',
55+
default=consts.SECRET_SCAN_TYPE,
5456
help='Specify the type of scan you wish to execute (the default is Secrets).',
5557
type=click.Choice(config['scans']['supported_scans']),
5658
required=False,
@@ -64,40 +66,68 @@ def _is_package_pattern_valid(package: str) -> bool:
6466
required=False,
6567
help='Add an ignore rule to the global CLI config.',
6668
)
67-
def ignore_command(
68-
by_value: str, by_sha: str, by_path: str, by_rule: str, by_package: str, scan_type: str, is_global: bool
69+
def ignore_command( # noqa: C901
70+
by_value: Optional[str],
71+
by_sha: Optional[str],
72+
by_path: Optional[str],
73+
by_rule: Optional[str],
74+
by_package: Optional[str],
75+
by_cve: Optional[str],
76+
scan_type: str = consts.SECRET_SCAN_TYPE,
77+
is_global: bool = False,
6978
) -> None:
7079
"""Ignores a specific value, path or rule ID."""
7180
add_breadcrumb('ignore')
7281

73-
if not by_value and not by_sha and not by_path and not by_rule and not by_package:
74-
raise click.ClickException('ignore by type is missing')
82+
all_by_values = [by_value, by_sha, by_path, by_rule, by_package, by_cve]
83+
if all(by is None for by in all_by_values):
84+
raise click.ClickException('Ignore by type is missing')
85+
if len([by for by in all_by_values if by is not None]) != 1:
86+
raise click.ClickException('You must specify only one ignore by type')
7587

7688
if any(by is not None for by in [by_value, by_sha]) and scan_type != consts.SECRET_SCAN_TYPE:
77-
raise click.ClickException('this exclude is supported only for secret scan type')
89+
raise click.ClickException('This exclude is supported only for Secret scan type')
90+
if (by_cve or by_package) and scan_type != consts.SCA_SCAN_TYPE:
91+
raise click.ClickException('This exclude is supported only for SCA scan type')
92+
93+
# only one of the by values must be set
94+
# at least one of the by values must be set
95+
exclusion_type = exclusion_value = None
7896

79-
if by_value is not None:
97+
if by_value:
8098
exclusion_type = consts.EXCLUSIONS_BY_VALUE_SECTION_NAME
8199
exclusion_value = hash_string_to_sha256(by_value)
82-
elif by_sha is not None:
100+
101+
if by_sha:
83102
exclusion_type = consts.EXCLUSIONS_BY_SHA_SECTION_NAME
84103
exclusion_value = by_sha
85-
elif by_path is not None:
104+
105+
if by_path:
86106
absolute_path = get_absolute_path(by_path)
87-
if not _is_path_to_ignore_exists(absolute_path):
88-
raise click.ClickException('the provided path to ignore by is not exist')
107+
if not is_path_exists(absolute_path):
108+
raise click.ClickException('The provided path to ignore by does not exist')
109+
89110
exclusion_type = consts.EXCLUSIONS_BY_PATH_SECTION_NAME
90111
exclusion_value = get_absolute_path(absolute_path)
91-
elif by_package is not None:
92-
if scan_type != consts.SCA_SCAN_TYPE:
93-
raise click.ClickException('exclude by package is supported only for sca scan type')
112+
113+
if by_rule:
114+
exclusion_type = consts.EXCLUSIONS_BY_RULE_SECTION_NAME
115+
exclusion_value = by_rule
116+
117+
if by_package:
94118
if not _is_package_pattern_valid(by_package):
95119
raise click.ClickException('wrong package pattern. should be name@version.')
120+
96121
exclusion_type = consts.EXCLUSIONS_BY_PACKAGE_SECTION_NAME
97122
exclusion_value = by_package
98-
else:
99-
exclusion_type = consts.EXCLUSIONS_BY_RULE_SECTION_NAME
100-
exclusion_value = by_rule
123+
124+
if by_cve:
125+
exclusion_type = consts.EXCLUSIONS_BY_CVE_SECTION_NAME
126+
exclusion_value = by_cve
127+
128+
if not exclusion_type or not exclusion_value:
129+
# should never happen
130+
raise click.ClickException('Invalid ignore by type')
101131

102132
configuration_scope = 'global' if is_global else 'local'
103133
logger.debug(

cycode/cli/commands/scan/code_scanner.py

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -764,58 +764,68 @@ def _exclude_detections_by_exclusions_configuration(detections: List[Detection],
764764

765765

766766
def _should_exclude_detection(detection: Detection, exclusions: Dict) -> bool:
767+
# FIXME(MarshalX): what the difference between by_value and by_sha?
767768
exclusions_by_value = exclusions.get(consts.EXCLUSIONS_BY_VALUE_SECTION_NAME, [])
768769
if _is_detection_sha_configured_in_exclusions(detection, exclusions_by_value):
769770
logger.debug(
770-
'Going to ignore violations because they are on the values-to-ignore list, %s',
771-
{'value_sha': detection.detection_details.get('sha512', '')},
771+
'Ignoring violation because its value is on the ignore list, %s',
772+
{'value_sha': detection.detection_details.get('sha512')},
772773
)
773774
return True
774775

775776
exclusions_by_sha = exclusions.get(consts.EXCLUSIONS_BY_SHA_SECTION_NAME, [])
776777
if _is_detection_sha_configured_in_exclusions(detection, exclusions_by_sha):
777778
logger.debug(
778-
'Going to ignore violations because they are on the SHA ignore list, %s',
779-
{'sha': detection.detection_details.get('sha512', '')},
779+
'Ignoring violation because its SHA value is on the ignore list, %s',
780+
{'sha': detection.detection_details.get('sha512')},
780781
)
781782
return True
782783

783784
exclusions_by_rule = exclusions.get(consts.EXCLUSIONS_BY_RULE_SECTION_NAME, [])
784-
if exclusions_by_rule:
785-
detection_rule = detection.detection_rule_id
786-
if detection_rule in exclusions_by_rule:
787-
logger.debug(
788-
'Going to ignore violations because they are on the Rule ID ignore list, %s',
789-
{'detection_rule': detection_rule},
790-
)
791-
return True
785+
detection_rule_id = detection.detection_rule_id
786+
if detection_rule_id in exclusions_by_rule:
787+
logger.debug(
788+
'Ignoring violation because its Detection Rule ID is on the ignore list, %s',
789+
{'detection_rule_id': detection_rule_id},
790+
)
791+
return True
792792

793793
exclusions_by_package = exclusions.get(consts.EXCLUSIONS_BY_PACKAGE_SECTION_NAME, [])
794-
if exclusions_by_package:
795-
package = _get_package_name(detection)
796-
if package in exclusions_by_package:
797-
logger.debug(
798-
'Going to ignore violations because they are on the packages-to-ignore list, %s', {'package': package}
799-
)
800-
return True
794+
package = _get_package_name(detection)
795+
if package and package in exclusions_by_package:
796+
logger.debug('Ignoring violation because its package@version is on the ignore list, %s', {'package': package})
797+
return True
798+
799+
exclusions_by_cve = exclusions.get(consts.EXCLUSIONS_BY_CVE_SECTION_NAME, [])
800+
cve = _get_cve_identifier(detection)
801+
if cve and cve in exclusions_by_cve:
802+
logger.debug('Ignoring violation because its CVE is on the ignore list, %s', {'cve': cve})
803+
return True
801804

802805
return False
803806

804807

805808
def _is_detection_sha_configured_in_exclusions(detection: Detection, exclusions: List[str]) -> bool:
806-
detection_sha = detection.detection_details.get('sha512', '')
809+
detection_sha = detection.detection_details.get('sha512')
807810
return detection_sha in exclusions
808811

809812

810-
def _get_package_name(detection: Detection) -> str:
811-
package_name = detection.detection_details.get('vulnerable_component', '')
812-
package_version = detection.detection_details.get('vulnerable_component_version', '')
813+
def _get_package_name(detection: Detection) -> Optional[str]:
814+
package_name = detection.detection_details.get('vulnerable_component')
815+
package_version = detection.detection_details.get('vulnerable_component_version')
816+
817+
if package_name is None:
818+
package_name = detection.detection_details.get('package_name')
819+
package_version = detection.detection_details.get('package_version')
820+
821+
if package_name and package_version:
822+
return f'{package_name}@{package_version}'
823+
824+
return None
813825

814-
if package_name == '':
815-
package_name = detection.detection_details.get('package_name', '')
816-
package_version = detection.detection_details.get('package_version', '')
817826

818-
return f'{package_name}@{package_version}'
827+
def _get_cve_identifier(detection: Detection) -> Optional[str]:
828+
return detection.detection_details.get('alert', {}).get('cve_identifier')
819829

820830

821831
def _get_document_by_file_name(

cycode/cli/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
EXCLUSIONS_BY_PATH_SECTION_NAME = 'paths'
132132
EXCLUSIONS_BY_RULE_SECTION_NAME = 'rules'
133133
EXCLUSIONS_BY_PACKAGE_SECTION_NAME = 'packages'
134+
EXCLUSIONS_BY_CVE_SECTION_NAME = 'cves'
134135

135136
# 5MB in bytes (in decimal)
136137
FILE_MAX_SIZE_LIMIT_IN_BYTES = 5000000

cycode/cli/user_settings/configuration_manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def add_exclusion(self, scope: str, scan_type: str, exclusion_type: str, value:
7979
config_file_manager = self.get_config_file_manager(scope)
8080
config_file_manager.add_exclusion(scan_type, exclusion_type, value)
8181

82-
def _merge_exclusions(self, local_exclusions: Dict, global_exclusions: Dict) -> Dict:
82+
@staticmethod
83+
def _merge_exclusions(local_exclusions: Dict, global_exclusions: Dict) -> Dict:
8384
keys = set(list(local_exclusions.keys()) + list(global_exclusions.keys()))
8485
return {key: local_exclusions.get(key, []) + global_exclusions.get(key, []) for key in keys}
8586

0 commit comments

Comments
 (0)