Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start testing with 3.14 alphas #1189

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
["3.11", "311"],
["3.12", "312"],
["3.13", "313"],
["3.14.0-alpha - 3.14", "314"],
]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
Expand Down
4 changes: 2 additions & 2 deletions bandit/core/blacklisting.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ def blacklist(context, config):
func = context.node.func
if isinstance(func, ast.Name) and func.id == "__import__":
if len(context.node.args):
if isinstance(context.node.args[0], ast.Str):
name = context.node.args[0].s
if isinstance(context.node.args[0], ast.Constant):
name = context.node.args[0].value
else:
# TODO(??): import through a variable, need symbol tab
name = "UNKNOWN"
Expand Down
22 changes: 7 additions & 15 deletions bandit/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,13 @@ def _get_literal_value(self, literal):
:param literal: The AST literal to convert
:return: The value of the AST literal
"""
if isinstance(literal, ast.Num):
literal_value = literal.n

elif isinstance(literal, ast.Str):
literal_value = literal.s
if isinstance(literal, ast.Constant):
if isinstance(literal.value, bool):
literal_value = str(literal.value)
elif literal.value is None:
literal_value = str(literal.value)
else:
literal_value = literal.value

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

elif isinstance(literal, ast.Ellipsis):
# what do we want to do with this?
literal_value = None

elif isinstance(literal, ast.Name):
literal_value = literal.id

elif isinstance(literal, ast.NameConstant):
literal_value = str(literal.value)

elif isinstance(literal, ast.Bytes):
literal_value = literal.s

else:
literal_value = None

Expand Down
4 changes: 2 additions & 2 deletions bandit/core/node_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def visit_Str(self, node):
:param node: The node that is being inspected
:return: -
"""
self.context["str"] = node.s
self.context["str"] = node.value
if not isinstance(node._bandit_parent, ast.Expr): # docstring
self.context["linerange"] = b_utils.linerange(node._bandit_parent)
self.update_scores(self.tester.run_tests(self.context, "Str"))
Expand All @@ -181,7 +181,7 @@ def visit_Bytes(self, node):
:param node: The node that is being inspected
:return: -
"""
self.context["bytes"] = node.s
self.context["bytes"] = node.value
if not isinstance(node._bandit_parent, ast.Expr): # docstring
self.context["linerange"] = b_utils.linerange(node._bandit_parent)
self.update_scores(self.tester.run_tests(self.context, "Bytes"))
Expand Down
22 changes: 18 additions & 4 deletions bandit/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,12 @@ def linerange(node):
def concat_string(node, stop=None):
"""Builds a string from a ast.BinOp chain.

This will build a string from a series of ast.Str nodes wrapped in
This will build a string from a series of ast.Constant nodes wrapped in
ast.BinOp nodes. Something like "a" + "b" + "c" or "a %s" % val etc.
The provided node can be any participant in the BinOp chain.

:param node: (ast.Str or ast.BinOp) The node to process
:param stop: (ast.Str or ast.BinOp) Optional base node to stop at
:param node: (ast.Constant or ast.BinOp) The node to process
:param stop: (ast.Constant or ast.BinOp) Optional base node to stop at
:returns: (Tuple) the root node of the expression, the string value
"""

Expand All @@ -300,7 +300,10 @@ def _get(node, bits, stop=None):
node = node._bandit_parent
if isinstance(node, ast.BinOp):
_get(node, bits, stop)
return (node, " ".join([x.s for x in bits if isinstance(x, ast.Str)]))
return (
node,
" ".join([x.value for x in bits if isinstance(x, ast.Constant)]),
)


def get_called_name(node):
Expand Down Expand Up @@ -361,6 +364,17 @@ def parse_ini_file(f_loc):
def check_ast_node(name):
"Check if the given name is that of a valid AST node."
try:
# These ast Node types don't exist in Python 3.14, but plugins may
# still check on them.
if sys.version_info >= (3, 14) and name in (
"Num",
"Str",
"Ellipsis",
"NameConstant",
"Bytes",
):
return name

node = getattr(ast, name)
if issubclass(node, ast.AST):
return name
Expand Down
8 changes: 4 additions & 4 deletions bandit/plugins/django_sql_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def django_extra_used(context):
if key in kwargs:
if isinstance(kwargs[key], ast.List):
for val in kwargs[key].elts:
if not isinstance(val, ast.Str):
if not isinstance(val, ast.Constant):
insecure = True
break
else:
Expand All @@ -77,12 +77,12 @@ def django_extra_used(context):
if not insecure and "select" in kwargs:
if isinstance(kwargs["select"], ast.Dict):
for k in kwargs["select"].keys:
if not isinstance(k, ast.Str):
if not isinstance(k, ast.Constant):
insecure = True
break
if not insecure:
for v in kwargs["select"].values:
if not isinstance(v, ast.Str):
if not isinstance(v, ast.Constant):
insecure = True
break
else:
Expand Down Expand Up @@ -135,7 +135,7 @@ def django_rawsql_used(context):
kwargs = keywords2dict(context.node.keywords)
sql = kwargs["sql"]

if not isinstance(sql, ast.Str):
if not isinstance(sql, ast.Constant):
return bandit.Issue(
severity=bandit.MEDIUM,
confidence=bandit.MEDIUM,
Expand Down
17 changes: 10 additions & 7 deletions bandit/plugins/django_xss.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def evaluate_var(xss_var, parent, until, ignore_nodes=None):
break
to = analyser.is_assigned(node)
if to:
if isinstance(to, ast.Str):
if isinstance(to, ast.Constant):
secure = True
elif isinstance(to, ast.Name):
secure = evaluate_var(to, parent, to.lineno, ignore_nodes)
Expand All @@ -105,7 +105,7 @@ def evaluate_var(xss_var, parent, until, ignore_nodes=None):
elif isinstance(to, (list, tuple)):
num_secure = 0
for some_to in to:
if isinstance(some_to, ast.Str):
if isinstance(some_to, ast.Constant):
num_secure += 1
elif isinstance(some_to, ast.Name):
if evaluate_var(
Expand All @@ -131,7 +131,10 @@ def evaluate_call(call, parent, ignore_nodes=None):
secure = False
evaluate = False
if isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute):
if isinstance(call.func.value, ast.Str) and call.func.attr == "format":
if (
isinstance(call.func.value, ast.Constant)
and call.func.attr == "format"
):
evaluate = True
if call.keywords:
evaluate = False # TODO(??) get support for this
Expand All @@ -140,7 +143,7 @@ def evaluate_call(call, parent, ignore_nodes=None):
args = list(call.args)
num_secure = 0
for arg in args:
if isinstance(arg, ast.Str):
if isinstance(arg, ast.Constant):
num_secure += 1
elif isinstance(arg, ast.Name):
if evaluate_var(arg, parent, call.lineno, ignore_nodes):
Expand All @@ -167,7 +170,7 @@ def evaluate_call(call, parent, ignore_nodes=None):
def transform2call(var):
if isinstance(var, ast.BinOp):
is_mod = isinstance(var.op, ast.Mod)
is_left_str = isinstance(var.left, ast.Str)
is_left_str = isinstance(var.left, ast.Constant)
if is_mod and is_left_str:
new_call = ast.Call()
new_call.args = []
Expand Down Expand Up @@ -212,7 +215,7 @@ def check_risk(node):
secure = evaluate_call(xss_var, parent)
elif isinstance(xss_var, ast.BinOp):
is_mod = isinstance(xss_var.op, ast.Mod)
is_left_str = isinstance(xss_var.left, ast.Str)
is_left_str = isinstance(xss_var.left, ast.Constant)
if is_mod and is_left_str:
parent = node._bandit_parent
while not isinstance(parent, (ast.Module, ast.FunctionDef)):
Expand Down Expand Up @@ -272,5 +275,5 @@ def django_mark_safe(context):
]
if context.call_function_name in affected_functions:
xss = context.node.args[0]
if not isinstance(xss, ast.Str):
if not isinstance(xss, ast.Constant):
return check_risk(context.node)
32 changes: 16 additions & 16 deletions bandit/plugins/general_hardcoded_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,45 +83,45 @@ def hardcoded_password_string(context):
# looks for "candidate='some_string'"
for targ in node._bandit_parent.targets:
if isinstance(targ, ast.Name) and RE_CANDIDATES.search(targ.id):
return _report(node.s)
return _report(node.value)
elif isinstance(targ, ast.Attribute) and RE_CANDIDATES.search(
targ.attr
):
return _report(node.s)
return _report(node.value)

elif isinstance(
node._bandit_parent, ast.Subscript
) and RE_CANDIDATES.search(node.s):
) and RE_CANDIDATES.search(node.value):
# Py39+: looks for "dict[candidate]='some_string'"
# subscript -> index -> string
assign = node._bandit_parent._bandit_parent
if isinstance(assign, ast.Assign) and isinstance(
assign.value, ast.Str
assign.value, ast.Constant
):
return _report(assign.value.s)
return _report(assign.value.value)

elif isinstance(node._bandit_parent, ast.Index) and RE_CANDIDATES.search(
node.s
node.value
):
# looks for "dict[candidate]='some_string'"
# assign -> subscript -> index -> string
assign = node._bandit_parent._bandit_parent._bandit_parent
if isinstance(assign, ast.Assign) and isinstance(
assign.value, ast.Str
assign.value, ast.Constant
):
return _report(assign.value.s)
return _report(assign.value.value)

elif isinstance(node._bandit_parent, ast.Compare):
# looks for "candidate == 'some_string'"
comp = node._bandit_parent
if isinstance(comp.left, ast.Name):
if RE_CANDIDATES.search(comp.left.id):
if isinstance(comp.comparators[0], ast.Str):
return _report(comp.comparators[0].s)
if isinstance(comp.comparators[0], ast.Constant):
return _report(comp.comparators[0].value)
elif isinstance(comp.left, ast.Attribute):
if RE_CANDIDATES.search(comp.left.attr):
if isinstance(comp.comparators[0], ast.Str):
return _report(comp.comparators[0].s)
if isinstance(comp.comparators[0], ast.Constant):
return _report(comp.comparators[0].value)


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


@test.checks("FunctionDef")
Expand Down Expand Up @@ -242,5 +242,5 @@ def hardcoded_password_default(context):
# go through all (param, value)s and look for candidates
for key, val in zip(context.node.args.args, defs):
if isinstance(key, (ast.Name, ast.arg)):
if isinstance(val, ast.Str) and RE_CANDIDATES.search(key.arg):
return _report(val.s)
if isinstance(val, ast.Constant) and RE_CANDIDATES.search(key.arg):
return _report(val.value)
12 changes: 6 additions & 6 deletions bandit/plugins/injection_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


def _evaluate_shell_call(context):
no_formatting = isinstance(context.node.args[0], ast.Str)
no_formatting = isinstance(context.node.args[0], ast.Constant)

if no_formatting:
return bandit.LOW
Expand Down Expand Up @@ -83,16 +83,14 @@ def has_shell(context):
for key in keywords:
if key.arg == "shell":
val = key.value
if isinstance(val, ast.Num):
result = bool(val.n)
if isinstance(val, ast.Constant):
result = bool(val.value)
elif isinstance(val, ast.List):
result = bool(val.elts)
elif isinstance(val, ast.Dict):
result = bool(val.keys)
elif isinstance(val, ast.Name) and val.id in ["False", "None"]:
result = False
elif isinstance(val, ast.NameConstant):
result = val.value
else:
result = True
return result
Expand Down Expand Up @@ -687,7 +685,9 @@ def start_process_with_partial_path(context, config):
node = node.elts[0]

# make sure the param is a string literal and not a var name
if isinstance(node, ast.Str) and not full_path_match.match(node.s):
if isinstance(node, ast.Constant) and not full_path_match.match(
node.value
):
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.HIGH,
Expand Down
6 changes: 3 additions & 3 deletions bandit/plugins/injection_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def _evaluate_ast(node):
elif isinstance(
node._bandit_parent, ast.Attribute
) and node._bandit_parent.attr in ("format", "replace"):
statement = node.s
statement = node.value
# Hierarchy for "".format() is Wrapper -> Call -> Attribute -> Str
wrapper = node._bandit_parent._bandit_parent._bandit_parent
if node._bandit_parent.attr == "replace":
Expand All @@ -107,14 +107,14 @@ def _evaluate_ast(node):
substrings = [
child
for child in node._bandit_parent.values
if isinstance(child, ast.Str)
if isinstance(child, ast.Constant)
]
# JoinedStr consists of list of Constant and FormattedValue
# instances. Let's perform one test for the whole string
# and abandon all parts except the first one to raise one
# failed test instead of many for the same SQL statement.
if substrings and node == substrings[0]:
statement = "".join([str(child.s) for child in substrings])
statement = "".join([str(child.value) for child in substrings])
wrapper = node._bandit_parent._bandit_parent

if isinstance(wrapper, ast.Call): # wrapped in "execute" call?
Expand Down
2 changes: 1 addition & 1 deletion bandit/plugins/tarfile_unsafe_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def is_filter_data(context):
for keyword in context.node.keywords:
if keyword.arg == "filter":
arg = keyword.value
return isinstance(arg, ast.Str) and arg.s == "data"
return isinstance(arg, ast.Constant) and arg.value == "data"


@test.test_id("B202")
Expand Down
Loading