Skip to content

Commit

Permalink
Merge branch 'main' into sphinx_pyscript
Browse files Browse the repository at this point in the history
  • Loading branch information
lukehinds authored Feb 9, 2025
2 parents 26c48cd + affd4fd commit fac7b52
Show file tree
Hide file tree
Showing 26 changed files with 313 additions and 131 deletions.
4 changes: 3 additions & 1 deletion .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ body:
label: Bandit version
description: Run "bandit --version" if unsure of version number
options:
- 1.8.0 (Default)
- 1.8.2 (Default)
- 1.8.1
- 1.8.0
- 1.7.10
- 1.7.9
- 1.7.8
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-publish-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
with:
context: .
file: ./docker/Dockerfile
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ 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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repos:
- id: reorder-python-imports
args: [--application-directories, '.:src', --py38-plus]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.10.0
rev: 25.1.0
hooks:
- id: black
args: [--line-length=79, --target-version=py38]
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.Constant):
name = context.node.args[0].value
if isinstance(context.node.args[0], ast.Str):
name = context.node.args[0].s
else:
# TODO(??): import through a variable, need symbol tab
name = "UNKNOWN"
Expand Down
26 changes: 19 additions & 7 deletions bandit/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,11 @@ def _get_literal_value(self, literal):
:param literal: The AST literal to convert
:return: The value of the AST literal
"""
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
if isinstance(literal, ast.Num):
literal_value = literal.n

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

elif isinstance(literal, ast.List):
return_list = list()
Expand All @@ -207,9 +205,19 @@ 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 Expand Up @@ -310,3 +318,7 @@ def filename(self):
@property
def file_data(self):
return self._context.get("file_data")

@property
def import_aliases(self):
return self._context.get("import_aliases")
7 changes: 5 additions & 2 deletions bandit/core/extension_loader.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#
# SPDX-License-Identifier: Apache-2.0
import logging
import sys

from stevedore import extension

from bandit.core import utils

LOG = logging.getLogger(__name__)


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

for exc in profile["exclude"]:
if not self.check_id(exc):
raise ValueError(f"Unknown test found in profile: {exc}")
LOG.warning(f"Unknown test found in profile: {exc}")

union = set(profile["include"]) & set(profile["exclude"])
if len(union) > 0:
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.value
self.context["str"] = node.s
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.value
self.context["bytes"] = node.s
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: 4 additions & 18 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.Constant nodes wrapped in
This will build a string from a series of ast.Str 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.Constant or ast.BinOp) The node to process
:param stop: (ast.Constant or ast.BinOp) Optional base node to stop at
:param node: (ast.Str or ast.BinOp) The node to process
:param stop: (ast.Str or ast.BinOp) Optional base node to stop at
:returns: (Tuple) the root node of the expression, the string value
"""

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


def get_called_name(node):
Expand Down Expand Up @@ -364,17 +361,6 @@ 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.Constant):
if not isinstance(val, ast.Str):
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.Constant):
if not isinstance(k, ast.Str):
insecure = True
break
if not insecure:
for v in kwargs["select"].values:
if not isinstance(v, ast.Constant):
if not isinstance(v, ast.Str):
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.Constant):
if not isinstance(sql, ast.Str):
return bandit.Issue(
severity=bandit.MEDIUM,
confidence=bandit.MEDIUM,
Expand Down
17 changes: 7 additions & 10 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.Constant):
if isinstance(to, ast.Str):
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.Constant):
if isinstance(some_to, ast.Str):
num_secure += 1
elif isinstance(some_to, ast.Name):
if evaluate_var(
Expand All @@ -131,10 +131,7 @@ 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.Constant)
and call.func.attr == "format"
):
if isinstance(call.func.value, ast.Str) and call.func.attr == "format":
evaluate = True
if call.keywords:
evaluate = False # TODO(??) get support for this
Expand All @@ -143,7 +140,7 @@ def evaluate_call(call, parent, ignore_nodes=None):
args = list(call.args)
num_secure = 0
for arg in args:
if isinstance(arg, ast.Constant):
if isinstance(arg, ast.Str):
num_secure += 1
elif isinstance(arg, ast.Name):
if evaluate_var(arg, parent, call.lineno, ignore_nodes):
Expand All @@ -170,7 +167,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.Constant)
is_left_str = isinstance(var.left, ast.Str)
if is_mod and is_left_str:
new_call = ast.Call()
new_call.args = []
Expand Down Expand Up @@ -215,7 +212,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.Constant)
is_left_str = isinstance(xss_var.left, ast.Str)
if is_mod and is_left_str:
parent = node._bandit_parent
while not isinstance(parent, (ast.Module, ast.FunctionDef)):
Expand Down Expand Up @@ -275,5 +272,5 @@ def django_mark_safe(context):
]
if context.call_function_name in affected_functions:
xss = context.node.args[0]
if not isinstance(xss, ast.Constant):
if not isinstance(xss, ast.Str):
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.value)
return _report(node.s)
elif isinstance(targ, ast.Attribute) and RE_CANDIDATES.search(
targ.attr
):
return _report(node.value)
return _report(node.s)

elif isinstance(
node._bandit_parent, ast.Subscript
) and RE_CANDIDATES.search(node.value):
) and RE_CANDIDATES.search(node.s):
# 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.Constant
assign.value, ast.Str
):
return _report(assign.value.value)
return _report(assign.value.s)

elif isinstance(node._bandit_parent, ast.Index) and RE_CANDIDATES.search(
node.value
node.s
):
# 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.Constant
assign.value, ast.Str
):
return _report(assign.value.value)
return _report(assign.value.s)

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.Constant):
return _report(comp.comparators[0].value)
if isinstance(comp.comparators[0], ast.Str):
return _report(comp.comparators[0].s)
elif isinstance(comp.left, ast.Attribute):
if RE_CANDIDATES.search(comp.left.attr):
if isinstance(comp.comparators[0], ast.Constant):
return _report(comp.comparators[0].value)
if isinstance(comp.comparators[0], ast.Str):
return _report(comp.comparators[0].s)


@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.Constant) and RE_CANDIDATES.search(kw.arg):
return _report(kw.value.value)
if isinstance(kw.value, ast.Str) and RE_CANDIDATES.search(kw.arg):
return _report(kw.value.s)


@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.Constant) and RE_CANDIDATES.search(key.arg):
return _report(val.value)
if isinstance(val, ast.Str) and RE_CANDIDATES.search(key.arg):
return _report(val.s)
Loading

0 comments on commit fac7b52

Please sign in to comment.