This commit is contained in:
Iliyan Angelov
2025-12-01 06:50:10 +02:00
parent 91f51bc6fe
commit 62c1fe5951
4682 changed files with 544807 additions and 31208 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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().&#10;" 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)

View File

@@ -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)