Skip to content
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

#640 - Exit zero status based on severity level #715

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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))