-
Notifications
You must be signed in to change notification settings - Fork 65
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
Open
AradTraub
wants to merge
13
commits into
cycodehq:main
Choose a base branch
from
AradTraub:CM-68446-unmaintained-package-cli-scan
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
5c0b995
CM-68446: Add the unmaintained-packages SCA scan option
AradTraub 79c620c
CM-68446: Render unmaintained package detections in the SCA printers
AradTraub 0f68323
CM-68446: Document the unmaintained-packages SCA scan option
AradTraub 20fa8ee
CM-68446: Stop rich from highlighting the OSSF score column
AradTraub e65887b
CM-68446: Read package health from the nested ossf detection detail
AradTraub beb6a62
Revert "CM-68446: Document the unmaintained-packages SCA scan option"
AradTraub c21ed68
Revert "CM-68446: Add the unmaintained-packages SCA scan option"
AradTraub 7b3fad8
Reapply "CM-68446: Add the unmaintained-packages SCA scan option"
AradTraub 973b7a4
Reapply "CM-68446: Document the unmaintained-packages SCA scan option"
AradTraub cdcb03e
CM-68446: Send the maintainability option as an explicit result filter
AradTraub 6c3788d
CM-68446: Drop explanatory comments from the scan parameter and ossf …
AradTraub b5e57bc
CM-68446: Render the OSSF score in the text printer
AradTraub 2284682
CM-71730: Report the OSSF Maintained check score, not the aggregate
AradTraub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}[/]') | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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?