diff --git a/bandit/cli/main.py b/bandit/cli/main.py index bbefc931d..5b0a182b5 100644 --- a/bandit/cli/main.py +++ b/bandit/cli/main.py @@ -269,9 +269,18 @@ def main(): '--ini', dest='ini_path', action='store', default=None, help='path to a .bandit file that supplies command line arguments' ) - parser.add_argument('--exit-zero', action='store_true', dest='exit_zero', - default=False, help='exit with 0, ' - 'even with results found') + exit_zero_group = parser.add_mutually_exclusive_group(required=False) + exit_zero_group.add_argument( + '--exit-zero', action='store_true', dest='exit_zero', default=False, + help='exit with 0, even with results found' + ) + exit_zero_group.add_argument( + '--exit-zero-severity', dest='exit_zero_severity_string', + action='store', default=None, choices=["all", "low", "medium", "high"], + help='control which severity makes bandit to exit with zero ' + 'status code. Lower severities to the specified one are ' + 'included implicitly. ' + ) python_ver = sys.version.replace('\n', '') parser.add_argument( '--version', action='version', @@ -351,6 +360,17 @@ def main(): args.confidence = 4 # Other strings will be blocked by argparse + if args.exit_zero_severity_string is not None: + if args.exit_zero_severity_string == "all": + args.exit_zero_severity = 1 + elif args.exit_zero_severity_string == "low": + args.exit_zero_severity = 2 + elif args.exit_zero_severity_string == "medium": + args.exit_zero_severity = 3 + elif args.exit_zero_severity_string == "high": + args.exit_zero_severity = 4 + # Other strings will be blocked by argparse + try: b_conf = b_config.BanditConfig(config_file=args.config_file) except utils.ConfigError as e: @@ -546,8 +566,12 @@ def main(): args.output_format, args.msg_template) - if (b_mgr.results_count(sev_filter=sev_level, conf_filter=conf_level) > 0 - and not args.exit_zero): + if (args.exit_zero + or "exit_zero_severity" in args + and not b_mgr.above_threshold_results(args.exit_zero_severity)): + sys.exit(0) + + if b_mgr.results_count(sev_filter=sev_level, conf_filter=conf_level) > 0: sys.exit(1) else: sys.exit(0) diff --git a/bandit/core/manager.py b/bandit/core/manager.py index 466670c8a..75cbc0d74 100644 --- a/bandit/core/manager.py +++ b/bandit/core/manager.py @@ -320,6 +320,33 @@ def _execute_ast_visitor(self, fname, data, nosec_lines): self.results.extend(res.tester.results) return score + def above_threshold_results(self, exit_zero_severity): + '''Check the above threshold results + + this method takes args.exit_zero_severity and checkes the count + of results of all severities above the defined exit zero severity. + if any of the above severities reports > 0 results this method + returns True else it returns False + + :param exit_zero_severity: integer value converted from + exit_zero_severity_string + :return: bool value depending on the results + ''' + + items_in_rankings = len(b_constants.RANKING) + # this is the minimal level we shouldn't exit with 0 + non_exit_zero_severity = exit_zero_severity + 1 + + while non_exit_zero_severity <= items_in_rankings: + some_var = b_constants.RANKING[non_exit_zero_severity - 1] + results_count = self.results_count(sev_filter=some_var) + non_exit_zero_severity += 1 + + if results_count > 0: + return True + + return False + def _get_files_from_dir(files_dir, included_globs=None, excluded_path_strings=None): diff --git a/doc/source/man/bandit.rst b/doc/source/man/bandit.rst index 46125e613..efa01e376 100644 --- a/doc/source/man/bandit.rst +++ b/doc/source/man/bandit.rst @@ -70,6 +70,10 @@ OPTIONS --ini INI_PATH path to a .bandit file that supplies command line arguments --exit-zero exit with 0, even with results found + --exit-zero-severity EXIT_ZERO_SEVERITY_STRING + control which severity makes bandit to exit with zero status code. + Lower severities to the specified one are included implicitly + (low for LOW, medium for MEDIUM, high for HIGH). --version show program's version number and exit CUSTOM FORMATTING diff --git a/tests/unit/cli/test_main.py b/tests/unit/cli/test_main.py index ecc0f1fa1..9d3089066 100644 --- a/tests/unit/cli/test_main.py +++ b/tests/unit/cli/test_main.py @@ -321,3 +321,36 @@ def test_main_exit_with_results_and_with_exit_zero_flag(self): mock_mgr_results_ct.return_value = 1 self.assertRaisesRegex(SystemExit, '0', bandit.main) + + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', + 'output', '--exit-zero-severity', 'low']) + def test_main_exit_with_results_and_with_ezs_flag_set_returning_true(self): + # Test that bandit exits with 0 on results and zero flag + temp_directory = self.useFixture(fixtures.TempDir()).path + os.chdir(temp_directory) + with open('bandit.yaml', 'wt') as fd: + fd.write(bandit_config_content) + with mock.patch('bandit.core.manager.BanditManager.results_count' + ) as mock_mgr_results_ct: + mock_mgr_results_ct.return_value = 2 + with mock.patch( + 'bandit.core.manager.BanditManager.above_threshold_results' + ) as mock_mgr_above_threshold_results_ct: + mock_mgr_above_threshold_results_ct.return_value = True + + self.assertRaisesRegex(SystemExit, '1', bandit.main) + + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', + 'output', '--exit-zero-severity', 'medium']) + def test_main_exit_with_results_and_with_ezs_flag_returning_false(self): + # Test that bandit exits with 0 on results and zero flag + temp_directory = self.useFixture(fixtures.TempDir()).path + os.chdir(temp_directory) + with open('bandit.yaml', 'wt') as fd: + fd.write(bandit_config_content) + with mock.patch( + 'bandit.core.manager.BanditManager.above_threshold_results' + ) as mock_mgr_above_threshold_results_ct: + mock_mgr_above_threshold_results_ct.return_value = False + + self.assertRaisesRegex(SystemExit, '0', bandit.main) diff --git a/tests/unit/core/test_manager.py b/tests/unit/core/test_manager.py index c5a3b9a37..9e205a4b8 100644 --- a/tests/unit/core/test_manager.py +++ b/tests/unit/core/test_manager.py @@ -333,3 +333,38 @@ def test_find_candidate_matches(self): {issue_a: [issue_a, issue_b], issue_b: [issue_a, issue_b]}, manager._find_candidate_matches([issue_a, issue_b], [issue_a, issue_b, issue_c])) + + def test_above_threshold_medium_severity_results_true(self): + levels = [constants.LOW, constants.MEDIUM] + self.manager.results = ( + [issue.Issue(severity=level, confidence=level) + for level in levels]) + self.assertTrue(self.manager.above_threshold_results(2)) + + def test_above_threshold_high_severity_results_true(self): + levels = [constants.LOW, constants.HIGH] + self.manager.results = ( + [issue.Issue(severity=level, confidence=level) + for level in levels]) + self.assertTrue(self.manager.above_threshold_results(2)) + + def test_above_threshold_low_severity_results_false(self): + levels = [constants.LOW] + self.manager.results = ( + [issue.Issue(severity=level, confidence=level) + for level in levels]) + self.assertFalse(self.manager.above_threshold_results(2)) + + def test_above_threshold_medium_severity_results_false(self): + levels = [constants.MEDIUM] + self.manager.results = ( + [issue.Issue(severity=level, confidence=level) + for level in levels]) + self.assertFalse(self.manager.above_threshold_results(3)) + + def test_above_threshold_high_severity_results_false(self): + levels = [constants.HIGH] + self.manager.results = ( + [issue.Issue(severity=level, confidence=level) + for level in levels]) + self.assertFalse(self.manager.above_threshold_results(4))