This commit is contained in:
Iliyan Angelov
2025-12-01 06:50:10 +02:00
parent 91f51bc6fe
commit 62c1fe5951
4682 changed files with 544807 additions and 31208 deletions

View File

@@ -0,0 +1,252 @@
#
# Copyright 2015 Hewlett-Packard Enterprise
#
# SPDX-License-Identifier: Apache-2.0
# #############################################################################
# Bandit Baseline is a tool that runs Bandit against a Git commit, and compares
# the current commit findings to the parent commit findings.
# To do this it checks out the parent commit, runs Bandit (with any provided
# filters or profiles), checks out the current commit, runs Bandit, and then
# reports on any new findings.
# #############################################################################
"""Bandit is a tool designed to find common security issues in Python code."""
import argparse
import contextlib
import logging
import os
import shutil
import subprocess # nosec: B404
import sys
import tempfile
try:
import git
except ImportError:
git = None
bandit_args = sys.argv[1:]
baseline_tmp_file = "_bandit_baseline_run.json_"
current_commit = None
default_output_format = "terminal"
LOG = logging.getLogger(__name__)
repo = None
report_basename = "bandit_baseline_result"
valid_baseline_formats = ["txt", "html", "json"]
"""baseline.py"""
def main():
"""Execute Bandit."""
# our cleanup function needs this and can't be passed arguments
global current_commit
global repo
parent_commit = None
output_format = None
repo = None
report_fname = None
init_logger()
output_format, repo, report_fname = initialize()
if not repo:
sys.exit(2)
# #################### Find current and parent commits ####################
try:
commit = repo.commit()
current_commit = commit.hexsha
LOG.info("Got current commit: [%s]", commit.name_rev)
commit = commit.parents[0]
parent_commit = commit.hexsha
LOG.info("Got parent commit: [%s]", commit.name_rev)
except git.GitCommandError:
LOG.error("Unable to get current or parent commit")
sys.exit(2)
except IndexError:
LOG.error("Parent commit not available")
sys.exit(2)
# #################### Run Bandit against both commits ####################
output_type = (
["-f", "txt"]
if output_format == default_output_format
else ["-o", report_fname]
)
with baseline_setup() as t:
bandit_tmpfile = f"{t}/{baseline_tmp_file}"
steps = [
{
"message": "Getting Bandit baseline results",
"commit": parent_commit,
"args": bandit_args + ["-f", "json", "-o", bandit_tmpfile],
},
{
"message": "Comparing Bandit results to baseline",
"commit": current_commit,
"args": bandit_args + ["-b", bandit_tmpfile] + output_type,
},
]
return_code = None
for step in steps:
repo.head.reset(commit=step["commit"], working_tree=True)
LOG.info(step["message"])
bandit_command = ["bandit"] + step["args"]
try:
output = subprocess.check_output(bandit_command) # nosec: B603
except subprocess.CalledProcessError as e:
output = e.output
return_code = e.returncode
else:
return_code = 0
output = output.decode("utf-8") # subprocess returns bytes
if return_code not in [0, 1]:
LOG.error(
"Error running command: %s\nOutput: %s\n",
bandit_args,
output,
)
# #################### Output and exit ####################################
# print output or display message about written report
if output_format == default_output_format:
print(output)
else:
LOG.info("Successfully wrote %s", report_fname)
# exit with the code the last Bandit run returned
sys.exit(return_code)
# #################### Clean up before exit ###################################
@contextlib.contextmanager
def baseline_setup():
"""Baseline setup by creating temp folder and resetting repo."""
d = tempfile.mkdtemp()
yield d
shutil.rmtree(d, True)
if repo:
repo.head.reset(commit=current_commit, working_tree=True)
# #################### Setup logging ##########################################
def init_logger():
"""Init logger."""
LOG.handlers = []
log_level = logging.INFO
log_format_string = "[%(levelname)7s ] %(message)s"
logging.captureWarnings(True)
LOG.setLevel(log_level)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter(log_format_string))
LOG.addHandler(handler)
# #################### Perform initialization and validate assumptions ########
def initialize():
"""Initialize arguments and output formats."""
valid = True
# #################### Parse Args #########################################
parser = argparse.ArgumentParser(
description="Bandit Baseline - Generates Bandit results compared to "
"a baseline",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="Additional Bandit arguments such as severity filtering (-ll) "
"can be added and will be passed to Bandit.",
)
if sys.version_info >= (3, 14):
parser.suggest_on_error = True
parser.color = False
parser.add_argument(
"targets",
metavar="targets",
type=str,
nargs="+",
help="source file(s) or directory(s) to be tested",
)
parser.add_argument(
"-f",
dest="output_format",
action="store",
default="terminal",
help="specify output format",
choices=valid_baseline_formats,
)
args, _ = parser.parse_known_args()
# #################### Setup Output #######################################
# set the output format, or use a default if not provided
output_format = (
args.output_format if args.output_format else default_output_format
)
if output_format == default_output_format:
LOG.info("No output format specified, using %s", default_output_format)
# set the report name based on the output format
report_fname = f"{report_basename}.{output_format}"
# #################### Check Requirements #################################
if git is None:
LOG.error("Git not available, reinstall with baseline extra")
valid = False
return (None, None, None)
try:
repo = git.Repo(os.getcwd())
except git.exc.InvalidGitRepositoryError:
LOG.error("Bandit baseline must be called from a git project root")
valid = False
except git.exc.GitCommandNotFound:
LOG.error("Git command not found")
valid = False
else:
if repo.is_dirty():
LOG.error(
"Current working directory is dirty and must be " "resolved"
)
valid = False
# if output format is specified, we need to be able to write the report
if output_format != default_output_format and os.path.exists(report_fname):
LOG.error("File %s already exists, aborting", report_fname)
valid = False
# Bandit needs to be able to create this temp file
if os.path.exists(baseline_tmp_file):
LOG.error(
"Temporary file %s needs to be removed prior to running",
baseline_tmp_file,
)
valid = False
# we must validate -o is not provided, as it will mess up Bandit baseline
if "-o" in bandit_args:
LOG.error("Bandit baseline must not be called with the -o option")
valid = False
return (output_format, repo, report_fname) if valid else (None, None, None)
if __name__ == "__main__":
main()