Skip to content

Commit e411eaf

Browse files
authored
Merge branch 'main' into missing-metric-in-report
2 parents de4b9f1 + c58c00a commit e411eaf

27 files changed

+356
-157
lines changed

.github/ISSUE_TEMPLATE/bug-report.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ body:
4444
label: Bandit version
4545
description: Run "bandit --version" if unsure of version number
4646
options:
47-
- 1.7.10 (Default)
47+
- 1.8.2 (Default)
48+
- 1.8.1
49+
- 1.8.0
50+
- 1.7.10
4851
- 1.7.9
4952
- 1.7.8
5053
- 1.7.7

.github/workflows/build-publish-image.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
ref: ${{ github.event_name == 'release' && github.ref || env.RELEASE_TAG }}
3232

3333
- name: Set up Docker Buildx
34-
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3
34+
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3
3535

3636
- name: Log in to GitHub Container Registry
3737
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
@@ -41,7 +41,7 @@ jobs:
4141
password: ${{ secrets.GITHUB_TOKEN }}
4242

4343
- name: Install Cosign
44-
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
44+
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3.8.0
4545
with:
4646
cosign-release: 'v2.2.2'
4747

@@ -51,7 +51,7 @@ jobs:
5151
5252
- name: Build and push Docker image
5353
id: build-and-push
54-
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6
54+
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
5555
with:
5656
context: .
5757
file: ./docker/Dockerfile

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ repos:
1313
- id: reorder-python-imports
1414
args: [--application-directories, '.:src', --py38-plus]
1515
- repo: https://github.com/psf/black-pre-commit-mirror
16-
rev: 24.10.0
16+
rev: 25.1.0
1717
hooks:
1818
- id: black
1919
args: [--line-length=79, --target-version=py38]
2020
- repo: https://github.com/asottile/pyupgrade
21-
rev: v3.19.0
21+
rev: v3.19.1
2222
hooks:
2323
- id: pyupgrade
2424
args: [--py38-plus]

README.rst

+6-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,12 @@ The development of Bandit is made possible by the following sponsors:
127127
:width: 100%
128128
:class: borderless
129129

130-
* - .. image:: https://github.githubassets.com/assets/tidelift-8cea37dea8fc.svg
130+
* - .. image:: https://avatars.githubusercontent.com/u/34240465?s=200&v=4
131+
:target: https://opensource.mercedes-benz.com/
132+
:alt: Mercedes-Benz
133+
:width: 88
134+
135+
- .. image:: https://github.githubassets.com/assets/tidelift-8cea37dea8fc.svg
131136
:target: https://tidelift.com/lifter/search/pypi/bandit
132137
:alt: Tidelift
133138
:width: 88

bandit/blacklists/calls.py

+17-21
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@
205205
| | | - random.uniform | |
206206
| | | - random.triangular | |
207207
| | | - random.randbytes | |
208+
| | | - random.randrange | |
209+
| | | - random.sample | |
210+
| | | - random.getrandbits | |
208211
+------+---------------------+------------------------------------+-----------+
209212
210213
B312: telnetlib
@@ -219,7 +222,7 @@
219222
| B312 | telnetlib | - telnetlib.\* | High |
220223
+------+---------------------+------------------------------------+-----------+
221224
222-
B313 - B320: XML
225+
B313 - B319: XML
223226
----------------
224227
225228
Most of this is based off of Christian Heimes' work on defusedxml:
@@ -256,6 +259,15 @@
256259
| B319 | xml_bad_pulldom | - xml.dom.pulldom.parse | Medium |
257260
| | | - xml.dom.pulldom.parseString | |
258261
+------+---------------------+------------------------------------+-----------+
262+
263+
B320: xml_bad_etree
264+
-------------------
265+
266+
The check for this call has been removed.
267+
268+
+------+---------------------+------------------------------------+-----------+
269+
| ID | Name | Calls | Severity |
270+
+======+=====================+====================================+===========+
259271
| B320 | xml_bad_etree | - lxml.etree.parse | Medium |
260272
| | | - lxml.etree.fromstring | |
261273
| | | - lxml.etree.RestrictedElement | |
@@ -506,6 +518,9 @@ def gen_blacklist():
506518
"random.uniform",
507519
"random.triangular",
508520
"random.randbytes",
521+
"random.sample",
522+
"random.randrange",
523+
"random.getrandbits",
509524
],
510525
"Standard pseudo-random generators are not suitable for "
511526
"security/cryptographic purposes.",
@@ -615,26 +630,7 @@ def gen_blacklist():
615630
)
616631
)
617632

618-
sets.append(
619-
utils.build_conf_dict(
620-
"xml_bad_etree",
621-
"B320",
622-
issue.Cwe.IMPROPER_INPUT_VALIDATION,
623-
[
624-
"lxml.etree.parse",
625-
"lxml.etree.fromstring",
626-
"lxml.etree.RestrictedElement",
627-
"lxml.etree.GlobalParserTLS",
628-
"lxml.etree.getDefaultParser",
629-
"lxml.etree.check_docinfo",
630-
],
631-
(
632-
"Using {name} to parse untrusted XML data is known to be "
633-
"vulnerable to XML attacks. Replace {name} with its "
634-
"defusedxml equivalent function."
635-
),
636-
)
637-
)
633+
# skipped B320 as the check for a call to lxml.etree has been removed
638634

639635
# end of XML tests
640636

bandit/blacklists/imports.py

+4-15
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@
133133
B410: import_lxml
134134
-----------------
135135
136+
This import blacklist has been removed. The information here has been
137+
left for historical purposes.
138+
136139
Using various methods to parse untrusted XML data is known to be vulnerable to
137140
XML attacks. Replace vulnerable imports with the equivalent defusedxml package.
138141
@@ -297,11 +300,6 @@ def gen_blacklist():
297300
"defusedxml package, or make sure defusedxml.defuse_stdlib() "
298301
"is called."
299302
)
300-
lxml_msg = (
301-
"Using {name} to parse untrusted XML data is known to be "
302-
"vulnerable to XML attacks. Replace {name} with the "
303-
"equivalent defusedxml package."
304-
)
305303

306304
sets.append(
307305
utils.build_conf_dict(
@@ -358,16 +356,7 @@ def gen_blacklist():
358356
)
359357
)
360358

361-
sets.append(
362-
utils.build_conf_dict(
363-
"import_lxml",
364-
"B410",
365-
issue.Cwe.IMPROPER_INPUT_VALIDATION,
366-
["lxml"],
367-
lxml_msg,
368-
"LOW",
369-
)
370-
)
359+
# skipped B410 as the check for import_lxml has been removed
371360

372361
sets.append(
373362
utils.build_conf_dict(

bandit/core/context.py

+4
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,7 @@ def filename(self):
318318
@property
319319
def file_data(self):
320320
return self._context.get("file_data")
321+
322+
@property
323+
def import_aliases(self):
324+
return self._context.get("import_aliases")

bandit/core/extension_loader.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
#
22
# SPDX-License-Identifier: Apache-2.0
3+
import logging
34
import sys
45

56
from stevedore import extension
67

78
from bandit.core import utils
89

10+
LOG = logging.getLogger(__name__)
11+
912

1013
class Manager:
1114
# These IDs are for bandit built in tests
@@ -84,11 +87,11 @@ def validate_profile(self, profile):
8487
"""Validate that everything in the configured profiles looks good."""
8588
for inc in profile["include"]:
8689
if not self.check_id(inc):
87-
raise ValueError(f"Unknown test found in profile: {inc}")
90+
LOG.warning(f"Unknown test found in profile: {inc}")
8891

8992
for exc in profile["exclude"]:
9093
if not self.check_id(exc):
91-
raise ValueError(f"Unknown test found in profile: {exc}")
94+
LOG.warning(f"Unknown test found in profile: {exc}")
9295

9396
union = set(profile["include"]) & set(profile["exclude"])
9497
if len(union) > 0:

bandit/plugins/general_hardcoded_password.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ def hardcoded_password_default(context):
201201
- "token"
202202
- "secrete"
203203
204-
Note: this can be noisy and may generate false positives.
204+
Note: this can be noisy and may generate false positives. We do not
205+
report on None values which can be legitimately used as a default value,
206+
when initializing a function or class.
205207
206208
**Config Options:**
207209
@@ -242,5 +244,11 @@ def hardcoded_password_default(context):
242244
# go through all (param, value)s and look for candidates
243245
for key, val in zip(context.node.args.args, defs):
244246
if isinstance(key, (ast.Name, ast.arg)):
247+
# Skip if the default value is None
248+
if val is None or (
249+
isinstance(val, (ast.Constant, ast.NameConstant))
250+
and val.value is None
251+
):
252+
continue
245253
if isinstance(val, ast.Str) and RE_CANDIDATES.search(key.arg):
246254
return _report(val.s)
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Copyright (c) 2025 David Salvisberg
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
r"""
5+
============================================
6+
B704: Potential XSS on markupsafe.Markup use
7+
============================================
8+
9+
``markupsafe.Markup`` does not perform any escaping, so passing dynamic
10+
content, like f-strings, variables or interpolated strings will potentially
11+
lead to XSS vulnerabilities, especially if that data was submitted by users.
12+
13+
Instead you should interpolate the resulting ``markupsafe.Markup`` object,
14+
which will perform escaping, or use ``markupsafe.escape``.
15+
16+
17+
**Config Options:**
18+
19+
This plugin allows you to specify additional callable that should be treated
20+
like ``markupsafe.Markup``. By default we recognize ``flask.Markup`` as
21+
an alias, but there are other subclasses or similar classes in the wild
22+
that you may wish to treat the same.
23+
24+
Additionally there is a whitelist for callable names, whose result may
25+
be safely passed into ``markupsafe.Markup``. This is useful for escape
26+
functions like e.g. ``bleach.clean`` which don't themselves return
27+
``markupsafe.Markup``, so they need to be wrapped. Take care when using
28+
this setting, since incorrect use may introduce false negatives.
29+
30+
These two options can be set in a shared configuration section
31+
`markupsafe_xss`.
32+
33+
34+
.. code-block:: yaml
35+
36+
markupsafe_xss:
37+
# Recognize additional aliases
38+
extend_markup_names:
39+
- webhelpers.html.literal
40+
- my_package.Markup
41+
42+
# Allow the output of these functions to pass into Markup
43+
allowed_calls:
44+
- bleach.clean
45+
- my_package.sanitize
46+
47+
48+
:Example:
49+
50+
.. code-block:: none
51+
52+
>> Issue: [B704:markupsafe_markup_xss] Potential XSS with
53+
``markupsafe.Markup`` detected. Do not use ``Markup``
54+
on untrusted data.
55+
Severity: Medium Confidence: High
56+
CWE: CWE-79 (https://cwe.mitre.org/data/definitions/79.html)
57+
Location: ./examples/markupsafe_markup_xss.py:5:0
58+
4 content = "<script>alert('Hello, world!')</script>"
59+
5 Markup(f"unsafe {content}")
60+
6 flask.Markup("unsafe {}".format(content))
61+
62+
.. seealso::
63+
64+
- https://pypi.org/project/MarkupSafe/
65+
- https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup
66+
- https://cwe.mitre.org/data/definitions/79.html
67+
68+
.. versionadded:: 1.8.3
69+
70+
"""
71+
import ast
72+
73+
import bandit
74+
from bandit.core import issue
75+
from bandit.core import test_properties as test
76+
from bandit.core.utils import get_call_name
77+
78+
79+
def gen_config(name):
80+
if name == "markupsafe_xss":
81+
return {
82+
"extend_markup_names": [],
83+
"allowed_calls": [],
84+
}
85+
86+
87+
@test.takes_config("markupsafe_xss")
88+
@test.checks("Call")
89+
@test.test_id("B704")
90+
def markupsafe_markup_xss(context, config):
91+
92+
qualname = context.call_function_name_qual
93+
if qualname not in ("markupsafe.Markup", "flask.Markup"):
94+
if qualname not in config.get("extend_markup_names", []):
95+
# not a Markup call
96+
return None
97+
98+
args = context.node.args
99+
if not args or isinstance(args[0], ast.Constant):
100+
# both no arguments and a constant are fine
101+
return None
102+
103+
allowed_calls = config.get("allowed_calls", [])
104+
if (
105+
allowed_calls
106+
and isinstance(args[0], ast.Call)
107+
and get_call_name(args[0], context.import_aliases) in allowed_calls
108+
):
109+
# the argument contains a whitelisted call
110+
return None
111+
112+
return bandit.Issue(
113+
severity=bandit.MEDIUM,
114+
confidence=bandit.HIGH,
115+
cwe=issue.Cwe.XSS,
116+
text=f"Potential XSS with ``{qualname}`` detected. Do "
117+
f"not use ``{context.call_function_name}`` on untrusted data.",
118+
)

0 commit comments

Comments
 (0)