content: #
  # 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()
  