Skip to content
34 changes: 29 additions & 5 deletions bandit/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
27 changes: 27 additions & 0 deletions bandit/core/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 4 additions & 0 deletions doc/source/man/bandit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/cli/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
35 changes: 35 additions & 0 deletions tests/unit/core/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))