-
Notifications
You must be signed in to change notification settings - Fork 38
Ceng 728 add command to return vulnerability results #272
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
Merged
colinmoynes
merged 29 commits into
master
from
CENG-728--add-command-to-return-vulnerability-results
Mar 13, 2026
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
ae90e5a
initial
colinmoynes fb33d81
added severity_filter flag
colinmoynes 48b2548
added html prototype flag
colinmoynes 080aa69
improvied html report formatting
colinmoynes 571fcf6
Added fixable/non-fixable filter flags
colinmoynes 30a36d1
added used filters to report output
colinmoynes e4e9adf
fixed html
colinmoynes 4312cc7
filtering improvements and output
colinmoynes d048d18
show assessment flag now shows tablular and parsed view. accepts seve…
colinmoynes 54a7ec2
improved url formatting
colinmoynes 93f2cfb
update html field for scan date
colinmoynes 9ffd10d
Added unit tests for vulnerabilities command
colinmoynes 8012fcd
added affected version to results
colinmoynes e6d4106
moved out the html report generation for this release
colinmoynes 73e3c7a
remove vulnerability html testcase
colinmoynes b145e01
Updated changelog and readme
colinmoynes 5a914c6
updated changelog to 1.14.0 release
colinmoynes 9d2c6cb
minor formatting and informational text updates
colinmoynes bcfec2c
Implemented rich table formatting. New rich table function in utils. …
colinmoynes 8b830c6
removed reference url and added version operator
colinmoynes 01fbf1c
Fixed changelog
colinmoynes 7327499
fix testcase
colinmoynes ea0d99e
updated requirements.txt and test case issue
colinmoynes c481473
test cases updated
colinmoynes 653446e
Update CHANGELOG.md
colinmoynes 9305f03
Update cloudsmith_cli/cli/commands/vulnerabilities.py
colinmoynes 79ddb28
restored and corrected requirements.txt
colinmoynes bf5cbda
Fixed readme, moved print logic from core/api to command.
colinmoynes 7c1af0f
updated envrc to remove comment
colinmoynes 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,5 +26,6 @@ | |
| tags, | ||
| tokens, | ||
| upstream, | ||
| vulnerabilities, | ||
| whoami, | ||
| ) | ||
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,141 @@ | ||
| """CLI/Commands - Vulnerabilities.""" | ||
|
|
||
| import click | ||
|
|
||
| from ...core.api.vulnerabilities import ( | ||
| _print_vulnerabilities_assessment_table, | ||
| _print_vulnerabilities_summary_table, | ||
| get_package_scan_result, | ||
| ) | ||
| from .. import decorators, utils, validators | ||
| from ..exceptions import handle_api_exceptions | ||
| from .main import main | ||
|
|
||
|
|
||
| @main.command() | ||
colinmoynes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @decorators.common_cli_config_options | ||
| @decorators.common_cli_output_options | ||
| @decorators.common_api_auth_options | ||
| @decorators.initialise_api | ||
| @click.argument( | ||
| "owner_repo_package", | ||
| metavar="OWNER/REPO/PACKAGE", | ||
| callback=validators.validate_owner_repo_package, | ||
| ) | ||
| @click.option( | ||
| "-A", | ||
| "--show-assessment", | ||
| is_flag=True, | ||
| help="Show assessment with vulnerability details.", | ||
| ) | ||
| @click.option( | ||
| "--fixable/--non-fixable", | ||
| is_flag=True, | ||
| default=None, # Changed to allow None (Show All) vs True/False | ||
| help="Filter by fixable status (only fixable vs only non-fixable).", | ||
| ) | ||
| @click.option( | ||
| "--severity", | ||
| "severity_filter", | ||
| help="Filter by severities (e.g., 'CRITICAL', 'HIGH', 'MEDIUM', 'LOW').", | ||
| ) | ||
| @click.pass_context | ||
| def vulnerabilities( | ||
| ctx, opts, owner_repo_package, show_assessment, fixable, severity_filter | ||
| ): | ||
| """ | ||
| Retrieve vulnerability scan results for a package. | ||
|
|
||
| \b | ||
| Usage: | ||
| cloudsmith vulnerabilities myorg/repo/pkg_identifier [flags] | ||
|
|
||
| \b | ||
| Aliases: | ||
| vulnerabilities, vuln | ||
|
|
||
colinmoynes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Examples: | ||
|
|
||
| \b | ||
| # Display the vulnerability summary | ||
| cloudsmith vulnerabilities myorg/repo/pkg_identifier | ||
|
|
||
| \b | ||
| # Display detailed vulnerability assessment | ||
| cloudsmith vulnerabilities myorg/repo/pkg_identifier -A / --show-assessment | ||
|
|
||
| \b | ||
| # Filter the result by severity | ||
| cloudsmith vulnerabilities myorg/repo/pkg_identifier --severity critical,high | ||
|
|
||
| \b | ||
| # Filter by fixable or non-fixable vulnerabilities | ||
| cloudsmith vulnerabilities myorg/repo/pkg_identifier --fixable / --non-fixable | ||
|
|
||
|
|
||
| """ | ||
| use_stderr = utils.should_use_stderr(opts) | ||
|
|
||
| owner, repo, slug = owner_repo_package | ||
|
|
||
| total_filtered_vulns = 0 | ||
|
|
||
| context_msg = "Failed to retrieve vulnerability report!" | ||
| with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): | ||
| with utils.maybe_spinner(opts): | ||
| data = get_package_scan_result( | ||
| opts=opts, | ||
| owner=owner, | ||
| repo=repo, | ||
| package=slug, | ||
| show_assessment=show_assessment, | ||
| severity_filter=severity_filter, | ||
| fixable=fixable, | ||
| ) | ||
|
|
||
| click.secho("OK", fg="green", err=use_stderr) | ||
|
|
||
| # Filter results if severity or fixable flags are active | ||
| if severity_filter or fixable is not None: | ||
| scans = getattr(data, "scans", []) | ||
|
|
||
| allowed_severities = ( | ||
| [s.strip().lower() for s in severity_filter.split(",")] | ||
| if severity_filter | ||
| else None | ||
| ) | ||
|
|
||
| for scan in scans: | ||
| results = getattr(scan, "results", []) | ||
|
|
||
| # 1. Filter by Severity | ||
| if allowed_severities: | ||
| results = [ | ||
| res | ||
| for res in results | ||
| if getattr(res, "severity", "unknown").lower() in allowed_severities | ||
| ] | ||
|
|
||
| # 2. Filter by Fixable Status | ||
| # fixable=True: Keep only if has fix_version | ||
| # fixable=False: Keep only if NO fix_version | ||
| if fixable is not None: | ||
| results = [ | ||
| res | ||
| for res in results | ||
| if bool( | ||
| getattr(res, "fix_version", getattr(res, "fixed_version", None)) | ||
| ) | ||
| is fixable | ||
| ] | ||
|
|
||
| scan.results = results | ||
| total_filtered_vulns += len(results) | ||
|
|
||
| if utils.maybe_print_as_json(opts, data): | ||
| return | ||
|
|
||
| _print_vulnerabilities_summary_table(data, severity_filter, total_filtered_vulns) | ||
|
|
||
| if show_assessment: | ||
| _print_vulnerabilities_assessment_table(data, severity_filter) | ||
112 changes: 112 additions & 0 deletions
112
cloudsmith_cli/cli/tests/commands/test_vulnerabilities.py
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 @@ | ||
| import unittest | ||
| from unittest.mock import patch | ||
|
|
||
| from click.testing import CliRunner | ||
|
|
||
| from cloudsmith_cli.cli.commands.vulnerabilities import vulnerabilities | ||
|
|
||
|
|
||
| class TestVulnerabilitiesCommand(unittest.TestCase): | ||
| def setUp(self): | ||
| self.runner = CliRunner() | ||
|
|
||
| @patch("cloudsmith_cli.cli.commands.vulnerabilities.get_package_scan_result") | ||
| def test_vulnerabilities_basic(self, mock_get_scan): | ||
| """Test basic vulnerabilities command invocation.""" | ||
| result = self.runner.invoke( | ||
| vulnerabilities, | ||
| [ | ||
| "testorg/testrepo/pkg-slug", | ||
| ], | ||
| ) | ||
|
|
||
| self.assertEqual(result.exit_code, 0) | ||
| mock_get_scan.assert_called_once() | ||
|
|
||
| # Verify args passed to core logic | ||
| args = mock_get_scan.call_args[1] | ||
| self.assertEqual(args["owner"], "testorg") | ||
| self.assertEqual(args["repo"], "testrepo") | ||
| self.assertEqual(args["package"], "pkg-slug") | ||
| self.assertFalse(args["show_assessment"]) | ||
| self.assertIsNone(args["severity_filter"]) | ||
|
|
||
| @patch("cloudsmith_cli.cli.commands.vulnerabilities.get_package_scan_result") | ||
| def test_vulnerabilities_show_assessment(self, mock_get_scan): | ||
| """Test vulnerabilities command with --show-assessment flag.""" | ||
| result = self.runner.invoke( | ||
| vulnerabilities, | ||
| [ | ||
| "testorg/testrepo/pkg-slug", | ||
| "--show-assessment", | ||
| ], | ||
| ) | ||
|
|
||
| self.assertEqual(result.exit_code, 0) | ||
| args = mock_get_scan.call_args[1] | ||
| self.assertTrue(args["show_assessment"]) | ||
|
|
||
| @patch("cloudsmith_cli.cli.commands.vulnerabilities.get_package_scan_result") | ||
| def test_vulnerabilities_alias_flags(self, mock_get_scan): | ||
| """Test vulnerabilities command with short flags (-A).""" | ||
| result = self.runner.invoke( | ||
| vulnerabilities, | ||
| [ | ||
| "testorg/testrepo/pkg-slug", | ||
| "-A", | ||
| ], | ||
| ) | ||
|
|
||
| self.assertEqual(result.exit_code, 0) | ||
| args = mock_get_scan.call_args[1] | ||
| self.assertTrue(args["show_assessment"]) | ||
|
|
||
| @patch("cloudsmith_cli.cli.commands.vulnerabilities.get_package_scan_result") | ||
| def test_vulnerabilities_severity_filter(self, mock_get_scan): | ||
| """Test vulnerabilities command with --severity filter.""" | ||
| result = self.runner.invoke( | ||
| vulnerabilities, | ||
| [ | ||
| "testorg/testrepo/pkg-slug", | ||
| "--severity", | ||
| "CRITICAL,HIGH", | ||
| ], | ||
| ) | ||
|
|
||
| self.assertEqual(result.exit_code, 0) | ||
| args = mock_get_scan.call_args[1] | ||
| self.assertEqual(args["severity_filter"], "CRITICAL,HIGH") | ||
|
|
||
| @patch("cloudsmith_cli.cli.commands.vulnerabilities.get_package_scan_result") | ||
| def test_vulnerabilities_fixable_filter(self, mock_get_scan): | ||
| """Test vulnerabilities command with --fixable filter.""" | ||
| result = self.runner.invoke( | ||
| vulnerabilities, | ||
| [ | ||
| "testorg/testrepo/pkg-slug", | ||
| "--fixable", | ||
| ], | ||
| ) | ||
|
|
||
| self.assertEqual(result.exit_code, 0) | ||
| args = mock_get_scan.call_args[1] | ||
| self.assertTrue(args["fixable"]) | ||
|
|
||
| @patch("cloudsmith_cli.cli.commands.vulnerabilities.get_package_scan_result") | ||
| def test_vulnerabilities_non_fixable_filter(self, mock_get_scan): | ||
| """Test vulnerabilities command with --non-fixable filter.""" | ||
| result = self.runner.invoke( | ||
| vulnerabilities, | ||
| [ | ||
| "testorg/testrepo/pkg-slug", | ||
| "--non-fixable", | ||
| ], | ||
| ) | ||
|
|
||
| self.assertEqual(result.exit_code, 0) | ||
| args = mock_get_scan.call_args[1] | ||
| self.assertFalse(args["fixable"]) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
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
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.
Uh oh!
There was an error while loading. Please reload this page.