html.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. # Copyright (c) 2015 Rackspace, Inc.
  2. # Copyright (c) 2015 Hewlett Packard Enterprise
  3. #
  4. # SPDX-License-Identifier: Apache-2.0
  5. r"""
  6. ==============
  7. HTML formatter
  8. ==============
  9. This formatter outputs the issues as HTML.
  10. :Example:
  11. .. code-block:: html
  12. <!DOCTYPE html>
  13. <html>
  14. <head>
  15. <meta charset="UTF-8">
  16. <title>
  17. Bandit Report
  18. </title>
  19. <style>
  20. html * {
  21. font-family: "Arial", sans-serif;
  22. }
  23. pre {
  24. font-family: "Monaco", monospace;
  25. }
  26. .bordered-box {
  27. border: 1px solid black;
  28. padding-top:.5em;
  29. padding-bottom:.5em;
  30. padding-left:1em;
  31. }
  32. .metrics-box {
  33. font-size: 1.1em;
  34. line-height: 130%;
  35. }
  36. .metrics-title {
  37. font-size: 1.5em;
  38. font-weight: 500;
  39. margin-bottom: .25em;
  40. }
  41. .issue-description {
  42. font-size: 1.3em;
  43. font-weight: 500;
  44. }
  45. .candidate-issues {
  46. margin-left: 2em;
  47. border-left: solid 1px; LightGray;
  48. padding-left: 5%;
  49. margin-top: .2em;
  50. margin-bottom: .2em;
  51. }
  52. .issue-block {
  53. border: 1px solid LightGray;
  54. padding-left: .5em;
  55. padding-top: .5em;
  56. padding-bottom: .5em;
  57. margin-bottom: .5em;
  58. }
  59. .issue-sev-high {
  60. background-color: Pink;
  61. }
  62. .issue-sev-medium {
  63. background-color: NavajoWhite;
  64. }
  65. .issue-sev-low {
  66. background-color: LightCyan;
  67. }
  68. </style>
  69. </head>
  70. <body>
  71. <div id="metrics">
  72. <div class="metrics-box bordered-box">
  73. <div class="metrics-title">
  74. Metrics:<br>
  75. </div>
  76. Total lines of code: <span id="loc">9</span><br>
  77. Total lines skipped (#nosec): <span id="nosec">0</span>
  78. </div>
  79. </div>
  80. <br>
  81. <div id="results">
  82. <div id="issue-0">
  83. <div class="issue-block issue-sev-medium">
  84. <b>yaml_load: </b> Use of unsafe yaml load. Allows
  85. instantiation of arbitrary objects. Consider yaml.safe_load().<br>
  86. <b>Test ID:</b> B506<br>
  87. <b>Severity: </b>MEDIUM<br>
  88. <b>Confidence: </b>HIGH<br>
  89. <b>CWE: </b>CWE-20 (https://cwe.mitre.org/data/definitions/20.html)<br>
  90. <b>File: </b><a href="examples/yaml_load.py"
  91. target="_blank">examples/yaml_load.py</a> <br>
  92. <b>More info: </b><a href="https://bandit.readthedocs.io/en/latest/
  93. plugins/yaml_load.html" target="_blank">
  94. https://bandit.readthedocs.io/en/latest/plugins/yaml_load.html</a>
  95. <br>
  96. <div class="code">
  97. <pre>
  98. 5 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
  99. 6 y = yaml.load(ystr)
  100. 7 yaml.dump(y)
  101. </pre>
  102. </div>
  103. </div>
  104. </div>
  105. </div>
  106. </body>
  107. </html>
  108. .. versionadded:: 0.14.0
  109. .. versionchanged:: 1.5.0
  110. New field `more_info` added to output
  111. .. versionchanged:: 1.7.3
  112. New field `CWE` added to output
  113. """
  114. import logging
  115. import sys
  116. from html import escape as html_escape
  117. from bandit.core import docs_utils
  118. from bandit.core import test_properties
  119. from bandit.formatters import utils
  120. LOG = logging.getLogger(__name__)
  121. @test_properties.accepts_baseline
  122. def report(manager, fileobj, sev_level, conf_level, lines=-1):
  123. """Writes issues to 'fileobj' in HTML format
  124. :param manager: the bandit manager object
  125. :param fileobj: The output file object, which may be sys.stdout
  126. :param sev_level: Filtering severity level
  127. :param conf_level: Filtering confidence level
  128. :param lines: Number of lines to report, -1 for all
  129. """
  130. header_block = """
  131. <!DOCTYPE html>
  132. <html>
  133. <head>
  134. <meta charset="UTF-8">
  135. <title>
  136. Bandit Report
  137. </title>
  138. <style>
  139. html * {
  140. font-family: "Arial", sans-serif;
  141. }
  142. pre {
  143. font-family: "Monaco", monospace;
  144. }
  145. .bordered-box {
  146. border: 1px solid black;
  147. padding-top:.5em;
  148. padding-bottom:.5em;
  149. padding-left:1em;
  150. }
  151. .metrics-box {
  152. font-size: 1.1em;
  153. line-height: 130%;
  154. }
  155. .metrics-title {
  156. font-size: 1.5em;
  157. font-weight: 500;
  158. margin-bottom: .25em;
  159. }
  160. .issue-description {
  161. font-size: 1.3em;
  162. font-weight: 500;
  163. }
  164. .candidate-issues {
  165. margin-left: 2em;
  166. border-left: solid 1px; LightGray;
  167. padding-left: 5%;
  168. margin-top: .2em;
  169. margin-bottom: .2em;
  170. }
  171. .issue-block {
  172. border: 1px solid LightGray;
  173. padding-left: .5em;
  174. padding-top: .5em;
  175. padding-bottom: .5em;
  176. margin-bottom: .5em;
  177. }
  178. .issue-sev-high {
  179. background-color: Pink;
  180. }
  181. .issue-sev-medium {
  182. background-color: NavajoWhite;
  183. }
  184. .issue-sev-low {
  185. background-color: LightCyan;
  186. }
  187. </style>
  188. </head>
  189. """
  190. report_block = """
  191. <body>
  192. {metrics}
  193. {skipped}
  194. <br>
  195. <div id="results">
  196. {results}
  197. </div>
  198. </body>
  199. </html>
  200. """
  201. issue_block = """
  202. <div id="issue-{issue_no}">
  203. <div class="issue-block {issue_class}">
  204. <b>{test_name}: </b> {test_text}<br>
  205. <b>Test ID:</b> {test_id}<br>
  206. <b>Severity: </b>{severity}<br>
  207. <b>Confidence: </b>{confidence}<br>
  208. <b>CWE: </b><a href="{cwe_link}" target="_blank">CWE-{cwe.id}</a><br>
  209. <b>File: </b><a href="{path}" target="_blank">{path}</a><br>
  210. <b>Line number: </b>{line_number}<br>
  211. <b>More info: </b><a href="{url}" target="_blank">{url}</a><br>
  212. {code}
  213. {candidates}
  214. </div>
  215. </div>
  216. """
  217. code_block = """
  218. <div class="code">
  219. <pre>
  220. {code}
  221. </pre>
  222. </div>
  223. """
  224. candidate_block = """
  225. <div class="candidates">
  226. <br>
  227. <b>Candidates: </b>
  228. {candidate_list}
  229. </div>
  230. """
  231. candidate_issue = """
  232. <div class="candidate">
  233. <div class="candidate-issues">
  234. <pre>{code}</pre>
  235. </div>
  236. </div>
  237. """
  238. skipped_block = """
  239. <br>
  240. <div id="skipped">
  241. <div class="bordered-box">
  242. <b>Skipped files:</b><br><br>
  243. {files_list}
  244. </div>
  245. </div>
  246. """
  247. metrics_block = """
  248. <div id="metrics">
  249. <div class="metrics-box bordered-box">
  250. <div class="metrics-title">
  251. Metrics:<br>
  252. </div>
  253. Total lines of code: <span id="loc">{loc}</span><br>
  254. Total lines skipped (#nosec): <span id="nosec">{nosec}</span>
  255. </div>
  256. </div>
  257. """
  258. issues = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level)
  259. baseline = not isinstance(issues, list)
  260. # build the skipped string to insert in the report
  261. skipped_str = "".join(
  262. f"{fname} <b>reason:</b> {reason}<br>"
  263. for fname, reason in manager.get_skipped()
  264. )
  265. if skipped_str:
  266. skipped_text = skipped_block.format(files_list=skipped_str)
  267. else:
  268. skipped_text = ""
  269. # build the results string to insert in the report
  270. results_str = ""
  271. for index, issue in enumerate(issues):
  272. if not baseline or len(issues[issue]) == 1:
  273. candidates = ""
  274. safe_code = html_escape(
  275. issue.get_code(lines, True).strip("\n").lstrip(" ")
  276. )
  277. code = code_block.format(code=safe_code)
  278. else:
  279. candidates_str = ""
  280. code = ""
  281. for candidate in issues[issue]:
  282. candidate_code = html_escape(
  283. candidate.get_code(lines, True).strip("\n").lstrip(" ")
  284. )
  285. candidates_str += candidate_issue.format(code=candidate_code)
  286. candidates = candidate_block.format(candidate_list=candidates_str)
  287. url = docs_utils.get_url(issue.test_id)
  288. results_str += issue_block.format(
  289. issue_no=index,
  290. issue_class=f"issue-sev-{issue.severity.lower()}",
  291. test_name=issue.test,
  292. test_id=issue.test_id,
  293. test_text=issue.text,
  294. severity=issue.severity,
  295. confidence=issue.confidence,
  296. cwe=issue.cwe,
  297. cwe_link=issue.cwe.link(),
  298. path=issue.fname,
  299. code=code,
  300. candidates=candidates,
  301. url=url,
  302. line_number=issue.lineno,
  303. )
  304. # build the metrics string to insert in the report
  305. metrics_summary = metrics_block.format(
  306. loc=manager.metrics.data["_totals"]["loc"],
  307. nosec=manager.metrics.data["_totals"]["nosec"],
  308. )
  309. # build the report and output it
  310. report_contents = report_block.format(
  311. metrics=metrics_summary, skipped=skipped_text, results=results_str
  312. )
  313. with fileobj:
  314. wrapped_file = utils.wrap_file_object(fileobj)
  315. wrapped_file.write(header_block)
  316. wrapped_file.write(report_contents)
  317. if fileobj.name != sys.stdout.name:
  318. LOG.info("HTML output written to file: %s", fileobj.name)