Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 66
CM-68446: Render unmaintained package detections in SCA output#523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base:main
Are you sure you want to change the base?
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
5c0b99579c620c0f6832320fa8eee65887bbeb6a62c21ed687b3fad8973b7a4cdcb03e6c3788db5e57bc2284682File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2,13 +2,18 @@ | ||
| from typing import TYPE_CHECKING | ||
| from cycode.cli.cli_types import SeverityOption | ||
| from cycode.cli.consts import LICENSE_COMPLIANCE_POLICY_ID, PACKAGE_VULNERABILITY_POLICY_ID | ||
| from cycode.cli.consts import ( | ||
| LICENSE_COMPLIANCE_POLICY_ID, | ||
| PACKAGE_VULNERABILITY_POLICY_ID, | ||
| UNMAINTAINED_PACKAGE_POLICY_ID, | ||
| ) | ||
| from cycode.cli.models import Detection | ||
| from cycode.cli.printers.tables.table import Table | ||
| from cycode.cli.printers.tables.table_models import ColumnInfoBuilder | ||
| from cycode.cli.printers.tables.table_printer_base import TablePrinterBase | ||
| from cycode.cli.printers.utils import is_git_diff_based_scan | ||
| from cycode.cli.printers.utils.detection_ordering.sca_ordering import sort_and_group_detections | ||
| from cycode.cli.printers.utils.sca_ossf import get_maintained_score | ||
| from cycode.cli.utils.string_utils import shortcut_dependency_paths | ||
| if TYPE_CHECKING: | ||
| @@ -23,6 +28,7 @@ | ||
| ECOSYSTEM_COLUMN = column_builder.build(name='Ecosystem', highlight=False) | ||
| PACKAGE_COLUMN = column_builder.build(name='Package', highlight=False) | ||
| CVE_COLUMNS = column_builder.build(name='CVE', highlight=False) | ||
| MAINTAINED_SCORE_COLUMN = column_builder.build(name='Maintained Score', highlight=False) | ||
| DEPENDENCY_PATHS_COLUMN = column_builder.build(name='Dependency Paths') | ||
| UPGRADE_COLUMN = column_builder.build(name='Upgrade') | ||
| LICENSE_COLUMN = column_builder.build(name='License', highlight=False) | ||
| @@ -51,6 +57,8 @@ def _get_title(policy_id: str) -> str: | ||
| return 'Dependency Vulnerabilities' | ||
| if policy_id == LICENSE_COMPLIANCE_POLICY_ID: | ||
| return 'License Compliance' | ||
| if policy_id == UNMAINTAINED_PACKAGE_POLICY_ID: | ||
| return 'Unmaintained Packages' | ||
| return 'Unknown' | ||
| @@ -62,6 +70,8 @@ def _get_table(self, policy_id: str) -> Table: | ||
| table.add_column(UPGRADE_COLUMN) | ||
| elif policy_id == LICENSE_COMPLIANCE_POLICY_ID: | ||
| table.add_column(LICENSE_COLUMN) | ||
| elif policy_id == UNMAINTAINED_PACKAGE_POLICY_ID: | ||
| table.add_column(MAINTAINED_SCORE_COLUMN) | ||
| if is_git_diff_based_scan(self.command_scan_type): | ||
| table.add_column(REPOSITORY_COLUMN) | ||
| @@ -120,6 +130,9 @@ def _enrich_table_with_values(table: Table, detection: Detection) -> None: | ||
| table.add_cell(CVE_COLUMNS, detection_details.get('vulnerability_id')) | ||
| table.add_cell(LICENSE_COLUMN, detection_details.get('license')) | ||
| maintained_score = get_maintained_score(detection_details) | ||
| table.add_cell(MAINTAINED_SCORE_COLUMN, 'N/A' if maintained_score is None else str(maintained_score)) | ||
Comment on lines
+133
to
+134
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please put this being policy gate (score is irrelevant for other policies | ||
| def _print_summary_issues(self, detections_count: int, title: str) -> None: | ||
| self.console.print(f'[bold]Cycode found {detections_count} violations of type: [cyan]{title}[/]') | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -7,6 +7,7 @@ | ||
| from cycode.cli.printers.utils.code_snippet_syntax import get_code_snippet_syntax, get_detection_line | ||
| from cycode.cli.printers.utils.detection_data import get_detection_title | ||
| from cycode.cli.printers.utils.detection_ordering.common_ordering import sort_and_group_detections_from_scan_result | ||
| from cycode.cli.printers.utils.sca_ossf import get_maintained_score, get_ossf_report_url, get_ossf_score | ||
| if TYPE_CHECKING: | ||
| from cycode.cli.models import Detection, LocalScanResult | ||
| @@ -84,7 +85,17 @@ def __get_intermediate_summary_lines(self, detection: 'Detection') -> list[str]: | ||
| def __get_sca_related_summary_lines(detection: 'Detection') -> list[str]: | ||
| summary_lines = [] | ||
| if detection.has_alert: | ||
| if detection.detection_type_id == consts.UNMAINTAINED_PACKAGE_POLICY_ID: | ||
| maintained_score = get_maintained_score(detection.detection_details) | ||
| ossf_score = get_ossf_score(detection.detection_details) | ||
| maintained = 'N/A' if maintained_score is None else maintained_score | ||
| score = 'N/A' if ossf_score is None else ossf_score | ||
| report_url = get_ossf_report_url(detection.detection_details) or 'N/A' | ||
| summary_lines.append(f'Maintained score: [cyan]{maintained}[/]\n') | ||
| summary_lines.append(f'OSSF Scorecard score: [cyan]{score}[/]\n') | ||
| summary_lines.append(f'Scorecard report: [cyan]{report_url}[/]\n') | ||
| elif detection.has_alert: | ||
Comment on lines
+88
to
+98
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this actually be conditional? | ||
| patched_version = detection.detection_details['alert'].get('first_patched_version') | ||
| patched_version = patched_version or 'Not fixed' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from typing import Any, Optional | ||
| _MAINTAINED_CHECK_NAME = 'maintained' | ||
| def _get_ossf_details(detection_details: dict) -> dict: | ||
| return detection_details.get('ossf') or {} | ||
| def get_ossf_score(detection_details: dict) -> Optional[Any]: | ||
| return _get_ossf_details(detection_details).get('score') | ||
| def get_ossf_report_url(detection_details: dict) -> Optional[str]: | ||
| return _get_ossf_details(detection_details).get('scorecard_report_url') | ||
| def get_maintained_score(detection_details: dict) -> Optional[Any]: | ||
| for check in _get_ossf_details(detection_details).get('checks') or []: | ||
| if str(check.get('name', '')).lower() == _MAINTAINED_CHECK_NAME: | ||
| return check.get('score') | ||
| return None |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| from unittest.mock import MagicMock | ||
| import pytest | ||
| from rich.console import Console | ||
| from cycode.cli.consts import ( | ||
| LICENSE_COMPLIANCE_POLICY_ID, | ||
| PACKAGE_VULNERABILITY_POLICY_ID, | ||
| UNMAINTAINED_PACKAGE_POLICY_ID, | ||
| ) | ||
| from cycode.cli.printers.tables.sca_table_printer import ( | ||
| CVE_COLUMNS, | ||
| LICENSE_COLUMN, | ||
| MAINTAINED_SCORE_COLUMN, | ||
| UPGRADE_COLUMN, | ||
| ScaTablePrinter, | ||
| ) | ||
| from cycode.cyclient.models import Detection | ||
| @pytest.fixture | ||
| def printer() -> ScaTablePrinter: | ||
| ctx = MagicMock() | ||
| ctx.obj = {'scan_type': 'sca'} | ||
| ctx.info_name = 'path' | ||
| return ScaTablePrinter(ctx, Console(), Console(stderr=True)) | ||
| def _make_detection(policy_id: str, **details: object) -> Detection: | ||
| return Detection( | ||
| detection_type_id=policy_id, | ||
| type='Unmaintained packages', | ||
| message='Package is unmaintained', | ||
| detection_details=dict(details), | ||
| detection_rule_id='rule-id', | ||
| severity='Medium', | ||
| ) | ||
| def test_get_title_unmaintained_packages() -> None: | ||
| assert ScaTablePrinter._get_title(UNMAINTAINED_PACKAGE_POLICY_ID) == 'Unmaintained Packages' | ||
| def test_get_title_known_policies_are_not_changed() -> None: | ||
| assert ScaTablePrinter._get_title(PACKAGE_VULNERABILITY_POLICY_ID) == 'Dependency Vulnerabilities' | ||
| assert ScaTablePrinter._get_title(LICENSE_COMPLIANCE_POLICY_ID) == 'License Compliance' | ||
| def test_get_title_unknown_policy() -> None: | ||
| assert ScaTablePrinter._get_title('not-a-known-policy-id') == 'Unknown' | ||
| def test_get_table_unmaintained_packages_columns(printer: ScaTablePrinter) -> None: | ||
| columns = printer._get_table(UNMAINTAINED_PACKAGE_POLICY_ID).get_columns_info() | ||
| assert MAINTAINED_SCORE_COLUMN in columns | ||
| assert CVE_COLUMNS not in columns | ||
| assert UPGRADE_COLUMN not in columns | ||
| assert LICENSE_COLUMN not in columns | ||
| def test_get_table_unmaintained_packages_column_order(printer: ScaTablePrinter) -> None: | ||
| column_names = [column.name for column in printer._get_table(UNMAINTAINED_PACKAGE_POLICY_ID).get_columns_info()] | ||
| assert column_names == [ | ||
| 'Severity', | ||
| 'Code Project', | ||
| 'Ecosystem', | ||
| 'Package', | ||
| 'Maintained Score', | ||
| 'Dependency Paths', | ||
| 'Direct Dependency', | ||
| 'Development Dependency', | ||
| ] | ||
| def test_get_table_other_policies_do_not_get_the_score_column(printer: ScaTablePrinter) -> None: | ||
| assert MAINTAINED_SCORE_COLUMN not in printer._get_table(PACKAGE_VULNERABILITY_POLICY_ID).get_columns_info() | ||
| assert MAINTAINED_SCORE_COLUMN not in printer._get_table(LICENSE_COMPLIANCE_POLICY_ID).get_columns_info() | ||
| def test_enrich_table_with_values_populates_the_score(printer: ScaTablePrinter) -> None: | ||
| table = printer._get_table(UNMAINTAINED_PACKAGE_POLICY_ID) | ||
| detection = _make_detection( | ||
| UNMAINTAINED_PACKAGE_POLICY_ID, | ||
| file_path='/repo/package.json', | ||
| ecosystem='npm', | ||
| package_name='left-pad', | ||
| package_version='1.0.0', | ||
| ossf={ | ||
| 'score': 4.1, | ||
| 'scorecard_report_url': 'https://scorecard.dev/viewer/?uri=github.com/example/left-pad', | ||
| 'checks': [{'name': 'Maintained', 'score': 1.5, 'reason': 'no recent activity'}], | ||
| }, | ||
| ) | ||
| ScaTablePrinter._enrich_table_with_values(table, detection) | ||
| row = table.get_rows()[0] | ||
| score_index = table.get_columns_info().index(MAINTAINED_SCORE_COLUMN) | ||
| assert row[score_index] == '1.5' | ||
| def test_enrich_table_with_values_missing_score(printer: ScaTablePrinter) -> None: | ||
| table = printer._get_table(UNMAINTAINED_PACKAGE_POLICY_ID) | ||
| detection = _make_detection(UNMAINTAINED_PACKAGE_POLICY_ID, file_path='/repo/package.json', package_name='left-pad') | ||
| ScaTablePrinter._enrich_table_with_values(table, detection) | ||
| row = table.get_rows()[0] | ||
| score_index = table.get_columns_info().index(MAINTAINED_SCORE_COLUMN) | ||
| assert row[score_index] == 'N/A' |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this actually be conditional?