updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
r"""
|
||||
=============
|
||||
CSV Formatter
|
||||
=============
|
||||
|
||||
This formatter outputs the issues in a comma separated values format.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
filename,test_name,test_id,issue_severity,issue_confidence,issue_cwe,
|
||||
issue_text,line_number,line_range,more_info
|
||||
examples/yaml_load.py,blacklist_calls,B301,MEDIUM,HIGH,
|
||||
https://cwe.mitre.org/data/definitions/20.html,"Use of unsafe yaml
|
||||
load. Allows instantiation of arbitrary objects. Consider yaml.safe_load().
|
||||
",5,[5],https://bandit.readthedocs.io/en/latest/
|
||||
|
||||
.. versionadded:: 0.11.0
|
||||
|
||||
.. versionchanged:: 1.5.0
|
||||
New field `more_info` added to output
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
# Necessary for this formatter to work when imported on Python 2. Importing
|
||||
# the standard library's csv module conflicts with the name of this module.
|
||||
import csv
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from bandit.core import docs_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""Prints issues in CSV format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
results = manager.get_issue_list(
|
||||
sev_level=sev_level, conf_level=conf_level
|
||||
)
|
||||
|
||||
with fileobj:
|
||||
fieldnames = [
|
||||
"filename",
|
||||
"test_name",
|
||||
"test_id",
|
||||
"issue_severity",
|
||||
"issue_confidence",
|
||||
"issue_cwe",
|
||||
"issue_text",
|
||||
"line_number",
|
||||
"col_offset",
|
||||
"end_col_offset",
|
||||
"line_range",
|
||||
"more_info",
|
||||
]
|
||||
|
||||
writer = csv.DictWriter(
|
||||
fileobj, fieldnames=fieldnames, extrasaction="ignore"
|
||||
)
|
||||
writer.writeheader()
|
||||
for result in results:
|
||||
r = result.as_dict(with_code=False)
|
||||
r["issue_cwe"] = r["issue_cwe"]["link"]
|
||||
r["more_info"] = docs_utils.get_url(r["test_id"])
|
||||
writer.writerow(r)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("CSV output written to file: %s", fileobj.name)
|
||||
@@ -0,0 +1,161 @@
|
||||
#
|
||||
# Copyright (c) 2017 Hewlett Packard Enterprise
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""
|
||||
================
|
||||
Custom Formatter
|
||||
================
|
||||
|
||||
This formatter outputs the issues in custom machine-readable format.
|
||||
|
||||
default template: ``{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}``
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/usr/lib/python3.6/site-packages/openlp/core/utils/__init__.py:\
|
||||
405: B310[bandit]: MEDIUM: Audit url open for permitted schemes. \
|
||||
Allowing use of file:/ or custom schemes is often unexpected.
|
||||
|
||||
.. versionadded:: 1.5.0
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
|
||||
from bandit.core import test_properties
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SafeMapper(dict):
|
||||
"""Safe mapper to handle format key errors"""
|
||||
|
||||
@classmethod # To prevent PEP8 warnings in the test suite
|
||||
def __missing__(cls, key):
|
||||
return "{%s}" % key
|
||||
|
||||
|
||||
@test_properties.accepts_baseline
|
||||
def report(manager, fileobj, sev_level, conf_level, template=None):
|
||||
"""Prints issues in custom format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param template: Output template with non-terminal tags <N>
|
||||
(default: '{abspath}:{line}:
|
||||
{test_id}[bandit]: {severity}: {msg}')
|
||||
"""
|
||||
|
||||
machine_output = {"results": [], "errors": []}
|
||||
for fname, reason in manager.get_skipped():
|
||||
machine_output["errors"].append({"filename": fname, "reason": reason})
|
||||
|
||||
results = manager.get_issue_list(
|
||||
sev_level=sev_level, conf_level=conf_level
|
||||
)
|
||||
|
||||
msg_template = template
|
||||
if template is None:
|
||||
msg_template = "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
|
||||
|
||||
# Dictionary of non-terminal tags that will be expanded
|
||||
tag_mapper = {
|
||||
"abspath": lambda issue: os.path.abspath(issue.fname),
|
||||
"relpath": lambda issue: os.path.relpath(issue.fname),
|
||||
"line": lambda issue: issue.lineno,
|
||||
"col": lambda issue: issue.col_offset,
|
||||
"end_col": lambda issue: issue.end_col_offset,
|
||||
"test_id": lambda issue: issue.test_id,
|
||||
"severity": lambda issue: issue.severity,
|
||||
"msg": lambda issue: issue.text,
|
||||
"confidence": lambda issue: issue.confidence,
|
||||
"range": lambda issue: issue.linerange,
|
||||
"cwe": lambda issue: issue.cwe,
|
||||
}
|
||||
|
||||
# Create dictionary with tag sets to speed up search for similar tags
|
||||
tag_sim_dict = {tag: set(tag) for tag, _ in tag_mapper.items()}
|
||||
|
||||
# Parse the format_string template and check the validity of tags
|
||||
try:
|
||||
parsed_template_orig = list(string.Formatter().parse(msg_template))
|
||||
# of type (literal_text, field_name, fmt_spec, conversion)
|
||||
|
||||
# Check the format validity only, ignore keys
|
||||
string.Formatter().vformat(msg_template, (), SafeMapper(line=0))
|
||||
except ValueError as e:
|
||||
LOG.error("Template is not in valid format: %s", e.args[0])
|
||||
sys.exit(2)
|
||||
|
||||
tag_set = {t[1] for t in parsed_template_orig if t[1] is not None}
|
||||
if not tag_set:
|
||||
LOG.error("No tags were found in the template. Are you missing '{}'?")
|
||||
sys.exit(2)
|
||||
|
||||
def get_similar_tag(tag):
|
||||
similarity_list = [
|
||||
(len(set(tag) & t_set), t) for t, t_set in tag_sim_dict.items()
|
||||
]
|
||||
return sorted(similarity_list)[-1][1]
|
||||
|
||||
tag_blacklist = []
|
||||
for tag in tag_set:
|
||||
# check if the tag is in dictionary
|
||||
if tag not in tag_mapper:
|
||||
similar_tag = get_similar_tag(tag)
|
||||
LOG.warning(
|
||||
"Tag '%s' was not recognized and will be skipped, "
|
||||
"did you mean to use '%s'?",
|
||||
tag,
|
||||
similar_tag,
|
||||
)
|
||||
tag_blacklist += [tag]
|
||||
|
||||
# Compose the message template back with the valid values only
|
||||
msg_parsed_template_list = []
|
||||
for literal_text, field_name, fmt_spec, conversion in parsed_template_orig:
|
||||
if literal_text:
|
||||
# if there is '{' or '}', double it to prevent expansion
|
||||
literal_text = re.sub("{", "{{", literal_text)
|
||||
literal_text = re.sub("}", "}}", literal_text)
|
||||
msg_parsed_template_list.append(literal_text)
|
||||
|
||||
if field_name is not None:
|
||||
if field_name in tag_blacklist:
|
||||
msg_parsed_template_list.append(field_name)
|
||||
continue
|
||||
# Append the fmt_spec part
|
||||
params = [field_name, fmt_spec, conversion]
|
||||
markers = ["", ":", "!"]
|
||||
msg_parsed_template_list.append(
|
||||
["{"]
|
||||
+ [f"{m + p}" if p else "" for m, p in zip(markers, params)]
|
||||
+ ["}"]
|
||||
)
|
||||
|
||||
msg_parsed_template = (
|
||||
"".join([item for lst in msg_parsed_template_list for item in lst])
|
||||
+ "\n"
|
||||
)
|
||||
with fileobj:
|
||||
for defect in results:
|
||||
evaluated_tags = SafeMapper(
|
||||
(k, v(defect)) for k, v in tag_mapper.items()
|
||||
)
|
||||
output = msg_parsed_template.format(**evaluated_tags)
|
||||
|
||||
fileobj.write(output)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("Result written to file: %s", fileobj.name)
|
||||
@@ -0,0 +1,394 @@
|
||||
# Copyright (c) 2015 Rackspace, Inc.
|
||||
# Copyright (c) 2015 Hewlett Packard Enterprise
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
r"""
|
||||
==============
|
||||
HTML formatter
|
||||
==============
|
||||
|
||||
This formatter outputs the issues as HTML.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<title>
|
||||
Bandit Report
|
||||
</title>
|
||||
|
||||
<style>
|
||||
|
||||
html * {
|
||||
font-family: "Arial", sans-serif;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: "Monaco", monospace;
|
||||
}
|
||||
|
||||
.bordered-box {
|
||||
border: 1px solid black;
|
||||
padding-top:.5em;
|
||||
padding-bottom:.5em;
|
||||
padding-left:1em;
|
||||
}
|
||||
|
||||
.metrics-box {
|
||||
font-size: 1.1em;
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
.metrics-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: 500;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
|
||||
.issue-description {
|
||||
font-size: 1.3em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.candidate-issues {
|
||||
margin-left: 2em;
|
||||
border-left: solid 1px; LightGray;
|
||||
padding-left: 5%;
|
||||
margin-top: .2em;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.issue-block {
|
||||
border: 1px solid LightGray;
|
||||
padding-left: .5em;
|
||||
padding-top: .5em;
|
||||
padding-bottom: .5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.issue-sev-high {
|
||||
background-color: Pink;
|
||||
}
|
||||
|
||||
.issue-sev-medium {
|
||||
background-color: NavajoWhite;
|
||||
}
|
||||
|
||||
.issue-sev-low {
|
||||
background-color: LightCyan;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="metrics">
|
||||
<div class="metrics-box bordered-box">
|
||||
<div class="metrics-title">
|
||||
Metrics:<br>
|
||||
</div>
|
||||
Total lines of code: <span id="loc">9</span><br>
|
||||
Total lines skipped (#nosec): <span id="nosec">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
<div id="results">
|
||||
|
||||
<div id="issue-0">
|
||||
<div class="issue-block issue-sev-medium">
|
||||
<b>yaml_load: </b> Use of unsafe yaml load. Allows
|
||||
instantiation of arbitrary objects. Consider yaml.safe_load().<br>
|
||||
<b>Test ID:</b> B506<br>
|
||||
<b>Severity: </b>MEDIUM<br>
|
||||
<b>Confidence: </b>HIGH<br>
|
||||
<b>CWE: </b>CWE-20 (https://cwe.mitre.org/data/definitions/20.html)<br>
|
||||
<b>File: </b><a href="examples/yaml_load.py"
|
||||
target="_blank">examples/yaml_load.py</a> <br>
|
||||
<b>More info: </b><a href="https://bandit.readthedocs.io/en/latest/
|
||||
plugins/yaml_load.html" target="_blank">
|
||||
https://bandit.readthedocs.io/en/latest/plugins/yaml_load.html</a>
|
||||
<br>
|
||||
|
||||
<div class="code">
|
||||
<pre>
|
||||
5 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
|
||||
6 y = yaml.load(ystr)
|
||||
7 yaml.dump(y)
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
.. versionadded:: 0.14.0
|
||||
|
||||
.. versionchanged:: 1.5.0
|
||||
New field `more_info` added to output
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
from html import escape as html_escape
|
||||
|
||||
from bandit.core import docs_utils
|
||||
from bandit.core import test_properties
|
||||
from bandit.formatters import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@test_properties.accepts_baseline
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""Writes issues to 'fileobj' in HTML format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
header_block = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<title>
|
||||
Bandit Report
|
||||
</title>
|
||||
|
||||
<style>
|
||||
|
||||
html * {
|
||||
font-family: "Arial", sans-serif;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: "Monaco", monospace;
|
||||
}
|
||||
|
||||
.bordered-box {
|
||||
border: 1px solid black;
|
||||
padding-top:.5em;
|
||||
padding-bottom:.5em;
|
||||
padding-left:1em;
|
||||
}
|
||||
|
||||
.metrics-box {
|
||||
font-size: 1.1em;
|
||||
line-height: 130%;
|
||||
}
|
||||
|
||||
.metrics-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: 500;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
|
||||
.issue-description {
|
||||
font-size: 1.3em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.candidate-issues {
|
||||
margin-left: 2em;
|
||||
border-left: solid 1px; LightGray;
|
||||
padding-left: 5%;
|
||||
margin-top: .2em;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.issue-block {
|
||||
border: 1px solid LightGray;
|
||||
padding-left: .5em;
|
||||
padding-top: .5em;
|
||||
padding-bottom: .5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.issue-sev-high {
|
||||
background-color: Pink;
|
||||
}
|
||||
|
||||
.issue-sev-medium {
|
||||
background-color: NavajoWhite;
|
||||
}
|
||||
|
||||
.issue-sev-low {
|
||||
background-color: LightCyan;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
"""
|
||||
|
||||
report_block = """
|
||||
<body>
|
||||
{metrics}
|
||||
{skipped}
|
||||
|
||||
<br>
|
||||
<div id="results">
|
||||
{results}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
issue_block = """
|
||||
<div id="issue-{issue_no}">
|
||||
<div class="issue-block {issue_class}">
|
||||
<b>{test_name}: </b> {test_text}<br>
|
||||
<b>Test ID:</b> {test_id}<br>
|
||||
<b>Severity: </b>{severity}<br>
|
||||
<b>Confidence: </b>{confidence}<br>
|
||||
<b>CWE: </b><a href="{cwe_link}" target="_blank">CWE-{cwe.id}</a><br>
|
||||
<b>File: </b><a href="{path}" target="_blank">{path}</a><br>
|
||||
<b>Line number: </b>{line_number}<br>
|
||||
<b>More info: </b><a href="{url}" target="_blank">{url}</a><br>
|
||||
{code}
|
||||
{candidates}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
code_block = """
|
||||
<div class="code">
|
||||
<pre>
|
||||
{code}
|
||||
</pre>
|
||||
</div>
|
||||
"""
|
||||
|
||||
candidate_block = """
|
||||
<div class="candidates">
|
||||
<br>
|
||||
<b>Candidates: </b>
|
||||
{candidate_list}
|
||||
</div>
|
||||
"""
|
||||
|
||||
candidate_issue = """
|
||||
<div class="candidate">
|
||||
<div class="candidate-issues">
|
||||
<pre>{code}</pre>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
skipped_block = """
|
||||
<br>
|
||||
<div id="skipped">
|
||||
<div class="bordered-box">
|
||||
<b>Skipped files:</b><br><br>
|
||||
{files_list}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
metrics_block = """
|
||||
<div id="metrics">
|
||||
<div class="metrics-box bordered-box">
|
||||
<div class="metrics-title">
|
||||
Metrics:<br>
|
||||
</div>
|
||||
Total lines of code: <span id="loc">{loc}</span><br>
|
||||
Total lines skipped (#nosec): <span id="nosec">{nosec}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
"""
|
||||
|
||||
issues = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level)
|
||||
|
||||
baseline = not isinstance(issues, list)
|
||||
|
||||
# build the skipped string to insert in the report
|
||||
skipped_str = "".join(
|
||||
f"{fname} <b>reason:</b> {reason}<br>"
|
||||
for fname, reason in manager.get_skipped()
|
||||
)
|
||||
if skipped_str:
|
||||
skipped_text = skipped_block.format(files_list=skipped_str)
|
||||
else:
|
||||
skipped_text = ""
|
||||
|
||||
# build the results string to insert in the report
|
||||
results_str = ""
|
||||
for index, issue in enumerate(issues):
|
||||
if not baseline or len(issues[issue]) == 1:
|
||||
candidates = ""
|
||||
safe_code = html_escape(
|
||||
issue.get_code(lines, True).strip("\n").lstrip(" ")
|
||||
)
|
||||
code = code_block.format(code=safe_code)
|
||||
else:
|
||||
candidates_str = ""
|
||||
code = ""
|
||||
for candidate in issues[issue]:
|
||||
candidate_code = html_escape(
|
||||
candidate.get_code(lines, True).strip("\n").lstrip(" ")
|
||||
)
|
||||
candidates_str += candidate_issue.format(code=candidate_code)
|
||||
|
||||
candidates = candidate_block.format(candidate_list=candidates_str)
|
||||
|
||||
url = docs_utils.get_url(issue.test_id)
|
||||
results_str += issue_block.format(
|
||||
issue_no=index,
|
||||
issue_class=f"issue-sev-{issue.severity.lower()}",
|
||||
test_name=issue.test,
|
||||
test_id=issue.test_id,
|
||||
test_text=issue.text,
|
||||
severity=issue.severity,
|
||||
confidence=issue.confidence,
|
||||
cwe=issue.cwe,
|
||||
cwe_link=issue.cwe.link(),
|
||||
path=issue.fname,
|
||||
code=code,
|
||||
candidates=candidates,
|
||||
url=url,
|
||||
line_number=issue.lineno,
|
||||
)
|
||||
|
||||
# build the metrics string to insert in the report
|
||||
metrics_summary = metrics_block.format(
|
||||
loc=manager.metrics.data["_totals"]["loc"],
|
||||
nosec=manager.metrics.data["_totals"]["nosec"],
|
||||
)
|
||||
|
||||
# build the report and output it
|
||||
report_contents = report_block.format(
|
||||
metrics=metrics_summary, skipped=skipped_text, results=results_str
|
||||
)
|
||||
|
||||
with fileobj:
|
||||
wrapped_file = utils.wrap_file_object(fileobj)
|
||||
wrapped_file.write(header_block)
|
||||
wrapped_file.write(report_contents)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("HTML output written to file: %s", fileobj.name)
|
||||
@@ -0,0 +1,155 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
r"""
|
||||
==============
|
||||
JSON formatter
|
||||
==============
|
||||
|
||||
This formatter outputs the issues in JSON.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"errors": [],
|
||||
"generated_at": "2015-12-16T22:27:34Z",
|
||||
"metrics": {
|
||||
"_totals": {
|
||||
"CONFIDENCE.HIGH": 1,
|
||||
"CONFIDENCE.LOW": 0,
|
||||
"CONFIDENCE.MEDIUM": 0,
|
||||
"CONFIDENCE.UNDEFINED": 0,
|
||||
"SEVERITY.HIGH": 0,
|
||||
"SEVERITY.LOW": 0,
|
||||
"SEVERITY.MEDIUM": 1,
|
||||
"SEVERITY.UNDEFINED": 0,
|
||||
"loc": 5,
|
||||
"nosec": 0
|
||||
},
|
||||
"examples/yaml_load.py": {
|
||||
"CONFIDENCE.HIGH": 1,
|
||||
"CONFIDENCE.LOW": 0,
|
||||
"CONFIDENCE.MEDIUM": 0,
|
||||
"CONFIDENCE.UNDEFINED": 0,
|
||||
"SEVERITY.HIGH": 0,
|
||||
"SEVERITY.LOW": 0,
|
||||
"SEVERITY.MEDIUM": 1,
|
||||
"SEVERITY.UNDEFINED": 0,
|
||||
"loc": 5,
|
||||
"nosec": 0
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"code": "4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})\n5
|
||||
y = yaml.load(ystr)\n6 yaml.dump(y)\n",
|
||||
"filename": "examples/yaml_load.py",
|
||||
"issue_confidence": "HIGH",
|
||||
"issue_severity": "MEDIUM",
|
||||
"issue_cwe": {
|
||||
"id": 20,
|
||||
"link": "https://cwe.mitre.org/data/definitions/20.html"
|
||||
},
|
||||
"issue_text": "Use of unsafe yaml load. Allows instantiation of
|
||||
arbitrary objects. Consider yaml.safe_load().\n",
|
||||
"line_number": 5,
|
||||
"line_range": [
|
||||
5
|
||||
],
|
||||
"more_info": "https://bandit.readthedocs.io/en/latest/",
|
||||
"test_name": "blacklist_calls",
|
||||
"test_id": "B301"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded:: 0.10.0
|
||||
|
||||
.. versionchanged:: 1.5.0
|
||||
New field `more_info` added to output
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
# Necessary so we can import the standard library json module while continuing
|
||||
# to name this file json.py. (Python 2 only)
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import operator
|
||||
import sys
|
||||
|
||||
from bandit.core import docs_utils
|
||||
from bandit.core import test_properties
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@test_properties.accepts_baseline
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""''Prints issues in JSON format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
machine_output = {"results": [], "errors": []}
|
||||
for fname, reason in manager.get_skipped():
|
||||
machine_output["errors"].append({"filename": fname, "reason": reason})
|
||||
|
||||
results = manager.get_issue_list(
|
||||
sev_level=sev_level, conf_level=conf_level
|
||||
)
|
||||
|
||||
baseline = not isinstance(results, list)
|
||||
|
||||
if baseline:
|
||||
collector = []
|
||||
for r in results:
|
||||
d = r.as_dict(max_lines=lines)
|
||||
d["more_info"] = docs_utils.get_url(d["test_id"])
|
||||
if len(results[r]) > 1:
|
||||
d["candidates"] = [
|
||||
c.as_dict(max_lines=lines) for c in results[r]
|
||||
]
|
||||
collector.append(d)
|
||||
|
||||
else:
|
||||
collector = [r.as_dict(max_lines=lines) for r in results]
|
||||
for elem in collector:
|
||||
elem["more_info"] = docs_utils.get_url(elem["test_id"])
|
||||
|
||||
itemgetter = operator.itemgetter
|
||||
if manager.agg_type == "vuln":
|
||||
machine_output["results"] = sorted(
|
||||
collector, key=itemgetter("test_name")
|
||||
)
|
||||
else:
|
||||
machine_output["results"] = sorted(
|
||||
collector, key=itemgetter("filename")
|
||||
)
|
||||
|
||||
machine_output["metrics"] = manager.metrics.data
|
||||
|
||||
# timezone agnostic format
|
||||
TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
time_string = datetime.datetime.now(datetime.timezone.utc).strftime(
|
||||
TS_FORMAT
|
||||
)
|
||||
machine_output["generated_at"] = time_string
|
||||
|
||||
result = json.dumps(
|
||||
machine_output, sort_keys=True, indent=2, separators=(",", ": ")
|
||||
)
|
||||
|
||||
with fileobj:
|
||||
fileobj.write(result)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("JSON output written to file: %s", fileobj.name)
|
||||
@@ -0,0 +1,374 @@
|
||||
# Copyright (c) Microsoft. All Rights Reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Note: this code mostly incorporated from
|
||||
# https://github.com/microsoft/bandit-sarif-formatter
|
||||
#
|
||||
r"""
|
||||
===============
|
||||
SARIF formatter
|
||||
===============
|
||||
|
||||
This formatter outputs the issues in SARIF formatted JSON.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "Bandit",
|
||||
"organization": "PyCQA",
|
||||
"rules": [
|
||||
{
|
||||
"id": "B101",
|
||||
"name": "assert_used",
|
||||
"properties": {
|
||||
"tags": [
|
||||
"security",
|
||||
"external/cwe/cwe-703"
|
||||
],
|
||||
"precision": "high"
|
||||
},
|
||||
"helpUri": "https://bandit.readthedocs.io/en/1.7.8/plugins/b101_assert_used.html"
|
||||
}
|
||||
],
|
||||
"version": "1.7.8",
|
||||
"semanticVersion": "1.7.8"
|
||||
}
|
||||
},
|
||||
"invocations": [
|
||||
{
|
||||
"executionSuccessful": true,
|
||||
"endTimeUtc": "2024-03-05T03:28:48Z"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"metrics": {
|
||||
"_totals": {
|
||||
"loc": 1,
|
||||
"nosec": 0,
|
||||
"skipped_tests": 0,
|
||||
"SEVERITY.UNDEFINED": 0,
|
||||
"CONFIDENCE.UNDEFINED": 0,
|
||||
"SEVERITY.LOW": 1,
|
||||
"CONFIDENCE.LOW": 0,
|
||||
"SEVERITY.MEDIUM": 0,
|
||||
"CONFIDENCE.MEDIUM": 0,
|
||||
"SEVERITY.HIGH": 0,
|
||||
"CONFIDENCE.HIGH": 1
|
||||
},
|
||||
"./examples/assert.py": {
|
||||
"loc": 1,
|
||||
"nosec": 0,
|
||||
"skipped_tests": 0,
|
||||
"SEVERITY.UNDEFINED": 0,
|
||||
"SEVERITY.LOW": 1,
|
||||
"SEVERITY.MEDIUM": 0,
|
||||
"SEVERITY.HIGH": 0,
|
||||
"CONFIDENCE.UNDEFINED": 0,
|
||||
"CONFIDENCE.LOW": 0,
|
||||
"CONFIDENCE.MEDIUM": 0,
|
||||
"CONFIDENCE.HIGH": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"message": {
|
||||
"text": "Use of assert detected. The enclosed code will be removed when compiling to optimised byte code."
|
||||
},
|
||||
"level": "note",
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"region": {
|
||||
"snippet": {
|
||||
"text": "assert True\n"
|
||||
},
|
||||
"endColumn": 11,
|
||||
"endLine": 1,
|
||||
"startColumn": 0,
|
||||
"startLine": 1
|
||||
},
|
||||
"artifactLocation": {
|
||||
"uri": "examples/assert.py"
|
||||
},
|
||||
"contextRegion": {
|
||||
"snippet": {
|
||||
"text": "assert True\n"
|
||||
},
|
||||
"endLine": 1,
|
||||
"startLine": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"issue_confidence": "HIGH",
|
||||
"issue_severity": "LOW"
|
||||
},
|
||||
"ruleId": "B101",
|
||||
"ruleIndex": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"version": "2.1.0",
|
||||
"$schema": "https://json.schemastore.org/sarif-2.1.0.json"
|
||||
}
|
||||
|
||||
.. versionadded:: 1.7.8
|
||||
|
||||
""" # noqa: E501
|
||||
import datetime
|
||||
import logging
|
||||
import pathlib
|
||||
import sys
|
||||
import urllib.parse as urlparse
|
||||
|
||||
import sarif_om as om
|
||||
from jschema_to_python.to_json import to_json
|
||||
|
||||
import bandit
|
||||
from bandit.core import docs_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
SCHEMA_URI = "https://json.schemastore.org/sarif-2.1.0.json"
|
||||
SCHEMA_VER = "2.1.0"
|
||||
TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""Prints issues in SARIF format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
log = om.SarifLog(
|
||||
schema_uri=SCHEMA_URI,
|
||||
version=SCHEMA_VER,
|
||||
runs=[
|
||||
om.Run(
|
||||
tool=om.Tool(
|
||||
driver=om.ToolComponent(
|
||||
name="Bandit",
|
||||
organization=bandit.__author__,
|
||||
semantic_version=bandit.__version__,
|
||||
version=bandit.__version__,
|
||||
)
|
||||
),
|
||||
invocations=[
|
||||
om.Invocation(
|
||||
end_time_utc=datetime.datetime.now(
|
||||
datetime.timezone.utc
|
||||
).strftime(TS_FORMAT),
|
||||
execution_successful=True,
|
||||
)
|
||||
],
|
||||
properties={"metrics": manager.metrics.data},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
run = log.runs[0]
|
||||
invocation = run.invocations[0]
|
||||
|
||||
skips = manager.get_skipped()
|
||||
add_skipped_file_notifications(skips, invocation)
|
||||
|
||||
issues = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level)
|
||||
|
||||
add_results(issues, run)
|
||||
|
||||
serializedLog = to_json(log)
|
||||
|
||||
with fileobj:
|
||||
fileobj.write(serializedLog)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("SARIF output written to file: %s", fileobj.name)
|
||||
|
||||
|
||||
def add_skipped_file_notifications(skips, invocation):
|
||||
if skips is None or len(skips) == 0:
|
||||
return
|
||||
|
||||
if invocation.tool_configuration_notifications is None:
|
||||
invocation.tool_configuration_notifications = []
|
||||
|
||||
for skip in skips:
|
||||
(file_name, reason) = skip
|
||||
|
||||
notification = om.Notification(
|
||||
level="error",
|
||||
message=om.Message(text=reason),
|
||||
locations=[
|
||||
om.Location(
|
||||
physical_location=om.PhysicalLocation(
|
||||
artifact_location=om.ArtifactLocation(
|
||||
uri=to_uri(file_name)
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
invocation.tool_configuration_notifications.append(notification)
|
||||
|
||||
|
||||
def add_results(issues, run):
|
||||
if run.results is None:
|
||||
run.results = []
|
||||
|
||||
rules = {}
|
||||
rule_indices = {}
|
||||
for issue in issues:
|
||||
result = create_result(issue, rules, rule_indices)
|
||||
run.results.append(result)
|
||||
|
||||
if len(rules) > 0:
|
||||
run.tool.driver.rules = list(rules.values())
|
||||
|
||||
|
||||
def create_result(issue, rules, rule_indices):
|
||||
issue_dict = issue.as_dict()
|
||||
|
||||
rule, rule_index = create_or_find_rule(issue_dict, rules, rule_indices)
|
||||
|
||||
physical_location = om.PhysicalLocation(
|
||||
artifact_location=om.ArtifactLocation(
|
||||
uri=to_uri(issue_dict["filename"])
|
||||
)
|
||||
)
|
||||
|
||||
add_region_and_context_region(
|
||||
physical_location,
|
||||
issue_dict["line_range"],
|
||||
issue_dict["col_offset"],
|
||||
issue_dict["end_col_offset"],
|
||||
issue_dict["code"],
|
||||
)
|
||||
|
||||
return om.Result(
|
||||
rule_id=rule.id,
|
||||
rule_index=rule_index,
|
||||
message=om.Message(text=issue_dict["issue_text"]),
|
||||
level=level_from_severity(issue_dict["issue_severity"]),
|
||||
locations=[om.Location(physical_location=physical_location)],
|
||||
properties={
|
||||
"issue_confidence": issue_dict["issue_confidence"],
|
||||
"issue_severity": issue_dict["issue_severity"],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def level_from_severity(severity):
|
||||
if severity == "HIGH":
|
||||
return "error"
|
||||
elif severity == "MEDIUM":
|
||||
return "warning"
|
||||
elif severity == "LOW":
|
||||
return "note"
|
||||
else:
|
||||
return "warning"
|
||||
|
||||
|
||||
def add_region_and_context_region(
|
||||
physical_location, line_range, col_offset, end_col_offset, code
|
||||
):
|
||||
if code:
|
||||
first_line_number, snippet_lines = parse_code(code)
|
||||
snippet_line = snippet_lines[line_range[0] - first_line_number]
|
||||
snippet = om.ArtifactContent(text=snippet_line)
|
||||
else:
|
||||
snippet = None
|
||||
|
||||
physical_location.region = om.Region(
|
||||
start_line=line_range[0],
|
||||
end_line=line_range[1] if len(line_range) > 1 else line_range[0],
|
||||
start_column=col_offset + 1,
|
||||
end_column=end_col_offset + 1,
|
||||
snippet=snippet,
|
||||
)
|
||||
|
||||
if code:
|
||||
physical_location.context_region = om.Region(
|
||||
start_line=first_line_number,
|
||||
end_line=first_line_number + len(snippet_lines) - 1,
|
||||
snippet=om.ArtifactContent(text="".join(snippet_lines)),
|
||||
)
|
||||
|
||||
|
||||
def parse_code(code):
|
||||
code_lines = code.split("\n")
|
||||
|
||||
# The last line from the split has nothing in it; it's an artifact of the
|
||||
# last "real" line ending in a newline. Unless, of course, it doesn't:
|
||||
last_line = code_lines[len(code_lines) - 1]
|
||||
|
||||
last_real_line_ends_in_newline = False
|
||||
if len(last_line) == 0:
|
||||
code_lines.pop()
|
||||
last_real_line_ends_in_newline = True
|
||||
|
||||
snippet_lines = []
|
||||
first_line_number = 0
|
||||
first = True
|
||||
for code_line in code_lines:
|
||||
number_and_snippet_line = code_line.split(" ", 1)
|
||||
if first:
|
||||
first_line_number = int(number_and_snippet_line[0])
|
||||
first = False
|
||||
|
||||
snippet_line = number_and_snippet_line[1] + "\n"
|
||||
snippet_lines.append(snippet_line)
|
||||
|
||||
if not last_real_line_ends_in_newline:
|
||||
last_line = snippet_lines[len(snippet_lines) - 1]
|
||||
snippet_lines[len(snippet_lines) - 1] = last_line[: len(last_line) - 1]
|
||||
|
||||
return first_line_number, snippet_lines
|
||||
|
||||
|
||||
def create_or_find_rule(issue_dict, rules, rule_indices):
|
||||
rule_id = issue_dict["test_id"]
|
||||
if rule_id in rules:
|
||||
return rules[rule_id], rule_indices[rule_id]
|
||||
|
||||
rule = om.ReportingDescriptor(
|
||||
id=rule_id,
|
||||
name=issue_dict["test_name"],
|
||||
help_uri=docs_utils.get_url(rule_id),
|
||||
properties={
|
||||
"tags": [
|
||||
"security",
|
||||
f"external/cwe/cwe-{issue_dict['issue_cwe'].get('id')}",
|
||||
],
|
||||
"precision": issue_dict["issue_confidence"].lower(),
|
||||
},
|
||||
)
|
||||
|
||||
index = len(rules)
|
||||
rules[rule_id] = rule
|
||||
rule_indices[rule_id] = index
|
||||
return rule, index
|
||||
|
||||
|
||||
def to_uri(file_path):
|
||||
pure_path = pathlib.PurePath(file_path)
|
||||
if pure_path.is_absolute():
|
||||
return pure_path.as_uri()
|
||||
else:
|
||||
# Replace backslashes with slashes.
|
||||
posix_path = pure_path.as_posix()
|
||||
# %-encode special characters.
|
||||
return urlparse.quote(posix_path)
|
||||
@@ -0,0 +1,244 @@
|
||||
# Copyright (c) 2015 Hewlett Packard Enterprise
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
r"""
|
||||
================
|
||||
Screen formatter
|
||||
================
|
||||
|
||||
This formatter outputs the issues as color coded text to screen.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
>> Issue: [B506: yaml_load] Use of unsafe yaml load. Allows
|
||||
instantiation of arbitrary objects. Consider yaml.safe_load().
|
||||
|
||||
Severity: Medium Confidence: High
|
||||
CWE: CWE-20 (https://cwe.mitre.org/data/definitions/20.html)
|
||||
More Info: https://bandit.readthedocs.io/en/latest/
|
||||
Location: examples/yaml_load.py:5
|
||||
4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
|
||||
5 y = yaml.load(ystr)
|
||||
6 yaml.dump(y)
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
.. versionchanged:: 1.5.0
|
||||
New field `more_info` added to output
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from bandit.core import constants
|
||||
from bandit.core import docs_utils
|
||||
from bandit.core import test_properties
|
||||
|
||||
IS_WIN_PLATFORM = sys.platform.startswith("win32")
|
||||
COLORAMA = False
|
||||
|
||||
# This fixes terminal colors not displaying properly on Windows systems.
|
||||
# Colorama will intercept any ANSI escape codes and convert them to the
|
||||
# proper Windows console API calls to change text color.
|
||||
if IS_WIN_PLATFORM:
|
||||
try:
|
||||
import colorama
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
COLORAMA = True
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
COLOR = {
|
||||
"DEFAULT": "\033[0m",
|
||||
"HEADER": "\033[95m",
|
||||
"LOW": "\033[94m",
|
||||
"MEDIUM": "\033[93m",
|
||||
"HIGH": "\033[91m",
|
||||
}
|
||||
|
||||
|
||||
def header(text, *args):
|
||||
return f"{COLOR['HEADER']}{text % args}{COLOR['DEFAULT']}"
|
||||
|
||||
|
||||
def get_verbose_details(manager):
|
||||
bits = []
|
||||
bits.append(header("Files in scope (%i):", len(manager.files_list)))
|
||||
tpl = "\t%s (score: {SEVERITY: %i, CONFIDENCE: %i})"
|
||||
bits.extend(
|
||||
[
|
||||
tpl % (item, sum(score["SEVERITY"]), sum(score["CONFIDENCE"]))
|
||||
for (item, score) in zip(manager.files_list, manager.scores)
|
||||
]
|
||||
)
|
||||
bits.append(header("Files excluded (%i):", len(manager.excluded_files)))
|
||||
bits.extend([f"\t{fname}" for fname in manager.excluded_files])
|
||||
return "\n".join([str(bit) for bit in bits])
|
||||
|
||||
|
||||
def get_metrics(manager):
|
||||
bits = []
|
||||
bits.append(header("\nRun metrics:"))
|
||||
for criteria, _ in constants.CRITERIA:
|
||||
bits.append(f"\tTotal issues (by {criteria.lower()}):")
|
||||
for rank in constants.RANKING:
|
||||
bits.append(
|
||||
"\t\t%s: %s"
|
||||
% (
|
||||
rank.capitalize(),
|
||||
manager.metrics.data["_totals"][f"{criteria}.{rank}"],
|
||||
)
|
||||
)
|
||||
return "\n".join([str(bit) for bit in bits])
|
||||
|
||||
|
||||
def _output_issue_str(
|
||||
issue, indent, show_lineno=True, show_code=True, lines=-1
|
||||
):
|
||||
# returns a list of lines that should be added to the existing lines list
|
||||
bits = []
|
||||
bits.append(
|
||||
"%s%s>> Issue: [%s:%s] %s"
|
||||
% (
|
||||
indent,
|
||||
COLOR[issue.severity],
|
||||
issue.test_id,
|
||||
issue.test,
|
||||
issue.text,
|
||||
)
|
||||
)
|
||||
|
||||
bits.append(
|
||||
"%s Severity: %s Confidence: %s"
|
||||
% (
|
||||
indent,
|
||||
issue.severity.capitalize(),
|
||||
issue.confidence.capitalize(),
|
||||
)
|
||||
)
|
||||
|
||||
bits.append(f"{indent} CWE: {str(issue.cwe)}")
|
||||
|
||||
bits.append(f"{indent} More Info: {docs_utils.get_url(issue.test_id)}")
|
||||
|
||||
bits.append(
|
||||
"%s Location: %s:%s:%s%s"
|
||||
% (
|
||||
indent,
|
||||
issue.fname,
|
||||
issue.lineno if show_lineno else "",
|
||||
issue.col_offset if show_lineno else "",
|
||||
COLOR["DEFAULT"],
|
||||
)
|
||||
)
|
||||
|
||||
if show_code:
|
||||
bits.extend(
|
||||
[indent + line for line in issue.get_code(lines, True).split("\n")]
|
||||
)
|
||||
|
||||
return "\n".join([bit for bit in bits])
|
||||
|
||||
|
||||
def get_results(manager, sev_level, conf_level, lines):
|
||||
bits = []
|
||||
issues = manager.get_issue_list(sev_level, conf_level)
|
||||
baseline = not isinstance(issues, list)
|
||||
candidate_indent = " " * 10
|
||||
|
||||
if not len(issues):
|
||||
return "\tNo issues identified."
|
||||
|
||||
for issue in issues:
|
||||
# if not a baseline or only one candidate we know the issue
|
||||
if not baseline or len(issues[issue]) == 1:
|
||||
bits.append(_output_issue_str(issue, "", lines=lines))
|
||||
|
||||
# otherwise show the finding and the candidates
|
||||
else:
|
||||
bits.append(
|
||||
_output_issue_str(
|
||||
issue, "", show_lineno=False, show_code=False
|
||||
)
|
||||
)
|
||||
|
||||
bits.append("\n-- Candidate Issues --")
|
||||
for candidate in issues[issue]:
|
||||
bits.append(
|
||||
_output_issue_str(candidate, candidate_indent, lines=lines)
|
||||
)
|
||||
bits.append("\n")
|
||||
bits.append("-" * 50)
|
||||
|
||||
return "\n".join([bit for bit in bits])
|
||||
|
||||
|
||||
def do_print(bits):
|
||||
# needed so we can mock this stuff
|
||||
print("\n".join([bit for bit in bits]))
|
||||
|
||||
|
||||
@test_properties.accepts_baseline
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""Prints discovered issues formatted for screen reading
|
||||
|
||||
This makes use of VT100 terminal codes for colored text.
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
if IS_WIN_PLATFORM and COLORAMA:
|
||||
colorama.init()
|
||||
|
||||
bits = []
|
||||
if not manager.quiet or manager.results_count(sev_level, conf_level):
|
||||
bits.append(
|
||||
header(
|
||||
"Run started:%s", datetime.datetime.now(datetime.timezone.utc)
|
||||
)
|
||||
)
|
||||
|
||||
if manager.verbose:
|
||||
bits.append(get_verbose_details(manager))
|
||||
|
||||
bits.append(header("\nTest results:"))
|
||||
bits.append(get_results(manager, sev_level, conf_level, lines))
|
||||
bits.append(header("\nCode scanned:"))
|
||||
bits.append(
|
||||
"\tTotal lines of code: %i"
|
||||
% (manager.metrics.data["_totals"]["loc"])
|
||||
)
|
||||
|
||||
bits.append(
|
||||
"\tTotal lines skipped (#nosec): %i"
|
||||
% (manager.metrics.data["_totals"]["nosec"])
|
||||
)
|
||||
|
||||
bits.append(get_metrics(manager))
|
||||
skipped = manager.get_skipped()
|
||||
bits.append(header("Files skipped (%i):", len(skipped)))
|
||||
bits.extend(["\t%s (%s)" % skip for skip in skipped])
|
||||
do_print(bits)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info(
|
||||
"Screen formatter output was not written to file: %s, "
|
||||
"consider '-f txt'",
|
||||
fileobj.name,
|
||||
)
|
||||
|
||||
if IS_WIN_PLATFORM and COLORAMA:
|
||||
colorama.deinit()
|
||||
@@ -0,0 +1,200 @@
|
||||
# Copyright (c) 2015 Hewlett Packard Enterprise
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
r"""
|
||||
==============
|
||||
Text Formatter
|
||||
==============
|
||||
|
||||
This formatter outputs the issues as plain text.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
>> Issue: [B301:blacklist_calls] Use of unsafe yaml load. Allows
|
||||
instantiation of arbitrary objects. Consider yaml.safe_load().
|
||||
|
||||
Severity: Medium Confidence: High
|
||||
CWE: CWE-20 (https://cwe.mitre.org/data/definitions/20.html)
|
||||
More Info: https://bandit.readthedocs.io/en/latest/
|
||||
Location: examples/yaml_load.py:5
|
||||
4 ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
|
||||
5 y = yaml.load(ystr)
|
||||
6 yaml.dump(y)
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
.. versionchanged:: 1.5.0
|
||||
New field `more_info` added to output
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from bandit.core import constants
|
||||
from bandit.core import docs_utils
|
||||
from bandit.core import test_properties
|
||||
from bandit.formatters import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_verbose_details(manager):
|
||||
bits = []
|
||||
bits.append(f"Files in scope ({len(manager.files_list)}):")
|
||||
tpl = "\t%s (score: {SEVERITY: %i, CONFIDENCE: %i})"
|
||||
bits.extend(
|
||||
[
|
||||
tpl % (item, sum(score["SEVERITY"]), sum(score["CONFIDENCE"]))
|
||||
for (item, score) in zip(manager.files_list, manager.scores)
|
||||
]
|
||||
)
|
||||
bits.append(f"Files excluded ({len(manager.excluded_files)}):")
|
||||
bits.extend([f"\t{fname}" for fname in manager.excluded_files])
|
||||
return "\n".join([bit for bit in bits])
|
||||
|
||||
|
||||
def get_metrics(manager):
|
||||
bits = []
|
||||
bits.append("\nRun metrics:")
|
||||
for criteria, _ in constants.CRITERIA:
|
||||
bits.append(f"\tTotal issues (by {criteria.lower()}):")
|
||||
for rank in constants.RANKING:
|
||||
bits.append(
|
||||
"\t\t%s: %s"
|
||||
% (
|
||||
rank.capitalize(),
|
||||
manager.metrics.data["_totals"][f"{criteria}.{rank}"],
|
||||
)
|
||||
)
|
||||
return "\n".join([bit for bit in bits])
|
||||
|
||||
|
||||
def _output_issue_str(
|
||||
issue, indent, show_lineno=True, show_code=True, lines=-1
|
||||
):
|
||||
# returns a list of lines that should be added to the existing lines list
|
||||
bits = []
|
||||
bits.append(
|
||||
f"{indent}>> Issue: [{issue.test_id}:{issue.test}] {issue.text}"
|
||||
)
|
||||
|
||||
bits.append(
|
||||
"%s Severity: %s Confidence: %s"
|
||||
% (
|
||||
indent,
|
||||
issue.severity.capitalize(),
|
||||
issue.confidence.capitalize(),
|
||||
)
|
||||
)
|
||||
|
||||
bits.append(f"{indent} CWE: {str(issue.cwe)}")
|
||||
|
||||
bits.append(f"{indent} More Info: {docs_utils.get_url(issue.test_id)}")
|
||||
|
||||
bits.append(
|
||||
"%s Location: %s:%s:%s"
|
||||
% (
|
||||
indent,
|
||||
issue.fname,
|
||||
issue.lineno if show_lineno else "",
|
||||
issue.col_offset if show_lineno else "",
|
||||
)
|
||||
)
|
||||
|
||||
if show_code:
|
||||
bits.extend(
|
||||
[indent + line for line in issue.get_code(lines, True).split("\n")]
|
||||
)
|
||||
|
||||
return "\n".join([bit for bit in bits])
|
||||
|
||||
|
||||
def get_results(manager, sev_level, conf_level, lines):
|
||||
bits = []
|
||||
issues = manager.get_issue_list(sev_level, conf_level)
|
||||
baseline = not isinstance(issues, list)
|
||||
candidate_indent = " " * 10
|
||||
|
||||
if not len(issues):
|
||||
return "\tNo issues identified."
|
||||
|
||||
for issue in issues:
|
||||
# if not a baseline or only one candidate we know the issue
|
||||
if not baseline or len(issues[issue]) == 1:
|
||||
bits.append(_output_issue_str(issue, "", lines=lines))
|
||||
|
||||
# otherwise show the finding and the candidates
|
||||
else:
|
||||
bits.append(
|
||||
_output_issue_str(
|
||||
issue, "", show_lineno=False, show_code=False
|
||||
)
|
||||
)
|
||||
|
||||
bits.append("\n-- Candidate Issues --")
|
||||
for candidate in issues[issue]:
|
||||
bits.append(
|
||||
_output_issue_str(candidate, candidate_indent, lines=lines)
|
||||
)
|
||||
bits.append("\n")
|
||||
bits.append("-" * 50)
|
||||
return "\n".join([bit for bit in bits])
|
||||
|
||||
|
||||
@test_properties.accepts_baseline
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""Prints discovered issues in the text format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
bits = []
|
||||
|
||||
if not manager.quiet or manager.results_count(sev_level, conf_level):
|
||||
bits.append(
|
||||
f"Run started:{datetime.datetime.now(datetime.timezone.utc)}"
|
||||
)
|
||||
|
||||
if manager.verbose:
|
||||
bits.append(get_verbose_details(manager))
|
||||
|
||||
bits.append("\nTest results:")
|
||||
bits.append(get_results(manager, sev_level, conf_level, lines))
|
||||
bits.append("\nCode scanned:")
|
||||
bits.append(
|
||||
"\tTotal lines of code: %i"
|
||||
% (manager.metrics.data["_totals"]["loc"])
|
||||
)
|
||||
|
||||
bits.append(
|
||||
"\tTotal lines skipped (#nosec): %i"
|
||||
% (manager.metrics.data["_totals"]["nosec"])
|
||||
)
|
||||
bits.append(
|
||||
"\tTotal potential issues skipped due to specifically being "
|
||||
"disabled (e.g., #nosec BXXX): %i"
|
||||
% (manager.metrics.data["_totals"]["skipped_tests"])
|
||||
)
|
||||
|
||||
skipped = manager.get_skipped()
|
||||
bits.append(get_metrics(manager))
|
||||
bits.append(f"Files skipped ({len(skipped)}):")
|
||||
bits.extend(["\t%s (%s)" % skip for skip in skipped])
|
||||
result = "\n".join([bit for bit in bits]) + "\n"
|
||||
|
||||
with fileobj:
|
||||
wrapped_file = utils.wrap_file_object(fileobj)
|
||||
wrapped_file.write(result)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("Text output written to file: %s", fileobj.name)
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2016 Rackspace, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Utility functions for formatting plugins for Bandit."""
|
||||
import io
|
||||
|
||||
|
||||
def wrap_file_object(fileobj):
|
||||
"""If the fileobj passed in cannot handle text, use TextIOWrapper
|
||||
to handle the conversion.
|
||||
"""
|
||||
if isinstance(fileobj, io.TextIOBase):
|
||||
return fileobj
|
||||
return io.TextIOWrapper(fileobj)
|
||||
@@ -0,0 +1,97 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
r"""
|
||||
=============
|
||||
XML Formatter
|
||||
=============
|
||||
|
||||
This formatter outputs the issues as XML.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<testsuite name="bandit" tests="1"><testcase
|
||||
classname="examples/yaml_load.py" name="blacklist_calls"><error
|
||||
message="Use of unsafe yaml load. Allows instantiation of arbitrary
|
||||
objects. Consider yaml.safe_load(). " type="MEDIUM"
|
||||
more_info="https://bandit.readthedocs.io/en/latest/">Test ID: B301
|
||||
Severity: MEDIUM Confidence: HIGH
|
||||
CWE: CWE-20 (https://cwe.mitre.org/data/definitions/20.html) Use of unsafe
|
||||
yaml load.
|
||||
Allows instantiation of arbitrary objects. Consider yaml.safe_load().
|
||||
|
||||
Location examples/yaml_load.py:5</error></testcase></testsuite>
|
||||
|
||||
.. versionadded:: 0.12.0
|
||||
|
||||
.. versionchanged:: 1.5.0
|
||||
New field `more_info` added to output
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
from xml.etree import ElementTree as ET # nosec: B405
|
||||
|
||||
from bandit.core import docs_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""Prints issues in XML format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
issues = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level)
|
||||
root = ET.Element("testsuite", name="bandit", tests=str(len(issues)))
|
||||
|
||||
for issue in issues:
|
||||
test = issue.test
|
||||
testcase = ET.SubElement(
|
||||
root, "testcase", classname=issue.fname, name=test
|
||||
)
|
||||
|
||||
text = (
|
||||
"Test ID: %s Severity: %s Confidence: %s\nCWE: %s\n%s\n"
|
||||
"Location %s:%s"
|
||||
)
|
||||
text %= (
|
||||
issue.test_id,
|
||||
issue.severity,
|
||||
issue.confidence,
|
||||
issue.cwe,
|
||||
issue.text,
|
||||
issue.fname,
|
||||
issue.lineno,
|
||||
)
|
||||
ET.SubElement(
|
||||
testcase,
|
||||
"error",
|
||||
more_info=docs_utils.get_url(issue.test_id),
|
||||
type=issue.severity,
|
||||
message=issue.text,
|
||||
).text = text
|
||||
|
||||
tree = ET.ElementTree(root)
|
||||
|
||||
if fileobj.name == sys.stdout.name:
|
||||
fileobj = sys.stdout.buffer
|
||||
elif fileobj.mode == "w":
|
||||
fileobj.close()
|
||||
fileobj = open(fileobj.name, "wb")
|
||||
|
||||
with fileobj:
|
||||
tree.write(fileobj, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("XML output written to file: %s", fileobj.name)
|
||||
@@ -0,0 +1,126 @@
|
||||
# Copyright (c) 2017 VMware, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
r"""
|
||||
==============
|
||||
YAML Formatter
|
||||
==============
|
||||
|
||||
This formatter outputs the issues in a yaml format.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
errors: []
|
||||
generated_at: '2017-03-09T22:29:30Z'
|
||||
metrics:
|
||||
_totals:
|
||||
CONFIDENCE.HIGH: 1
|
||||
CONFIDENCE.LOW: 0
|
||||
CONFIDENCE.MEDIUM: 0
|
||||
CONFIDENCE.UNDEFINED: 0
|
||||
SEVERITY.HIGH: 0
|
||||
SEVERITY.LOW: 0
|
||||
SEVERITY.MEDIUM: 1
|
||||
SEVERITY.UNDEFINED: 0
|
||||
loc: 9
|
||||
nosec: 0
|
||||
examples/yaml_load.py:
|
||||
CONFIDENCE.HIGH: 1
|
||||
CONFIDENCE.LOW: 0
|
||||
CONFIDENCE.MEDIUM: 0
|
||||
CONFIDENCE.UNDEFINED: 0
|
||||
SEVERITY.HIGH: 0
|
||||
SEVERITY.LOW: 0
|
||||
SEVERITY.MEDIUM: 1
|
||||
SEVERITY.UNDEFINED: 0
|
||||
loc: 9
|
||||
nosec: 0
|
||||
results:
|
||||
- code: '5 ystr = yaml.dump({''a'' : 1, ''b'' : 2, ''c'' : 3})\n
|
||||
6 y = yaml.load(ystr)\n7 yaml.dump(y)\n'
|
||||
filename: examples/yaml_load.py
|
||||
issue_confidence: HIGH
|
||||
issue_severity: MEDIUM
|
||||
issue_text: Use of unsafe yaml load. Allows instantiation of arbitrary
|
||||
objects.
|
||||
Consider yaml.safe_load().
|
||||
line_number: 6
|
||||
line_range:
|
||||
- 6
|
||||
more_info: https://bandit.readthedocs.io/en/latest/
|
||||
test_id: B506
|
||||
test_name: yaml_load
|
||||
|
||||
.. versionadded:: 1.5.0
|
||||
|
||||
.. versionchanged:: 1.7.3
|
||||
New field `CWE` added to output
|
||||
|
||||
"""
|
||||
# Necessary for this formatter to work when imported on Python 2. Importing
|
||||
# the standard library's yaml module conflicts with the name of this module.
|
||||
import datetime
|
||||
import logging
|
||||
import operator
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
|
||||
from bandit.core import docs_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def report(manager, fileobj, sev_level, conf_level, lines=-1):
|
||||
"""Prints issues in YAML format
|
||||
|
||||
:param manager: the bandit manager object
|
||||
:param fileobj: The output file object, which may be sys.stdout
|
||||
:param sev_level: Filtering severity level
|
||||
:param conf_level: Filtering confidence level
|
||||
:param lines: Number of lines to report, -1 for all
|
||||
"""
|
||||
|
||||
machine_output = {"results": [], "errors": []}
|
||||
for fname, reason in manager.get_skipped():
|
||||
machine_output["errors"].append({"filename": fname, "reason": reason})
|
||||
|
||||
results = manager.get_issue_list(
|
||||
sev_level=sev_level, conf_level=conf_level
|
||||
)
|
||||
|
||||
collector = [r.as_dict(max_lines=lines) for r in results]
|
||||
for elem in collector:
|
||||
elem["more_info"] = docs_utils.get_url(elem["test_id"])
|
||||
|
||||
itemgetter = operator.itemgetter
|
||||
if manager.agg_type == "vuln":
|
||||
machine_output["results"] = sorted(
|
||||
collector, key=itemgetter("test_name")
|
||||
)
|
||||
else:
|
||||
machine_output["results"] = sorted(
|
||||
collector, key=itemgetter("filename")
|
||||
)
|
||||
|
||||
machine_output["metrics"] = manager.metrics.data
|
||||
|
||||
for result in machine_output["results"]:
|
||||
if "code" in result:
|
||||
code = result["code"].replace("\n", "\\n")
|
||||
result["code"] = code
|
||||
|
||||
# timezone agnostic format
|
||||
TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
time_string = datetime.datetime.now(datetime.timezone.utc).strftime(
|
||||
TS_FORMAT
|
||||
)
|
||||
machine_output["generated_at"] = time_string
|
||||
|
||||
yaml.safe_dump(machine_output, fileobj, default_flow_style=False)
|
||||
|
||||
if fileobj.name != sys.stdout.name:
|
||||
LOG.info("YAML output written to file: %s", fileobj.name)
|
||||
Reference in New Issue
Block a user