text.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. # Copyright (c) 2015 Hewlett Packard Enterprise
  2. #
  3. # SPDX-License-Identifier: Apache-2.0
  4. r"""
  5. ==============
  6. Text Formatter
  7. ==============
  8. This formatter outputs the issues as plain text.
  9. :Example:
  10. .. code-block:: none
  11. >> Issue: [B301:blacklist_calls] 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. from bandit.formatters import utils
  33. LOG = logging.getLogger(__name__)
  34. def get_verbose_details(manager):
  35. bits = []
  36. bits.append("Files in scope (%i):" % len(manager.files_list))
  37. tpl = "\t%s (score: {SEVERITY: %i, CONFIDENCE: %i})"
  38. bits.extend(
  39. [
  40. tpl % (item, sum(score["SEVERITY"]), sum(score["CONFIDENCE"]))
  41. for (item, score) in zip(manager.files_list, manager.scores)
  42. ]
  43. )
  44. bits.append("Files excluded (%i):" % len(manager.excluded_files))
  45. bits.extend(["\t%s" % fname for fname in manager.excluded_files])
  46. return "\n".join([bit for bit in bits])
  47. def get_metrics(manager):
  48. bits = []
  49. bits.append("\nRun metrics:")
  50. for (criteria, _) in constants.CRITERIA:
  51. bits.append("\tTotal issues (by %s):" % (criteria.lower()))
  52. for rank in constants.RANKING:
  53. bits.append(
  54. "\t\t%s: %s"
  55. % (
  56. rank.capitalize(),
  57. manager.metrics.data["_totals"][f"{criteria}.{rank}"],
  58. )
  59. )
  60. return "\n".join([bit for bit in bits])
  61. def _output_issue_str(
  62. issue, indent, show_lineno=True, show_code=True, lines=-1
  63. ):
  64. # returns a list of lines that should be added to the existing lines list
  65. bits = []
  66. bits.append(
  67. "%s>> Issue: [%s:%s] %s"
  68. % (indent, issue.test_id, issue.test, issue.text)
  69. )
  70. bits.append(
  71. "%s Severity: %s Confidence: %s"
  72. % (
  73. indent,
  74. issue.severity.capitalize(),
  75. issue.confidence.capitalize(),
  76. )
  77. )
  78. bits.append(f"{indent} CWE: {str(issue.cwe)}")
  79. bits.append(f"{indent} More Info: {docs_utils.get_url(issue.test_id)}")
  80. bits.append(
  81. "%s Location: %s:%s:%s"
  82. % (
  83. indent,
  84. issue.fname,
  85. issue.lineno if show_lineno else "",
  86. issue.col_offset if show_lineno else "",
  87. )
  88. )
  89. if show_code:
  90. bits.extend(
  91. [indent + line for line in issue.get_code(lines, True).split("\n")]
  92. )
  93. return "\n".join([bit for bit in bits])
  94. def get_results(manager, sev_level, conf_level, lines):
  95. bits = []
  96. issues = manager.get_issue_list(sev_level, conf_level)
  97. baseline = not isinstance(issues, list)
  98. candidate_indent = " " * 10
  99. if not len(issues):
  100. return "\tNo issues identified."
  101. for issue in issues:
  102. # if not a baseline or only one candidate we know the issue
  103. if not baseline or len(issues[issue]) == 1:
  104. bits.append(_output_issue_str(issue, "", lines=lines))
  105. # otherwise show the finding and the candidates
  106. else:
  107. bits.append(
  108. _output_issue_str(
  109. issue, "", show_lineno=False, show_code=False
  110. )
  111. )
  112. bits.append("\n-- Candidate Issues --")
  113. for candidate in issues[issue]:
  114. bits.append(
  115. _output_issue_str(candidate, candidate_indent, lines=lines)
  116. )
  117. bits.append("\n")
  118. bits.append("-" * 50)
  119. return "\n".join([bit for bit in bits])
  120. @test_properties.accepts_baseline
  121. def report(manager, fileobj, sev_level, conf_level, lines=-1):
  122. """Prints discovered issues in the text format
  123. :param manager: the bandit manager object
  124. :param fileobj: The output file object, which may be sys.stdout
  125. :param sev_level: Filtering severity level
  126. :param conf_level: Filtering confidence level
  127. :param lines: Number of lines to report, -1 for all
  128. """
  129. bits = []
  130. if not manager.quiet or manager.results_count(sev_level, conf_level):
  131. bits.append("Run started:%s" % datetime.datetime.utcnow())
  132. if manager.verbose:
  133. bits.append(get_verbose_details(manager))
  134. bits.append("\nTest results:")
  135. bits.append(get_results(manager, sev_level, conf_level, lines))
  136. bits.append("\nCode scanned:")
  137. bits.append(
  138. "\tTotal lines of code: %i"
  139. % (manager.metrics.data["_totals"]["loc"])
  140. )
  141. bits.append(
  142. "\tTotal lines skipped (#nosec): %i"
  143. % (manager.metrics.data["_totals"]["nosec"])
  144. )
  145. bits.append(
  146. "\tTotal potential issues skipped due to specifically being "
  147. "disabled (e.g., #nosec BXXX): %i"
  148. % (manager.metrics.data["_totals"]["skipped_tests"])
  149. )
  150. skipped = manager.get_skipped()
  151. bits.append(get_metrics(manager))
  152. bits.append("Files skipped (%i):" % len(skipped))
  153. bits.extend(["\t%s (%s)" % skip for skip in skipped])
  154. result = "\n".join([bit for bit in bits]) + "\n"
  155. with fileobj:
  156. wrapped_file = utils.wrap_file_object(fileobj)
  157. wrapped_file.write(result)
  158. if fileobj.name != sys.stdout.name:
  159. LOG.info("Text output written to file: %s", fileobj.name)