Skip to content

Commit ecb4aea

Browse files
authored
Merge branch 'main' into pre-commit-ci-update-config
2 parents f8944d0 + 5c30350 commit ecb4aea

14 files changed

Lines changed: 142 additions & 79 deletions

.github/workflows/pythonpackage.yml

Lines changed: 1 addition & 0 deletions
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", "314"],
5556
]
5657
os: [ubuntu-latest, macos-latest]
5758
runs-on: ${{ matrix.os }}

bandit/core/blacklisting.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ 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(
39+
context.node.args[0], ast.Constant
40+
) and isinstance(context.node.args[0].value, str):
41+
name = context.node.args[0].value
4042
else:
4143
# TODO(??): import through a variable, need symbol tab
4244
name = "UNKNOWN"

bandit/core/context.py

Lines changed: 7 additions & 15 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 18 additions & 4 deletions
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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ 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 (
72+
isinstance(val, ast.Constant)
73+
and isinstance(val.value, str)
74+
):
7275
insecure = True
7376
break
7477
else:
@@ -77,12 +80,18 @@ def django_extra_used(context):
7780
if not insecure and "select" in kwargs:
7881
if isinstance(kwargs["select"], ast.Dict):
7982
for k in kwargs["select"].keys:
80-
if not isinstance(k, ast.Str):
83+
if not (
84+
isinstance(k, ast.Constant)
85+
and isinstance(k.value, str)
86+
):
8187
insecure = True
8288
break
8389
if not insecure:
8490
for v in kwargs["select"].values:
85-
if not isinstance(v, ast.Str):
91+
if not (
92+
isinstance(v, ast.Constant)
93+
and isinstance(v.value, str)
94+
):
8695
insecure = True
8796
break
8897
else:
@@ -135,7 +144,9 @@ def django_rawsql_used(context):
135144
kwargs = keywords2dict(context.node.keywords)
136145
sql = kwargs["sql"]
137146

138-
if not isinstance(sql, ast.Str):
147+
if not (
148+
isinstance(sql, ast.Constant) and isinstance(sql.value, str)
149+
):
139150
return bandit.Issue(
140151
severity=bandit.MEDIUM,
141152
confidence=bandit.MEDIUM,

bandit/plugins/django_xss.py

Lines changed: 18 additions & 7 deletions
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) and isinstance(to.value, str):
100100
secure = True
101101
elif isinstance(to, ast.Name):
102102
secure = evaluate_var(to, parent, to.lineno, ignore_nodes)
@@ -105,7 +105,9 @@ 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) and isinstance(
109+
some_to.value, str
110+
):
109111
num_secure += 1
110112
elif isinstance(some_to, ast.Name):
111113
if evaluate_var(
@@ -131,7 +133,10 @@ def evaluate_call(call, parent, ignore_nodes=None):
131133
secure = False
132134
evaluate = False
133135
if isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute):
134-
if isinstance(call.func.value, ast.Str) and call.func.attr == "format":
136+
if (
137+
isinstance(call.func.value, ast.Constant)
138+
and call.func.attr == "format"
139+
):
135140
evaluate = True
136141
if call.keywords:
137142
evaluate = False # TODO(??) get support for this
@@ -140,7 +145,7 @@ def evaluate_call(call, parent, ignore_nodes=None):
140145
args = list(call.args)
141146
num_secure = 0
142147
for arg in args:
143-
if isinstance(arg, ast.Str):
148+
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
144149
num_secure += 1
145150
elif isinstance(arg, ast.Name):
146151
if evaluate_var(arg, parent, call.lineno, ignore_nodes):
@@ -167,7 +172,9 @@ def evaluate_call(call, parent, ignore_nodes=None):
167172
def transform2call(var):
168173
if isinstance(var, ast.BinOp):
169174
is_mod = isinstance(var.op, ast.Mod)
170-
is_left_str = isinstance(var.left, ast.Str)
175+
is_left_str = isinstance(var.left, ast.Constant) and isinstance(
176+
var.left.value, str
177+
)
171178
if is_mod and is_left_str:
172179
new_call = ast.Call()
173180
new_call.args = []
@@ -212,7 +219,9 @@ def check_risk(node):
212219
secure = evaluate_call(xss_var, parent)
213220
elif isinstance(xss_var, ast.BinOp):
214221
is_mod = isinstance(xss_var.op, ast.Mod)
215-
is_left_str = isinstance(xss_var.left, ast.Str)
222+
is_left_str = isinstance(xss_var.left, ast.Constant) and isinstance(
223+
xss_var.left.value, str
224+
)
216225
if is_mod and is_left_str:
217226
parent = node._bandit_parent
218227
while not isinstance(parent, (ast.Module, ast.FunctionDef)):
@@ -272,5 +281,7 @@ def django_mark_safe(context):
272281
]
273282
if context.call_function_name in affected_functions:
274283
xss = context.node.args[0]
275-
if not isinstance(xss, ast.Str):
284+
if not (
285+
isinstance(xss, ast.Constant) and isinstance(xss.value, str)
286+
):
276287
return check_risk(context.node)

bandit/plugins/general_hardcoded_password.py

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -83,45 +83,53 @@ 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
98-
if isinstance(assign, ast.Assign) and isinstance(
99-
assign.value, ast.Str
98+
if (
99+
isinstance(assign, ast.Assign)
100+
and isinstance(assign.value, ast.Constant)
101+
and isinstance(assign.value.value, str)
100102
):
101-
return _report(assign.value.s)
103+
return _report(assign.value.value)
102104

103105
elif isinstance(node._bandit_parent, ast.Index) and RE_CANDIDATES.search(
104-
node.s
106+
node.value
105107
):
106108
# looks for "dict[candidate]='some_string'"
107109
# assign -> subscript -> index -> string
108110
assign = node._bandit_parent._bandit_parent._bandit_parent
109-
if isinstance(assign, ast.Assign) and isinstance(
110-
assign.value, ast.Str
111+
if (
112+
isinstance(assign, ast.Assign)
113+
and isinstance(assign.value, ast.Constant)
114+
and isinstance(assign.value.value, str)
111115
):
112-
return _report(assign.value.s)
116+
return _report(assign.value.value)
113117

114118
elif isinstance(node._bandit_parent, ast.Compare):
115119
# looks for "candidate == 'some_string'"
116120
comp = node._bandit_parent
117121
if isinstance(comp.left, ast.Name):
118122
if RE_CANDIDATES.search(comp.left.id):
119-
if isinstance(comp.comparators[0], ast.Str):
120-
return _report(comp.comparators[0].s)
123+
if isinstance(
124+
comp.comparators[0], ast.Constant
125+
) and isinstance(comp.comparators[0].value, str):
126+
return _report(comp.comparators[0].value)
121127
elif isinstance(comp.left, ast.Attribute):
122128
if RE_CANDIDATES.search(comp.left.attr):
123-
if isinstance(comp.comparators[0], ast.Str):
124-
return _report(comp.comparators[0].s)
129+
if isinstance(
130+
comp.comparators[0], ast.Constant
131+
) and isinstance(comp.comparators[0].value, str):
132+
return _report(comp.comparators[0].value)
125133

126134

127135
@test.checks("Call")
@@ -176,8 +184,12 @@ def hardcoded_password_funcarg(context):
176184
"""
177185
# looks for "function(candidate='some_string')"
178186
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)
187+
if (
188+
isinstance(kw.value, ast.Constant)
189+
and isinstance(kw.value.value, str)
190+
and RE_CANDIDATES.search(kw.arg)
191+
):
192+
return _report(kw.value.value)
181193

182194

183195
@test.checks("FunctionDef")
@@ -246,9 +258,12 @@ def hardcoded_password_default(context):
246258
if isinstance(key, (ast.Name, ast.arg)):
247259
# Skip if the default value is None
248260
if val is None or (
249-
isinstance(val, (ast.Constant, ast.NameConstant))
250-
and val.value is None
261+
isinstance(val, ast.Constant) and val.value is None
251262
):
252263
continue
253-
if isinstance(val, ast.Str) and RE_CANDIDATES.search(key.arg):
254-
return _report(val.s)
264+
if (
265+
isinstance(val, ast.Constant)
266+
and isinstance(val.value, str)
267+
and RE_CANDIDATES.search(key.arg)
268+
):
269+
return _report(val.value)

bandit/plugins/injection_shell.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616

1717
def _evaluate_shell_call(context):
18-
no_formatting = isinstance(context.node.args[0], ast.Str)
18+
no_formatting = isinstance(
19+
context.node.args[0], ast.Constant
20+
) and isinstance(context.node.args[0].value, str)
1921

2022
if no_formatting:
2123
return bandit.LOW
@@ -83,15 +85,19 @@ def has_shell(context):
8385
for key in keywords:
8486
if key.arg == "shell":
8587
val = key.value
86-
if isinstance(val, ast.Num):
87-
result = bool(val.n)
88+
if isinstance(val, ast.Constant) and (
89+
isinstance(val.value, int)
90+
or isinstance(val.value, float)
91+
or isinstance(val.value, complex)
92+
):
93+
result = bool(val.value)
8894
elif isinstance(val, ast.List):
8995
result = bool(val.elts)
9096
elif isinstance(val, ast.Dict):
9197
result = bool(val.keys)
9298
elif isinstance(val, ast.Name) and val.id in ["False", "None"]:
9399
result = False
94-
elif isinstance(val, ast.NameConstant):
100+
elif isinstance(val, ast.Constant):
95101
result = val.value
96102
else:
97103
result = True
@@ -687,7 +693,11 @@ def start_process_with_partial_path(context, config):
687693
node = node.elts[0]
688694

689695
# 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):
696+
if (
697+
isinstance(node, ast.Constant)
698+
and isinstance(node.value, str)
699+
and not full_path_match.match(node.value)
700+
):
691701
return bandit.Issue(
692702
severity=bandit.LOW,
693703
confidence=bandit.HIGH,

0 commit comments

Comments
 (0)