screen.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. # Copyright (c) 2015 Hewlett Packard Enterprise
  2. #
  3. # SPDX-License-Identifier: Apache-2.0
  4. r"""
  5. ================
  6. Screen formatter
  7. ================
  8. This formatter outputs the issues as color coded text to screen.
  9. :Example:
  10. .. code-block:: none
  11. >> Issue: [B506: yaml_load] Use of unsafe yaml load. Allows
  12. instantiation of arbitrary objects. Consider yaml.safe_load().
  13. Severity: Medium Confidence: High
  14. CWE: CWE-20 (https://cwe.mitre.org/data/definitions/20.html)
  15. More Info: https://bandit.readthedocs.io/en/latest/
  16. Location: examples/yaml_load.py:5
  17. 4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
  18. 5 y = yaml.load(ystr)
  19. 6 yaml.dump(y)
  20. .. versionadded:: 0.9.0
  21. .. versionchanged:: 1.5.0
  22. New field `more_info` added to output
  23. .. versionchanged:: 1.7.3
  24. New field `CWE` added to output
  25. """
  26. import datetime
  27. import logging
  28. import sys
  29. from bandit.core import constants
  30. from bandit.core import docs_utils
  31. from bandit.core import test_properties
  32. IS_WIN_PLATFORM = sys.platform.startswith("win32")
  33. COLORAMA = False
  34. # This fixes terminal colors not displaying properly on Windows systems.
  35. # Colorama will intercept any ANSI escape codes and convert them to the
  36. # proper Windows console API calls to change text color.
  37. if IS_WIN_PLATFORM:
  38. try:
  39. import colorama
  40. except ImportError:
  41. pass
  42. else:
  43. COLORAMA = True
  44. LOG = logging.getLogger(__name__)
  45. COLOR = {
  46. "DEFAULT": "\033[0m",
  47. "HEADER": "\033[95m",
  48. "LOW": "\033[94m",
  49. "MEDIUM": "\033[93m",
  50. "HIGH": "\033[91m",
  51. }
  52. def header(text, *args):
  53. return "{}{}{}".format(COLOR["HEADER"], (text % args), COLOR["DEFAULT"])
  54. def get_verbose_details(manager):
  55. bits = []
  56. bits.append(header("Files in scope (%i):", len(manager.files_list)))
  57. tpl = "\t%s (score: {SEVERITY: %i, CONFIDENCE: %i})"
  58. bits.extend(
  59. [
  60. tpl % (item, sum(score["SEVERITY"]), sum(score["CONFIDENCE"]))
  61. for (item, score) in zip(manager.files_list, manager.scores)
  62. ]
  63. )
  64. bits.append(header("Files excluded (%i):", len(manager.excluded_files)))
  65. bits.extend(["\t%s" % fname for fname in manager.excluded_files])
  66. return "\n".join([str(bit) for bit in bits])
  67. def get_metrics(manager):
  68. bits = []
  69. bits.append(header("\nRun metrics:"))
  70. for (criteria, _) in constants.CRITERIA:
  71. bits.append("\tTotal issues (by %s):" % (criteria.lower()))
  72. for rank in constants.RANKING:
  73. bits.append(
  74. "\t\t%s: %s"
  75. % (
  76. rank.capitalize(),
  77. manager.metrics.data["_totals"][f"{criteria}.{rank}"],
  78. )
  79. )
  80. return "\n".join([str(bit) for bit in bits])
  81. def _output_issue_str(
  82. issue, indent, show_lineno=True, show_code=True, lines=-1
  83. ):
  84. # returns a list of lines that should be added to the existing lines list
  85. bits = []
  86. bits.append(
  87. "%s%s>> Issue: [%s:%s] %s"
  88. % (
  89. indent,
  90. COLOR[issue.severity],
  91. issue.test_id,
  92. issue.test,
  93. issue.text,
  94. )
  95. )
  96. bits.append(
  97. "%s Severity: %s Confidence: %s"
  98. % (
  99. indent,
  100. issue.severity.capitalize(),
  101. issue.confidence.capitalize(),
  102. )
  103. )
  104. bits.append(f"{indent} CWE: {str(issue.cwe)}")
  105. bits.append(f"{indent} More Info: {docs_utils.get_url(issue.test_id)}")
  106. bits.append(
  107. "%s Location: %s:%s:%s%s"
  108. % (
  109. indent,
  110. issue.fname,
  111. issue.lineno if show_lineno else "",
  112. issue.col_offset if show_lineno else "",
  113. COLOR["DEFAULT"],
  114. )
  115. )
  116. if show_code:
  117. bits.extend(
  118. [indent + line for line in issue.get_code(lines, True).split("\n")]
  119. )
  120. return "\n".join([bit for bit in bits])
  121. def get_results(manager, sev_level, conf_level, lines):
  122. bits = []
  123. issues = manager.get_issue_list(sev_level, conf_level)
  124. baseline = not isinstance(issues, list)
  125. candidate_indent = " " * 10
  126. if not len(issues):
  127. return "\tNo issues identified."
  128. for issue in issues:
  129. # if not a baseline or only one candidate we know the issue
  130. if not baseline or len(issues[issue]) == 1:
  131. bits.append(_output_issue_str(issue, "", lines=lines))
  132. # otherwise show the finding and the candidates
  133. else:
  134. bits.append(
  135. _output_issue_str(
  136. issue, "", show_lineno=False, show_code=False
  137. )
  138. )
  139. bits.append("\n-- Candidate Issues --")
  140. for candidate in issues[issue]:
  141. bits.append(
  142. _output_issue_str(candidate, candidate_indent, lines=lines)
  143. )
  144. bits.append("\n")
  145. bits.append("-" * 50)
  146. return "\n".join([bit for bit in bits])
  147. def do_print(bits):
  148. # needed so we can mock this stuff
  149. print("\n".join([bit for bit in bits]))
  150. @test_properties.accepts_baseline
  151. def report(manager, fileobj, sev_level, conf_level, lines=-1):
  152. """Prints discovered issues formatted for screen reading
  153. This makes use of VT100 terminal codes for colored text.
  154. :param manager: the bandit manager object
  155. :param fileobj: The output file object, which may be sys.stdout
  156. :param sev_level: Filtering severity level
  157. :param conf_level: Filtering confidence level
  158. :param lines: Number of lines to report, -1 for all
  159. """
  160. if IS_WIN_PLATFORM and COLORAMA:
  161. colorama.init()
  162. bits = []
  163. if not manager.quiet or manager.results_count(sev_level, conf_level):
  164. bits.append(header("Run started:%s", datetime.datetime.utcnow()))
  165. if manager.verbose:
  166. bits.append(get_verbose_details(manager))
  167. bits.append(header("\nTest results:"))
  168. bits.append(get_results(manager, sev_level, conf_level, lines))
  169. bits.append(header("\nCode scanned:"))
  170. bits.append(
  171. "\tTotal lines of code: %i"
  172. % (manager.metrics.data["_totals"]["loc"])
  173. )
  174. bits.append(
  175. "\tTotal lines skipped (#nosec): %i"
  176. % (manager.metrics.data["_totals"]["nosec"])
  177. )
  178. bits.append(get_metrics(manager))
  179. skipped = manager.get_skipped()
  180. bits.append(header("Files skipped (%i):", len(skipped)))
  181. bits.extend(["\t%s (%s)" % skip for skip in skipped])
  182. do_print(bits)
  183. if fileobj.name != sys.stdout.name:
  184. LOG.info(
  185. "Screen formatter output was not written to file: %s, "
  186. "consider '-f txt'",
  187. fileobj.name,
  188. )
  189. if IS_WIN_PLATFORM and COLORAMA:
  190. colorama.deinit()