From bfb2ad8c8125263b899d368b5ea9152c42032381 Mon Sep 17 00:00:00 2001 From: 9iang22 <9iang22@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:39:34 +0800 Subject: [PATCH] Fix B103: Support stat constants and BitOr operations in os.chmod Adds AST mode resolution for os.chmod to prevent false negatives, along with updated test cases. --- .../plugins/general_bad_file_permissions.py | 23 +++++++++++++++++++ examples/os-chmod.py | 2 ++ tests/functional/test_functional.py | 4 ++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/bandit/plugins/general_bad_file_permissions.py b/bandit/plugins/general_bad_file_permissions.py index 7d3fce4df..2316a7615 100644 --- a/bandit/plugins/general_bad_file_permissions.py +++ b/bandit/plugins/general_bad_file_permissions.py @@ -53,6 +53,7 @@ Added checks for S_IWGRP and S_IXOTH """ # noqa: E501 +import ast import stat import bandit @@ -69,6 +70,24 @@ def _stat_is_dangerous(mode): ) +def _resolve_mode_from_ast(node): + if isinstance(node, ast.Constant) and isinstance(node.value, int): + return node.value + elif isinstance(node, getattr(ast, "Num", type(None))): + return getattr(node, "n", None) + elif isinstance(node, ast.Attribute): + if hasattr(stat, node.attr): + val = getattr(stat, node.attr) + if isinstance(val, int): + return val + elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr): + left = _resolve_mode_from_ast(node.left) + right = _resolve_mode_from_ast(node.right) + if left is not None and right is not None: + return left | right + return None + + @test.checks("Call") @test.test_id("B103") def set_bad_file_permissions(context): @@ -76,6 +95,10 @@ def set_bad_file_permissions(context): if context.call_args_count == 2: mode = context.get_call_arg_at_position(1) + if mode is None: + arg_node = context.node.args[1] + mode = _resolve_mode_from_ast(arg_node) + if ( mode is not None and isinstance(mode, int) diff --git a/examples/os-chmod.py b/examples/os-chmod.py index f7fff8517..c665ab2c8 100644 --- a/examples/os-chmod.py +++ b/examples/os-chmod.py @@ -17,3 +17,5 @@ os.chmod(keyfile, 0o777) os.chmod('~/hidden_exec', stat.S_IXGRP) os.chmod('~/hidden_exec', stat.S_IXOTH) +os.chmod('config.ini', stat.S_IWGRP | stat.S_IWOTH) +os.chmod('config.ini', stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXOTH) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 08b1c5c5b..53181c1c7 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -269,8 +269,8 @@ def test_subdirectory_okay(self): def test_os_chmod(self): """Test setting file permissions.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 4, "HIGH": 8}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 1, "HIGH": 11}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 5, "HIGH": 9}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 1, "HIGH": 13}, } self.check_example("os-chmod.py", expect)