Files
Hotel-Booking/Backend/venv/lib/python3.12/site-packages/bandit/cli/main.py
Iliyan Angelov 62c1fe5951 updates
2025-12-01 06:50:10 +02:00

702 lines
20 KiB
Python

#
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# SPDX-License-Identifier: Apache-2.0
"""Bandit is a tool designed to find common security issues in Python code."""
import argparse
import fnmatch
import logging
import os
import sys
import textwrap
import bandit
from bandit.core import config as b_config
from bandit.core import constants
from bandit.core import manager as b_manager
from bandit.core import utils
BASE_CONFIG = "bandit.yaml"
LOG = logging.getLogger()
def _init_logger(log_level=logging.INFO, log_format=None):
"""Initialize the logger.
:param debug: Whether to enable debug mode
:return: An instantiated logging instance
"""
LOG.handlers = []
if not log_format:
# default log format
log_format_string = constants.log_format_string
else:
log_format_string = log_format
logging.captureWarnings(True)
LOG.setLevel(log_level)
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(logging.Formatter(log_format_string))
LOG.addHandler(handler)
LOG.debug("logging initialized")
def _get_options_from_ini(ini_path, target):
"""Return a dictionary of config options or None if we can't load any."""
ini_file = None
if ini_path:
ini_file = ini_path
else:
bandit_files = []
for t in target:
for root, _, filenames in os.walk(t):
for filename in fnmatch.filter(filenames, ".bandit"):
bandit_files.append(os.path.join(root, filename))
if len(bandit_files) > 1:
LOG.error(
"Multiple .bandit files found - scan separately or "
"choose one with --ini\n\t%s",
", ".join(bandit_files),
)
sys.exit(2)
elif len(bandit_files) == 1:
ini_file = bandit_files[0]
LOG.info("Found project level .bandit file: %s", bandit_files[0])
if ini_file:
return utils.parse_ini_file(ini_file)
else:
return None
def _init_extensions():
from bandit.core import extension_loader as ext_loader
return ext_loader.MANAGER
def _log_option_source(default_val, arg_val, ini_val, option_name):
"""It's useful to show the source of each option."""
# When default value is not defined, arg_val and ini_val is deterministic
if default_val is None:
if arg_val:
LOG.info("Using command line arg for %s", option_name)
return arg_val
elif ini_val:
LOG.info("Using ini file for %s", option_name)
return ini_val
else:
return None
# No value passed to commad line and default value is used
elif default_val == arg_val:
return ini_val if ini_val else arg_val
# Certainly a value is passed to commad line
else:
return arg_val
def _running_under_virtualenv():
if hasattr(sys, "real_prefix"):
return True
elif sys.prefix != getattr(sys, "base_prefix", sys.prefix):
return True
def _get_profile(config, profile_name, config_path):
profile = {}
if profile_name:
profiles = config.get_option("profiles") or {}
profile = profiles.get(profile_name)
if profile is None:
raise utils.ProfileNotFound(config_path, profile_name)
LOG.debug("read in legacy profile '%s': %s", profile_name, profile)
else:
profile["include"] = set(config.get_option("tests") or [])
profile["exclude"] = set(config.get_option("skips") or [])
return profile
def _log_info(args, profile):
inc = ",".join([t for t in profile["include"]]) or "None"
exc = ",".join([t for t in profile["exclude"]]) or "None"
LOG.info("profile include tests: %s", inc)
LOG.info("profile exclude tests: %s", exc)
LOG.info("cli include tests: %s", args.tests)
LOG.info("cli exclude tests: %s", args.skips)
def main():
"""Bandit CLI."""
# bring our logging stuff up as early as possible
debug = (
logging.DEBUG
if "-d" in sys.argv or "--debug" in sys.argv
else logging.INFO
)
_init_logger(debug)
extension_mgr = _init_extensions()
baseline_formatters = [
f.name
for f in filter(
lambda x: hasattr(x.plugin, "_accepts_baseline"),
extension_mgr.formatters,
)
]
# now do normal startup
parser = argparse.ArgumentParser(
description="Bandit - a Python source code security analyzer",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
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(
"-r",
"--recursive",
dest="recursive",
action="store_true",
help="find and process files in subdirectories",
)
parser.add_argument(
"-a",
"--aggregate",
dest="agg_type",
action="store",
default="file",
type=str,
choices=["file", "vuln"],
help="aggregate output by vulnerability (default) or by filename",
)
parser.add_argument(
"-n",
"--number",
dest="context_lines",
action="store",
default=3,
type=int,
help="maximum number of code lines to output for each issue",
)
parser.add_argument(
"-c",
"--configfile",
dest="config_file",
action="store",
default=None,
type=str,
help="optional config file to use for selecting plugins and "
"overriding defaults",
)
parser.add_argument(
"-p",
"--profile",
dest="profile",
action="store",
default=None,
type=str,
help="profile to use (defaults to executing all tests)",
)
parser.add_argument(
"-t",
"--tests",
dest="tests",
action="store",
default=None,
type=str,
help="comma-separated list of test IDs to run",
)
parser.add_argument(
"-s",
"--skip",
dest="skips",
action="store",
default=None,
type=str,
help="comma-separated list of test IDs to skip",
)
severity_group = parser.add_mutually_exclusive_group(required=False)
severity_group.add_argument(
"-l",
"--level",
dest="severity",
action="count",
default=1,
help="report only issues of a given severity level or "
"higher (-l for LOW, -ll for MEDIUM, -lll for HIGH)",
)
severity_group.add_argument(
"--severity-level",
dest="severity_string",
action="store",
help="report only issues of a given severity level or higher."
' "all" and "low" are likely to produce the same results, but it'
" is possible for rules to be undefined which will"
' not be listed in "low".',
choices=["all", "low", "medium", "high"],
)
confidence_group = parser.add_mutually_exclusive_group(required=False)
confidence_group.add_argument(
"-i",
"--confidence",
dest="confidence",
action="count",
default=1,
help="report only issues of a given confidence level or "
"higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)",
)
confidence_group.add_argument(
"--confidence-level",
dest="confidence_string",
action="store",
help="report only issues of a given confidence level or higher."
' "all" and "low" are likely to produce the same results, but it'
" is possible for rules to be undefined which will"
' not be listed in "low".',
choices=["all", "low", "medium", "high"],
)
output_format = (
"screen"
if (
sys.stdout.isatty()
and os.getenv("NO_COLOR") is None
and os.getenv("TERM") != "dumb"
)
else "txt"
)
parser.add_argument(
"-f",
"--format",
dest="output_format",
action="store",
default=output_format,
help="specify output format",
choices=sorted(extension_mgr.formatter_names),
)
parser.add_argument(
"--msg-template",
action="store",
default=None,
help="specify output message template"
" (only usable with --format custom),"
" see CUSTOM FORMAT section"
" for list of available values",
)
parser.add_argument(
"-o",
"--output",
dest="output_file",
action="store",
nargs="?",
type=argparse.FileType("w", encoding="utf-8"),
default=sys.stdout,
help="write report to filename",
)
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument(
"-v",
"--verbose",
dest="verbose",
action="store_true",
help="output extra information like excluded and included files",
)
parser.add_argument(
"-d",
"--debug",
dest="debug",
action="store_true",
help="turn on debug mode",
)
group.add_argument(
"-q",
"--quiet",
"--silent",
dest="quiet",
action="store_true",
help="only show output in the case of an error",
)
parser.add_argument(
"--ignore-nosec",
dest="ignore_nosec",
action="store_true",
help="do not skip lines with # nosec comments",
)
parser.add_argument(
"-x",
"--exclude",
dest="excluded_paths",
action="store",
default=",".join(constants.EXCLUDE),
help="comma-separated list of paths (glob patterns "
"supported) to exclude from scan "
"(note that these are in addition to the excluded "
"paths provided in the config file) (default: "
+ ",".join(constants.EXCLUDE)
+ ")",
)
parser.add_argument(
"-b",
"--baseline",
dest="baseline",
action="store",
default=None,
help="path of a baseline report to compare against "
"(only JSON-formatted files are accepted)",
)
parser.add_argument(
"--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",
)
python_ver = sys.version.replace("\n", "")
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {bandit.__version__}\n"
f" python version = {python_ver}",
)
parser.set_defaults(debug=False)
parser.set_defaults(verbose=False)
parser.set_defaults(quiet=False)
parser.set_defaults(ignore_nosec=False)
plugin_info = [
f"{a[0]}\t{a[1].name}" for a in extension_mgr.plugins_by_id.items()
]
blacklist_info = []
for a in extension_mgr.blacklist.items():
for b in a[1]:
blacklist_info.append(f"{b['id']}\t{b['name']}")
plugin_list = "\n\t".join(sorted(set(plugin_info + blacklist_info)))
dedent_text = textwrap.dedent(
"""
CUSTOM FORMATTING
-----------------
Available tags:
{abspath}, {relpath}, {line}, {col}, {test_id},
{severity}, {msg}, {confidence}, {range}
Example usage:
Default template:
bandit -r examples/ --format custom --msg-template \\
"{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
Provides same output as:
bandit -r examples/ --format custom
Tags can also be formatted in python string.format() style:
bandit -r examples/ --format custom --msg-template \\
"{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
See python documentation for more information about formatting style:
https://docs.python.org/3/library/string.html
The following tests were discovered and loaded:
-----------------------------------------------
"""
)
parser.epilog = dedent_text + f"\t{plugin_list}"
# setup work - parse arguments, and initialize BanditManager
args = parser.parse_args()
# Check if `--msg-template` is not present without custom formatter
if args.output_format != "custom" and args.msg_template is not None:
parser.error("--msg-template can only be used with --format=custom")
# Check if confidence or severity level have been specified with strings
if args.severity_string is not None:
if args.severity_string == "all":
args.severity = 1
elif args.severity_string == "low":
args.severity = 2
elif args.severity_string == "medium":
args.severity = 3
elif args.severity_string == "high":
args.severity = 4
# Other strings will be blocked by argparse
if args.confidence_string is not None:
if args.confidence_string == "all":
args.confidence = 1
elif args.confidence_string == "low":
args.confidence = 2
elif args.confidence_string == "medium":
args.confidence = 3
elif args.confidence_string == "high":
args.confidence = 4
# Other strings will be blocked by argparse
# Handle .bandit files in projects to pass cmdline args from file
ini_options = _get_options_from_ini(args.ini_path, args.targets)
if ini_options:
# prefer command line, then ini file
args.config_file = _log_option_source(
parser.get_default("configfile"),
args.config_file,
ini_options.get("configfile"),
"config file",
)
args.excluded_paths = _log_option_source(
parser.get_default("excluded_paths"),
args.excluded_paths,
ini_options.get("exclude"),
"excluded paths",
)
args.skips = _log_option_source(
parser.get_default("skips"),
args.skips,
ini_options.get("skips"),
"skipped tests",
)
args.tests = _log_option_source(
parser.get_default("tests"),
args.tests,
ini_options.get("tests"),
"selected tests",
)
ini_targets = ini_options.get("targets")
if ini_targets:
ini_targets = ini_targets.split(",")
args.targets = _log_option_source(
parser.get_default("targets"),
args.targets,
ini_targets,
"selected targets",
)
# TODO(tmcpeak): any other useful options to pass from .bandit?
args.recursive = _log_option_source(
parser.get_default("recursive"),
args.recursive,
ini_options.get("recursive"),
"recursive scan",
)
args.agg_type = _log_option_source(
parser.get_default("agg_type"),
args.agg_type,
ini_options.get("aggregate"),
"aggregate output type",
)
args.context_lines = _log_option_source(
parser.get_default("context_lines"),
args.context_lines,
int(ini_options.get("number") or 0) or None,
"max code lines output for issue",
)
args.profile = _log_option_source(
parser.get_default("profile"),
args.profile,
ini_options.get("profile"),
"profile",
)
args.severity = _log_option_source(
parser.get_default("severity"),
args.severity,
ini_options.get("level"),
"severity level",
)
args.confidence = _log_option_source(
parser.get_default("confidence"),
args.confidence,
ini_options.get("confidence"),
"confidence level",
)
args.output_format = _log_option_source(
parser.get_default("output_format"),
args.output_format,
ini_options.get("format"),
"output format",
)
args.msg_template = _log_option_source(
parser.get_default("msg_template"),
args.msg_template,
ini_options.get("msg-template"),
"output message template",
)
args.output_file = _log_option_source(
parser.get_default("output_file"),
args.output_file,
ini_options.get("output"),
"output file",
)
args.verbose = _log_option_source(
parser.get_default("verbose"),
args.verbose,
ini_options.get("verbose"),
"output extra information",
)
args.debug = _log_option_source(
parser.get_default("debug"),
args.debug,
ini_options.get("debug"),
"debug mode",
)
args.quiet = _log_option_source(
parser.get_default("quiet"),
args.quiet,
ini_options.get("quiet"),
"silent mode",
)
args.ignore_nosec = _log_option_source(
parser.get_default("ignore_nosec"),
args.ignore_nosec,
ini_options.get("ignore-nosec"),
"do not skip lines with # nosec",
)
args.baseline = _log_option_source(
parser.get_default("baseline"),
args.baseline,
ini_options.get("baseline"),
"path of a baseline report",
)
try:
b_conf = b_config.BanditConfig(config_file=args.config_file)
except utils.ConfigError as e:
LOG.error(e)
sys.exit(2)
if not args.targets:
parser.print_usage()
sys.exit(2)
# if the log format string was set in the options, reinitialize
if b_conf.get_option("log_format"):
log_format = b_conf.get_option("log_format")
_init_logger(log_level=logging.DEBUG, log_format=log_format)
if args.quiet:
_init_logger(log_level=logging.WARN)
try:
profile = _get_profile(b_conf, args.profile, args.config_file)
_log_info(args, profile)
profile["include"].update(args.tests.split(",") if args.tests else [])
profile["exclude"].update(args.skips.split(",") if args.skips else [])
extension_mgr.validate_profile(profile)
except (utils.ProfileNotFound, ValueError) as e:
LOG.error(e)
sys.exit(2)
b_mgr = b_manager.BanditManager(
b_conf,
args.agg_type,
args.debug,
profile=profile,
verbose=args.verbose,
quiet=args.quiet,
ignore_nosec=args.ignore_nosec,
)
if args.baseline is not None:
try:
with open(args.baseline) as bl:
data = bl.read()
b_mgr.populate_baseline(data)
except OSError:
LOG.warning("Could not open baseline report: %s", args.baseline)
sys.exit(2)
if args.output_format not in baseline_formatters:
LOG.warning(
"Baseline must be used with one of the following "
"formats: " + str(baseline_formatters)
)
sys.exit(2)
if args.output_format != "json":
if args.config_file:
LOG.info("using config: %s", args.config_file)
LOG.info(
"running on Python %d.%d.%d",
sys.version_info.major,
sys.version_info.minor,
sys.version_info.micro,
)
# initiate file discovery step within Bandit Manager
b_mgr.discover_files(args.targets, args.recursive, args.excluded_paths)
if not b_mgr.b_ts.tests:
LOG.error("No tests would be run, please check the profile.")
sys.exit(2)
# initiate execution of tests within Bandit Manager
b_mgr.run_tests()
LOG.debug(b_mgr.b_ma)
LOG.debug(b_mgr.metrics)
# trigger output of results by Bandit Manager
sev_level = constants.RANKING[args.severity - 1]
conf_level = constants.RANKING[args.confidence - 1]
b_mgr.output_results(
args.context_lines,
sev_level,
conf_level,
args.output_file,
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
):
sys.exit(1)
else:
sys.exit(0)
if __name__ == "__main__":
main()