Skip to content

Commit 13d3406

Browse files
authored
Start testing with 3.14 alphas (#1189)
* Test with official 3.13 and 3.14 alphas This change updates the unit testing to use the official Python 3.13 released yesterday (Oct 7). It also starts testing against the alpha versions of Python 3.14 to catch potential problems early before it is officially released. Signed-off-by: Eric Brown <eric_wade_brown@yahoo.com> * Update setup.cfg * Update setup.cfg Signed-off-by: Eric Brown <eric_wade_brown@yahoo.com> --------- Signed-off-by: Eric Brown <eric_wade_brown@yahoo.com>
1 parent 1abd1d7 commit 13d3406

12 files changed

+83
-76
lines changed

.github/workflows/pythonpackage.yml

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
["3.11", "311"],
5353
["3.12", "312"],
5454
["3.13", "313"],
55+
["3.14.0-alpha - 3.14", "314"],
5556
]
5657
os: [ubuntu-latest, macos-latest]
5758
runs-on: ${{ matrix.os }}

bandit/core/blacklisting.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ def blacklist(context, config):
3535
func = context.node.func
3636
if isinstance(func, ast.Name) and func.id == "__import__":
3737
if len(context.node.args):
38-
if isinstance(context.node.args[0], ast.Str):
39-
name = context.node.args[0].s
38+
if isinstance(context.node.args[0], ast.Constant):
39+
name = context.node.args[0].value
4040
else:
4141
# TODO(??): import through a variable, need symbol tab
4242
name = "UNKNOWN"

bandit/core/context.py

+7-15
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,13 @@ def _get_literal_value(self, literal):
178178
:param literal: The AST literal to convert
179179
:return: The value of the AST literal
180180
"""
181-
if isinstance(literal, ast.Num):
182-
literal_value = literal.n
183-
184-
elif isinstance(literal, ast.Str):
185-
literal_value = literal.s
181+
if isinstance(literal, ast.Constant):
182+
if isinstance(literal.value, bool):
183+
literal_value = str(literal.value)
184+
elif literal.value is None:
185+
literal_value = str(literal.value)
186+
else:
187+
literal_value = literal.value
186188

187189
elif isinstance(literal, ast.List):
188190
return_list = list()
@@ -205,19 +207,9 @@ def _get_literal_value(self, literal):
205207
elif isinstance(literal, ast.Dict):
206208
literal_value = dict(zip(literal.keys, literal.values))
207209

208-
elif isinstance(literal, ast.Ellipsis):
209-
# what do we want to do with this?
210-
literal_value = None
211-
212210
elif isinstance(literal, ast.Name):
213211
literal_value = literal.id
214212

215-
elif isinstance(literal, ast.NameConstant):
216-
literal_value = str(literal.value)
217-
218-
elif isinstance(literal, ast.Bytes):
219-
literal_value = literal.s
220-
221213
else:
222214
literal_value = None
223215

bandit/core/node_visitor.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def visit_Str(self, node):
168168
:param node: The node that is being inspected
169169
:return: -
170170
"""
171-
self.context["str"] = node.s
171+
self.context["str"] = node.value
172172
if not isinstance(node._bandit_parent, ast.Expr): # docstring
173173
self.context["linerange"] = b_utils.linerange(node._bandit_parent)
174174
self.update_scores(self.tester.run_tests(self.context, "Str"))
@@ -181,7 +181,7 @@ def visit_Bytes(self, node):
181181
:param node: The node that is being inspected
182182
:return: -
183183
"""
184-
self.context["bytes"] = node.s
184+
self.context["bytes"] = node.value
185185
if not isinstance(node._bandit_parent, ast.Expr): # docstring
186186
self.context["linerange"] = b_utils.linerange(node._bandit_parent)
187187
self.update_scores(self.tester.run_tests(self.context, "Bytes"))

bandit/core/utils.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,12 @@ def linerange(node):
273273
def concat_string(node, stop=None):
274274
"""Builds a string from a ast.BinOp chain.
275275
276-
This will build a string from a series of ast.Str nodes wrapped in
276+
This will build a string from a series of ast.Constant nodes wrapped in
277277
ast.BinOp nodes. Something like "a" + "b" + "c" or "a %s" % val etc.
278278
The provided node can be any participant in the BinOp chain.
279279
280-
:param node: (ast.Str or ast.BinOp) The node to process
281-
:param stop: (ast.Str or ast.BinOp) Optional base node to stop at
280+
:param node: (ast.Constant or ast.BinOp) The node to process
281+
:param stop: (ast.Constant or ast.BinOp) Optional base node to stop at
282282
:returns: (Tuple) the root node of the expression, the string value
283283
"""
284284

@@ -300,7 +300,10 @@ def _get(node, bits, stop=None):
300300
node = node._bandit_parent
301301
if isinstance(node, ast.BinOp):
302302
_get(node, bits, stop)
303-
return (node, " ".join([x.s for x in bits if isinstance(x, ast.Str)]))
303+
return (
304+
node,
305+
" ".join([x.value for x in bits if isinstance(x, ast.Constant)]),
306+
)
304307

305308

306309
def get_called_name(node):
@@ -361,6 +364,17 @@ def parse_ini_file(f_loc):
361364
def check_ast_node(name):
362365
"Check if the given name is that of a valid AST node."
363366
try:
367+
# These ast Node types don't exist in Python 3.14, but plugins may
368+
# still check on them.
369+
if sys.version_info >= (3, 14) and name in (
370+
"Num",
371+
"Str",
372+
"Ellipsis",
373+
"NameConstant",
374+
"Bytes",
375+
):
376+
return name
377+
364378
node = getattr(ast, name)
365379
if issubclass(node, ast.AST):
366380
return name

bandit/plugins/django_sql_injection.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def django_extra_used(context):
6868
if key in kwargs:
6969
if isinstance(kwargs[key], ast.List):
7070
for val in kwargs[key].elts:
71-
if not isinstance(val, ast.Str):
71+
if not isinstance(val, ast.Constant):
7272
insecure = True
7373
break
7474
else:
@@ -77,12 +77,12 @@ def django_extra_used(context):
7777
if not insecure and "select" in kwargs:
7878
if isinstance(kwargs["select"], ast.Dict):
7979
for k in kwargs["select"].keys:
80-
if not isinstance(k, ast.Str):
80+
if not isinstance(k, ast.Constant):
8181
insecure = True
8282
break
8383
if not insecure:
8484
for v in kwargs["select"].values:
85-
if not isinstance(v, ast.Str):
85+
if not isinstance(v, ast.Constant):
8686
insecure = True
8787
break
8888
else:
@@ -135,7 +135,7 @@ def django_rawsql_used(context):
135135
kwargs = keywords2dict(context.node.keywords)
136136
sql = kwargs["sql"]
137137

138-
if not isinstance(sql, ast.Str):
138+
if not isinstance(sql, ast.Constant):
139139
return bandit.Issue(
140140
severity=bandit.MEDIUM,
141141
confidence=bandit.MEDIUM,

bandit/plugins/django_xss.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def evaluate_var(xss_var, parent, until, ignore_nodes=None):
9696
break
9797
to = analyser.is_assigned(node)
9898
if to:
99-
if isinstance(to, ast.Str):
99+
if isinstance(to, ast.Constant):
100100
secure = True
101101
elif isinstance(to, ast.Name):
102102
secure = evaluate_var(to, parent, to.lineno, ignore_nodes)
@@ -105,7 +105,7 @@ def evaluate_var(xss_var, parent, until, ignore_nodes=None):
105105
elif isinstance(to, (list, tuple)):
106106
num_secure = 0
107107
for some_to in to:
108-
if isinstance(some_to, ast.Str):
108+
if isinstance(some_to, ast.Constant):
109109
num_secure += 1
110110
elif isinstance(some_to, ast.Name):
111111
if evaluate_var(
@@ -131,7 +131,10 @@ def evaluate_call(call, parent, ignore_nodes=None):
131131
secure = False
132132
evaluate = False
133133
if isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute):
134-
if isinstance(call.func.value, ast.Str) and call.func.attr == "format":
134+
if (
135+
isinstance(call.func.value, ast.Constant)
136+
and call.func.attr == "format"
137+
):
135138
evaluate = True
136139
if call.keywords:
137140
evaluate = False # TODO(??) get support for this
@@ -140,7 +143,7 @@ def evaluate_call(call, parent, ignore_nodes=None):
140143
args = list(call.args)
141144
num_secure = 0
142145
for arg in args:
143-
if isinstance(arg, ast.Str):
146+
if isinstance(arg, ast.Constant):
144147
num_secure += 1
145148
elif isinstance(arg, ast.Name):
146149
if evaluate_var(arg, parent, call.lineno, ignore_nodes):
@@ -167,7 +170,7 @@ def evaluate_call(call, parent, ignore_nodes=None):
167170
def transform2call(var):
168171
if isinstance(var, ast.BinOp):
169172
is_mod = isinstance(var.op, ast.Mod)
170-
is_left_str = isinstance(var.left, ast.Str)
173+
is_left_str = isinstance(var.left, ast.Constant)
171174
if is_mod and is_left_str:
172175
new_call = ast.Call()
173176
new_call.args = []
@@ -212,7 +215,7 @@ def check_risk(node):
212215
secure = evaluate_call(xss_var, parent)
213216
elif isinstance(xss_var, ast.BinOp):
214217
is_mod = isinstance(xss_var.op, ast.Mod)
215-
is_left_str = isinstance(xss_var.left, ast.Str)
218+
is_left_str = isinstance(xss_var.left, ast.Constant)
216219
if is_mod and is_left_str:
217220
parent = node._bandit_parent
218221
while not isinstance(parent, (ast.Module, ast.FunctionDef)):
@@ -272,5 +275,5 @@ def django_mark_safe(context):
272275
]
273276
if context.call_function_name in affected_functions:
274277
xss = context.node.args[0]
275-
if not isinstance(xss, ast.Str):
278+
if not isinstance(xss, ast.Constant):
276279
return check_risk(context.node)

bandit/plugins/general_hardcoded_password.py

+16-16
Original file line numberDiff line numberDiff line change
@@ -83,45 +83,45 @@ def hardcoded_password_string(context):
8383
# looks for "candidate='some_string'"
8484
for targ in node._bandit_parent.targets:
8585
if isinstance(targ, ast.Name) and RE_CANDIDATES.search(targ.id):
86-
return _report(node.s)
86+
return _report(node.value)
8787
elif isinstance(targ, ast.Attribute) and RE_CANDIDATES.search(
8888
targ.attr
8989
):
90-
return _report(node.s)
90+
return _report(node.value)
9191

9292
elif isinstance(
9393
node._bandit_parent, ast.Subscript
94-
) and RE_CANDIDATES.search(node.s):
94+
) and RE_CANDIDATES.search(node.value):
9595
# Py39+: looks for "dict[candidate]='some_string'"
9696
# subscript -> index -> string
9797
assign = node._bandit_parent._bandit_parent
9898
if isinstance(assign, ast.Assign) and isinstance(
99-
assign.value, ast.Str
99+
assign.value, ast.Constant
100100
):
101-
return _report(assign.value.s)
101+
return _report(assign.value.value)
102102

103103
elif isinstance(node._bandit_parent, ast.Index) and RE_CANDIDATES.search(
104-
node.s
104+
node.value
105105
):
106106
# looks for "dict[candidate]='some_string'"
107107
# assign -> subscript -> index -> string
108108
assign = node._bandit_parent._bandit_parent._bandit_parent
109109
if isinstance(assign, ast.Assign) and isinstance(
110-
assign.value, ast.Str
110+
assign.value, ast.Constant
111111
):
112-
return _report(assign.value.s)
112+
return _report(assign.value.value)
113113

114114
elif isinstance(node._bandit_parent, ast.Compare):
115115
# looks for "candidate == 'some_string'"
116116
comp = node._bandit_parent
117117
if isinstance(comp.left, ast.Name):
118118
if RE_CANDIDATES.search(comp.left.id):
119-
if isinstance(comp.comparators[0], ast.Str):
120-
return _report(comp.comparators[0].s)
119+
if isinstance(comp.comparators[0], ast.Constant):
120+
return _report(comp.comparators[0].value)
121121
elif isinstance(comp.left, ast.Attribute):
122122
if RE_CANDIDATES.search(comp.left.attr):
123-
if isinstance(comp.comparators[0], ast.Str):
124-
return _report(comp.comparators[0].s)
123+
if isinstance(comp.comparators[0], ast.Constant):
124+
return _report(comp.comparators[0].value)
125125

126126

127127
@test.checks("Call")
@@ -176,8 +176,8 @@ def hardcoded_password_funcarg(context):
176176
"""
177177
# looks for "function(candidate='some_string')"
178178
for kw in context.node.keywords:
179-
if isinstance(kw.value, ast.Str) and RE_CANDIDATES.search(kw.arg):
180-
return _report(kw.value.s)
179+
if isinstance(kw.value, ast.Constant) and RE_CANDIDATES.search(kw.arg):
180+
return _report(kw.value.value)
181181

182182

183183
@test.checks("FunctionDef")
@@ -242,5 +242,5 @@ def hardcoded_password_default(context):
242242
# go through all (param, value)s and look for candidates
243243
for key, val in zip(context.node.args.args, defs):
244244
if isinstance(key, (ast.Name, ast.arg)):
245-
if isinstance(val, ast.Str) and RE_CANDIDATES.search(key.arg):
246-
return _report(val.s)
245+
if isinstance(val, ast.Constant) and RE_CANDIDATES.search(key.arg):
246+
return _report(val.value)

bandit/plugins/injection_shell.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
def _evaluate_shell_call(context):
18-
no_formatting = isinstance(context.node.args[0], ast.Str)
18+
no_formatting = isinstance(context.node.args[0], ast.Constant)
1919

2020
if no_formatting:
2121
return bandit.LOW
@@ -83,16 +83,14 @@ def has_shell(context):
8383
for key in keywords:
8484
if key.arg == "shell":
8585
val = key.value
86-
if isinstance(val, ast.Num):
87-
result = bool(val.n)
86+
if isinstance(val, ast.Constant):
87+
result = bool(val.value)
8888
elif isinstance(val, ast.List):
8989
result = bool(val.elts)
9090
elif isinstance(val, ast.Dict):
9191
result = bool(val.keys)
9292
elif isinstance(val, ast.Name) and val.id in ["False", "None"]:
9393
result = False
94-
elif isinstance(val, ast.NameConstant):
95-
result = val.value
9694
else:
9795
result = True
9896
return result
@@ -687,7 +685,9 @@ def start_process_with_partial_path(context, config):
687685
node = node.elts[0]
688686

689687
# make sure the param is a string literal and not a var name
690-
if isinstance(node, ast.Str) and not full_path_match.match(node.s):
688+
if isinstance(node, ast.Constant) and not full_path_match.match(
689+
node.value
690+
):
691691
return bandit.Issue(
692692
severity=bandit.LOW,
693693
confidence=bandit.HIGH,

bandit/plugins/injection_sql.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def _evaluate_ast(node):
9696
elif isinstance(
9797
node._bandit_parent, ast.Attribute
9898
) and node._bandit_parent.attr in ("format", "replace"):
99-
statement = node.s
99+
statement = node.value
100100
# Hierarchy for "".format() is Wrapper -> Call -> Attribute -> Str
101101
wrapper = node._bandit_parent._bandit_parent._bandit_parent
102102
if node._bandit_parent.attr == "replace":
@@ -107,14 +107,14 @@ def _evaluate_ast(node):
107107
substrings = [
108108
child
109109
for child in node._bandit_parent.values
110-
if isinstance(child, ast.Str)
110+
if isinstance(child, ast.Constant)
111111
]
112112
# JoinedStr consists of list of Constant and FormattedValue
113113
# instances. Let's perform one test for the whole string
114114
# and abandon all parts except the first one to raise one
115115
# failed test instead of many for the same SQL statement.
116116
if substrings and node == substrings[0]:
117-
statement = "".join([str(child.s) for child in substrings])
117+
statement = "".join([str(child.value) for child in substrings])
118118
wrapper = node._bandit_parent._bandit_parent
119119

120120
if isinstance(wrapper, ast.Call): # wrapped in "execute" call?

bandit/plugins/tarfile_unsafe_members.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def is_filter_data(context):
9898
for keyword in context.node.keywords:
9999
if keyword.arg == "filter":
100100
arg = keyword.value
101-
return isinstance(arg, ast.Str) and arg.s == "data"
101+
return isinstance(arg, ast.Constant) and arg.value == "data"
102102

103103

104104
@test.test_id("B202")

0 commit comments

Comments
 (0)