From df7c27c0f50d13082aefbdef5fc410fcbf958929 Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 11:28:31 +0100 Subject: [PATCH 01/12] Add check for hardcoded passwords in annotated assignments. --- bandit/plugins/general_hardcoded_password.py | 24 ++++++++++++++++++++ examples/hardcoded-passwords.py | 18 +++++++++++++++ tests/functional/test_functional.py | 4 ++-- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index 12f629074..e9115ccf2 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -79,6 +79,7 @@ def hardcoded_password_string(context): """ node = context.node + if isinstance(node._bandit_parent, ast.Assign): # looks for "candidate='some_string'" for targ in node._bandit_parent.targets: @@ -89,6 +90,29 @@ def hardcoded_password_string(context): ): return _report(node.value) + elif isinstance(node._bandit_parent, ast.AnnAssign): + target_node = node._bandit_parent.target + if isinstance(target_node, ast.Name): + # looks for "candidate: str = 'some_string'" + if RE_CANDIDATES.search(target_node.id): + return _report(node.value) + elif isinstance(target_node, ast.Attribute): + # looks for "o.candidate: str = 'some_str'" + if RE_CANDIDATES.search(target_node.attr): + return _report(node.value) + elif isinstance(target_node, ast.Subscript): + if ( + isinstance(target_node.slice, ast.Constant) + and isinstance(target_node.slice.value, str) + ): + # looks for "d["candidate"]: str = 'some_str'" + if RE_CANDIDATES.search(target_node.slice.value): + return _report(node.value) + elif isinstance(target_node.slice, ast.Name): + # looks for "d[candidate]: str = 'some_str'" + if RE_CANDIDATES.search(target_node.slice.id): + return _report(node.value) + elif ( isinstance(node._bandit_parent, ast.Dict) and node in node._bandit_parent.keys diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index b1665ae7b..164bdd912 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -103,3 +103,21 @@ def __init__(self, auth_scheme, auth_token=None, auth_username=None, auth_passwo # ... but not: info = {"password": password} + +# Possible hardcoded password: 'password' +# Severity: Low Confidence: Medium +# https://github.com/PyCQA/bandit/issues/642 +class MyConfig: + my_password: str = 'password' + +# Possible hardcoded password: 'admin123' +# Severity: Low Confidence: Medium +config.password: str = "admin123" + +# Possible hardcoded password: 'admin123' +# Severity: Low Confidence: Medium +d["password"]: str = "admin123" + +# Possible hardcoded password: 'admin123' +# Severity: Low Confidence: Medium +d[password]: str = 'admin123' diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 08b1c5c5b..0d2010f2c 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -168,8 +168,8 @@ def test_exec(self): def test_hardcoded_passwords(self): """Test for hard-coded passwords.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 16, "MEDIUM": 0, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 16, "HIGH": 0}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 20, "MEDIUM": 0, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 20, "HIGH": 0}, } self.check_example("hardcoded-passwords.py", expect) From b9fb2e760fb7e1ac36f06c03f0668c889a22e749 Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 18:02:08 +0100 Subject: [PATCH 02/12] Refactored password checks for assignments and dictionaries. --- bandit/plugins/general_hardcoded_password.py | 114 +++++++++++-------- 1 file changed, 64 insertions(+), 50 deletions(-) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index e9115ccf2..f795fd34f 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -81,75 +81,89 @@ def hardcoded_password_string(context): node = context.node if isinstance(node._bandit_parent, ast.Assign): - # looks for "candidate='some_string'" + # multiple targets? "a = b.d = c[e] = ..." for targ in node._bandit_parent.targets: - if isinstance(targ, ast.Name) and RE_CANDIDATES.search(targ.id): + if ( + isinstance(targ, ast.Name) + and RE_CANDIDATES.search(targ.id) + ): + # looks for "candidate='this_string'" return _report(node.value) - elif isinstance(targ, ast.Attribute) and RE_CANDIDATES.search( - targ.attr + elif ( + isinstance(targ, ast.Attribute) + and RE_CANDIDATES.search(targ.attr) ): + # looks for "o.candidate='this_string'" return _report(node.value) + elif isinstance(targ, ast.Subscript): + if ( + isinstance(targ.slice, ast.Constant) + and isinstance(targ.slice.value, str) + and RE_CANDIDATES.search(targ.slice.value) + ): + # looks for "d['candidate'] = 'this_string'" + return _report(node.value) + elif ( + isinstance(targ.slice, ast.Name) + and RE_CANDIDATES.search(targ.slice.id) + ): + # looks for "d[candidate] = 'this_string'" + return _report(node.value) elif isinstance(node._bandit_parent, ast.AnnAssign): + # skip if current node is an annotation string + # e.g. password: 'this_string' = 'other_string' + if node._bandit_parent.annotation == node: + return None + target_node = node._bandit_parent.target - if isinstance(target_node, ast.Name): - # looks for "candidate: str = 'some_string'" - if RE_CANDIDATES.search(target_node.id): - return _report(node.value) - elif isinstance(target_node, ast.Attribute): - # looks for "o.candidate: str = 'some_str'" - if RE_CANDIDATES.search(target_node.attr): + + if ( + isinstance(target_node, ast.Name) + and RE_CANDIDATES.search(target_node.id) + ): return _report(node.value) + elif ( + isinstance(target_node, ast.Attribute) + and RE_CANDIDATES.search(target_node.attr) + ): + # looks for "o.candidate: str = 'this_str'" + return _report(node.value) elif isinstance(target_node, ast.Subscript): if ( isinstance(target_node.slice, ast.Constant) and isinstance(target_node.slice.value, str) + and RE_CANDIDATES.search(target_node.slice.value) + ): + # looks for "d["candidate"]: str = 'this_str'" + return _report(node.value) + elif ( + isinstance(target_node.slice, ast.Name) + and RE_CANDIDATES.search(target_node.slice.id) ): - # looks for "d["candidate"]: str = 'some_str'" - if RE_CANDIDATES.search(target_node.slice.value): - return _report(node.value) - elif isinstance(target_node.slice, ast.Name): # looks for "d[candidate]: str = 'some_str'" - if RE_CANDIDATES.search(target_node.slice.id): - return _report(node.value) + return _report(node.value) + + elif isinstance(node._bandit_parent, ast.Dict): + # skip if current node is a dictionary key + if node in node._bandit_parent.keys: + return None - elif ( - isinstance(node._bandit_parent, ast.Dict) - and node in node._bandit_parent.keys - and RE_CANDIDATES.search(node.value) - ): - # looks for "{'candidate': 'some_string'}" dict_node = node._bandit_parent - pos = dict_node.keys.index(node) - value_node = dict_node.values[pos] - if isinstance(value_node, ast.Constant): - return _report(value_node.value) - - elif isinstance( - node._bandit_parent, ast.Subscript - ) and RE_CANDIDATES.search(node.value): - # Py39+: looks for "dict[candidate]='some_string'" - # subscript -> index -> string - assign = node._bandit_parent._bandit_parent + pos = dict_node.values.index(node) + key_node = dict_node.keys[pos] if ( - isinstance(assign, ast.Assign) - and isinstance(assign.value, ast.Constant) - and isinstance(assign.value.value, str) + isinstance(key_node, ast.Constant) + and RE_CANDIDATES.search(key_node.value) ): - return _report(assign.value.value) - - elif isinstance(node._bandit_parent, ast.Index) and RE_CANDIDATES.search( - 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.Constant) - and isinstance(assign.value.value, str) + # looks for "{'candidate': 'some_string'}" + return _report(node.value) + elif ( + isinstance(key_node, ast.Name) + and RE_CANDIDATES.search(key_node.id) ): - return _report(assign.value.value) + # looks for "{candidate: 'some_string'}" + return _report(node.value) elif isinstance(node._bandit_parent, ast.Compare): # looks for "candidate == 'some_string'" From 0454abe7295604e8dc71e1b99fdc7404c6c957bc Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 19:50:24 +0100 Subject: [PATCH 03/12] Cleaned up checks a bit. --- bandit/plugins/general_hardcoded_password.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index f795fd34f..88b454aa5 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -122,7 +122,8 @@ def hardcoded_password_string(context): isinstance(target_node, ast.Name) and RE_CANDIDATES.search(target_node.id) ): - return _report(node.value) + # looks for "candidate: str = 'this_str'" + return _report(node.value) elif ( isinstance(target_node, ast.Attribute) and RE_CANDIDATES.search(target_node.attr) @@ -135,7 +136,7 @@ def hardcoded_password_string(context): and isinstance(target_node.slice.value, str) and RE_CANDIDATES.search(target_node.slice.value) ): - # looks for "d["candidate"]: str = 'this_str'" + # looks for "d['candidate']: str = 'this_str'" return _report(node.value) elif ( isinstance(target_node.slice, ast.Name) @@ -152,6 +153,7 @@ def hardcoded_password_string(context): dict_node = node._bandit_parent pos = dict_node.values.index(node) key_node = dict_node.keys[pos] + if ( isinstance(key_node, ast.Constant) and RE_CANDIDATES.search(key_node.value) From e0613008d047b9b5a9d20e89fb04a0b39d461a5d Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 20:08:15 +0100 Subject: [PATCH 04/12] Reordered hardcoded password tests. --- examples/hardcoded-passwords.py | 169 +++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 58 deletions(-) diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index 164bdd912..91555d0db 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -1,12 +1,96 @@ +#----------------------------------------------------------------------------- +# ASSIGNMENTS +#----------------------------------------------------------------------------- + # Possible hardcoded password: 'class_password' # Severity: Low Confidence: Medium class SomeClass: password = "class_password" -# Possible hardcoded password: 'Admin' + +# Possible hardcoded password: 'blerg' # Severity: Low Confidence: Medium -def someFunction(user, password="Admin"): - print("Hi " + user) +password = "blerg" + + +# Possible hardcoded password: 'blerg' +# Severity: Low Confidence: Medium +password["password"] = "blerg" + + +# Possible hardcoded password: 'secret' +# Severity: Low Confidence: Medium +EMAIL_PASSWORD = "secret" + + +# Possible hardcoded password: 'emails_secret' +# Severity: Low Confidence: Medium +email_pwd = 'emails_secret' + + +# Possible hardcoded password: 'd6s$f9g!j8mg7hw?n&2' +# Severity: Low Confidence: Medium +my_secret_password_for_email = 'd6s$f9g!j8mg7hw?n&2' + + +# Possible hardcoded password: '1234' +# Severity: Low Confidence: Medium +passphrase='1234' + + +#----------------------------------------------------------------------------- +# ANNOTATED ASSIGNMENTS +#----------------------------------------------------------------------------- + +# Possible hardcoded password: 'password' +# Severity: Low Confidence: Medium +# https://github.com/PyCQA/bandit/issues/642 +class MyConfig: + my_password: str = 'password' + + +# Possible hardcoded password: 'admin123' +# Severity: Low Confidence: Medium +config.password: str = "admin123" + + +# Possible hardcoded password: 'admin123' +# Severity: Low Confidence: Medium +d["password"]: str = "admin123" + + +# Possible hardcoded password: 'admin123' +# Severity: Low Confidence: Medium +d[password]: str = 'admin123' + + +#----------------------------------------------------------------------------- +# DICTIONARIES +#----------------------------------------------------------------------------- + +# Possible hardcoded password: 'pass' +# Severity: Low Confidence: Medium +# https://github.com/PyCQA/bandit/issues/313 +log({"server": server, "password": 'pass', "user": user}) + + +# ... but not: +log({"server": server, "password": password, "user": user}) + + +# Possible hardcoded password: '12345' +# Severity: Low Confidence: Medium +# https://github.com/PyCQA/bandit/issues/1267 +info = {"password": "12345"} + + +# ... but not: +info = {"password": password} + + +#----------------------------------------------------------------------------- +# COMMPARISONS +#----------------------------------------------------------------------------- def someFunction2(password): # Possible hardcoded password: 'root' @@ -14,18 +98,21 @@ def someFunction2(password): if password == "root": print("OK, logged in") + def noMatch(password): # Possible hardcoded password: '' # Severity: Low Confidence: Medium if password == '': print("No password!") + def NoMatch2(password): # Possible hardcoded password: 'ajklawejrkl42348swfgkg' # Severity: Low Confidence: Medium if password == "ajklawejrkl42348swfgkg": print("Nice password!") + def noMatchObject(): obj = SomeClass() # Possible hardcoded password: 'this cool password' @@ -33,42 +120,41 @@ def noMatchObject(): if obj.password == "this cool password": print(obj.password) -# Possible hardcoded password: 'blerg' -# Severity: Low Confidence: Medium -def doLogin(password="blerg"): - pass -def NoMatch3(a, b): - pass +#----------------------------------------------------------------------------- +# FUNCTION CALLS +#----------------------------------------------------------------------------- # Possible hardcoded password: 'blerg' # Severity: Low Confidence: Medium doLogin(password="blerg") -# Possible hardcoded password: 'blerg' + +#----------------------------------------------------------------------------- +# FUNCTION DEFINITIONS +#----------------------------------------------------------------------------- + +# Possible hardcoded password: 'Admin' # Severity: Low Confidence: Medium -password = "blerg" +def someFunction(user, password="Admin"): + print("Hi " + user) + # Possible hardcoded password: 'blerg' # Severity: Low Confidence: Medium -password["password"] = "blerg" +def doLogin(password="blerg"): + pass -# Possible hardcoded password: 'secret' -# Severity: Low Confidence: Medium -EMAIL_PASSWORD = "secret" -# Possible hardcoded password: 'emails_secret' -# Severity: Low Confidence: Medium -email_pwd = 'emails_secret' +def NoMatch3(a, b): + pass -# Possible hardcoded password: 'd6s$f9g!j8mg7hw?n&2' -# Severity: Low Confidence: Medium -my_secret_password_for_email = 'd6s$f9g!j8mg7hw?n&2' -# Possible hardcoded password: '1234' -# Severity: Low Confidence: Medium -passphrase='1234' +#----------------------------------------------------------------------------- +# OTHER +#----------------------------------------------------------------------------- +# TODO: what's the purpose of this example? # Possible hardcoded password: None # Severity: High Confidence: High def __init__(self, auth_scheme, auth_token=None, auth_username=None, auth_password=None, auth_link=None, **kwargs): @@ -79,6 +165,7 @@ def __init__(self, auth_scheme, auth_token=None, auth_username=None, auth_passwo self.auth_link = auth_link self.kwargs = kwargs +# TODO: what's the purpose of this example? # Possible hardcoded password: None # Severity: High Confidence: High from oslo_config import cfg @@ -87,37 +174,3 @@ def __init__(self, auth_scheme, auth_token=None, auth_username=None, auth_passwo default='', secret=True, ) - -# Possible hardcoded password: 'pass' -# Severity: Low Confidence: Medium -# https://github.com/PyCQA/bandit/issues/313 -log({"server": server, "password": 'pass', "user": user}) - -# ... but not: -log({"server": server, "password": password, "user": user}) - -# Possible hardcoded password: '12345' -# Severity: Low Confidence: Medium -# https://github.com/PyCQA/bandit/issues/1267 -info = {"password": "12345"} - -# ... but not: -info = {"password": password} - -# Possible hardcoded password: 'password' -# Severity: Low Confidence: Medium -# https://github.com/PyCQA/bandit/issues/642 -class MyConfig: - my_password: str = 'password' - -# Possible hardcoded password: 'admin123' -# Severity: Low Confidence: Medium -config.password: str = "admin123" - -# Possible hardcoded password: 'admin123' -# Severity: Low Confidence: Medium -d["password"]: str = "admin123" - -# Possible hardcoded password: 'admin123' -# Severity: Low Confidence: Medium -d[password]: str = 'admin123' From 132a4fed3fd04bbcaa591d6b1f3efdf9ab8c2331 Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 20:22:27 +0100 Subject: [PATCH 05/12] Clean up hardcoded password tests for assignments. --- examples/hardcoded-passwords.py | 69 ++++++++++++++++++++++------- tests/functional/test_functional.py | 4 +- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index 91555d0db..e7cb972ae 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -2,40 +2,79 @@ # ASSIGNMENTS #----------------------------------------------------------------------------- -# Possible hardcoded password: 'class_password' +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -class SomeClass: - password = "class_password" +password = "this_string" +# not! +a = "password" -# Possible hardcoded password: 'blerg' + +# not! +password.a = "this_string" + +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -password = "blerg" +a.password = "this_string" +# not! +a.b = "password" -# Possible hardcoded password: 'blerg' +# not! +password['b'] = "this_string" + +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -password["password"] = "blerg" +a['password'] = "this_string" + +# not! +a['b'] = "password" -# Possible hardcoded password: 'secret' +# not! +password[b] = "this_string" + +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -EMAIL_PASSWORD = "secret" +a[password] = "this_string" + +# not! +a[b] = "password" -# Possible hardcoded password: 'emails_secret' +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -email_pwd = 'emails_secret' +password = b.c = d['e'] = f[g] = "this_string" +# not! +a = password.c = d['e'] = f[g] = "this_string" -# Possible hardcoded password: 'd6s$f9g!j8mg7hw?n&2' +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -my_secret_password_for_email = 'd6s$f9g!j8mg7hw?n&2' +a = b.password = d['e'] = f[g] = "this_string" +# not! +a = b.c = password['e'] = f[g] = "this_string" -# Possible hardcoded password: '1234' +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -passphrase='1234' +a = b.c = d['password'] = f[g] = "this_string" + +# not! +a = b.c = d['e'] = password[g] = "this_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +a = b.c = d['e'] = f[password] = "this_string" + +# not! +a = b.c = d['e'] = f[g] = "password" + + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +class SomeClass: + password = "this_string" #----------------------------------------------------------------------------- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 0d2010f2c..7b3bce814 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -168,8 +168,8 @@ def test_exec(self): def test_hardcoded_passwords(self): """Test for hard-coded passwords.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 20, "MEDIUM": 0, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 20, "HIGH": 0}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 22, "MEDIUM": 0, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 22, "HIGH": 0}, } self.check_example("hardcoded-passwords.py", expect) From 2f2d3bb4414d9ebf3943b270e67f2b38d285efdc Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 21:34:53 +0100 Subject: [PATCH 06/12] Clean up hardcoded password tests for annotated assignments. --- examples/hardcoded-passwords.py | 107 +++++++++++++++++++++++++--- tests/functional/test_functional.py | 4 +- 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index e7cb972ae..3405fcd1a 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -81,26 +81,113 @@ class SomeClass: # ANNOTATED ASSIGNMENTS #----------------------------------------------------------------------------- -# Possible hardcoded password: 'password' +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -# https://github.com/PyCQA/bandit/issues/642 -class MyConfig: - my_password: str = 'password' +password: str = "this_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +password: "str" = "this_string" + +# not! +a: password = "this_string" + +# not! +a: "password" = "this_string" + +# not! +a: str = "password" + +# not! +a: "str" = "password" + + +# not! +password.b: str = "this_string" +# not! +password.b: "str" = "this_string" -# Possible hardcoded password: 'admin123' +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -config.password: str = "admin123" +a.password: str = "this_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +a.password: "str" = "this_string" + +# not! +a.b: password = "this_string" + +# not! +a.b: "password" = "this_string" + +# not! +a.b: str = "password" + +# not! +a.b: "str" = "password" -# Possible hardcoded password: 'admin123' +# not! +password["b"]: str = "this_string" + +# not! +password["b"]: "str" = "this_string" + +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -d["password"]: str = "admin123" +a["password"]: str = "this_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +a["password"]: "str" = "this_string" + +# not! +a["b"]: password = "this_string" + +# not! +a["b"]: "password" = "this_string" + +# not! +a["b"]: str = "password" + +# not! +a["b"]: "str" = "password" -# Possible hardcoded password: 'admin123' +# not! +password[b]: str = "this_string" + +# not! +password[b]: "str" = "this_string" + +# Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -d[password]: str = 'admin123' +a[password]: str = "this_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +a[password]: "str" = "this_string" + +# not! +a[b]: password = "this_string" + +# not! +a[b]: "password" = "this_string" + +# not! +a[b]: str = "password" + +# not! +a[b]: "str" = "password" + + +# Possible hardcoded password: 'password' +# Severity: Low Confidence: Medium +# https://github.com/PyCQA/bandit/issues/642 +class MyConfig: + my_password: str = 'password' #----------------------------------------------------------------------------- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 7b3bce814..b60540b4b 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -168,8 +168,8 @@ def test_exec(self): def test_hardcoded_passwords(self): """Test for hard-coded passwords.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 22, "MEDIUM": 0, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 22, "HIGH": 0}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 27, "MEDIUM": 0, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 27, "HIGH": 0}, } self.check_example("hardcoded-passwords.py", expect) From 282cca70662c9dd8519b8aba00acb4b7ec955f48 Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 21:42:11 +0100 Subject: [PATCH 07/12] Clean up hardcoded password tests for dictionaries. --- examples/hardcoded-passwords.py | 25 ++++++++++++++++++------- tests/functional/test_functional.py | 4 ++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index 3405fcd1a..d45ab3720 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -194,24 +194,35 @@ class MyConfig: # DICTIONARIES #----------------------------------------------------------------------------- +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +{"password": "this_string"} + +# not! +{"a": "password"} + + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +{password: "this_string"} + +# not! +{a: "password"} + + # Possible hardcoded password: 'pass' # Severity: Low Confidence: Medium # https://github.com/PyCQA/bandit/issues/313 log({"server": server, "password": 'pass', "user": user}) - -# ... but not: +# not! log({"server": server, "password": password, "user": user}) # Possible hardcoded password: '12345' # Severity: Low Confidence: Medium # https://github.com/PyCQA/bandit/issues/1267 -info = {"password": "12345"} - - -# ... but not: -info = {"password": password} +{"password": "12345"} #----------------------------------------------------------------------------- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index b60540b4b..d69248a2b 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -168,8 +168,8 @@ def test_exec(self): def test_hardcoded_passwords(self): """Test for hard-coded passwords.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 27, "MEDIUM": 0, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 27, "HIGH": 0}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 29, "MEDIUM": 0, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 29, "HIGH": 0}, } self.check_example("hardcoded-passwords.py", expect) From 09bae71734e8cc77b0feb38ab9049d133c0af6e6 Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 22:08:20 +0100 Subject: [PATCH 08/12] Clean up hardcoded password tests for comparisons. --- examples/hardcoded-passwords.py | 72 +++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index d45ab3720..4c36be018 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -226,36 +226,64 @@ class MyConfig: #----------------------------------------------------------------------------- -# COMMPARISONS +# COMPARISONS #----------------------------------------------------------------------------- -def someFunction2(password): - # Possible hardcoded password: 'root' - # Severity: Low Confidence: Medium - if password == "root": - print("OK, logged in") +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +password == "this_string" + +# not! +a == "password" + + +# not! +password.b == "this_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +a.password == "this_string" + +# not! +a.b == "password" + + +# not! +password["b"] == "this_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +a["password"] == "this_string" # TODO: false negative! +# not! +a["b"] == "password" -def noMatch(password): - # Possible hardcoded password: '' - # Severity: Low Confidence: Medium - if password == '': - print("No password!") +# not! +password[b] == "this_string" -def NoMatch2(password): - # Possible hardcoded password: 'ajklawejrkl42348swfgkg' - # Severity: Low Confidence: Medium - if password == "ajklawejrkl42348swfgkg": - print("Nice password!") +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +a[password] == "this_string" # TODO: false negative! + +# not! +a[b] == "password" -def noMatchObject(): - obj = SomeClass() - # Possible hardcoded password: 'this cool password' - # Severity: Low Confidence: Medium - if obj.password == "this cool password": - print(obj.password) +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +password != "this_string" + +# not! +a != "password" + + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +"this_string" == password # TODO: false negative! + +# not! +"password" == b #----------------------------------------------------------------------------- From 241901fb569590c1f679542796588f871442f665 Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 23:27:03 +0100 Subject: [PATCH 09/12] Add hardcoded password tests for comparisons with more than 2 operands. --- examples/hardcoded-passwords.py | 88 +++++++++++++++++++++++++++++ tests/functional/test_functional.py | 4 +- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index 4c36be018..cd7beb998 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -286,6 +286,94 @@ class MyConfig: "password" == b +# not! +password == b == c + +# not! +a == password == c + +# not! +a == b == password + +# Possible hardcoded password: 'third_string' +# Severity: Low Confidence: Medium +password == b == "third_string" # TODO: false negative! + +# Possible hardcoded password: 'third_string' +# Severity: Low Confidence: Medium +a == password == "third_string" # TODO: false negative! + +# not! +a == b == "password" + +# Possible hardcoded password: 'other_string' +# Severity: Low Confidence: Medium +password == "other_string" == c + +# not! +a == "password" == c + +# Possible hardcoded password: 'other_string' +# Severity: Low Confidence: Medium +a == "other_string" == password # TODO: false negative! + +# Possible hardcoded password: 'other_string' +# Severity: Low Confidence: Medium +# Possible hardcoded password: 'third_string' +# Severity: Low Confidence: Medium +password == "other_string" == "third_string" # TODO: wrong password! + +# not! +a == "password" == "third_string" + +# not! +a == "other_string" == "password" + +# not! +"password" == b == c + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +"this_string" == password == c # TODO: false negative! + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +"this_string" == b == password # TODO: false negative! + +# not! +"password" == b == "third_string" + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +# Possible hardcoded password: 'third_string' +# Severity: Low Confidence: Medium +"this_string" == password == "third_string" # TODO: false negative! + +# not! +"this_string" == b == "password" + +# not! +"password" == "other_string" == c + +# not! +"this_string" == "password" == c + +# Possible hardcoded password: 'this_string' +# Severity: Low Confidence: Medium +# Possible hardcoded password: 'other_string' +# Severity: Low Confidence: Medium +"this_string" == "other_string" == password # TODO: false negative! + +# not! +"password" == "other_string" == "third_string" + +# not! +"this_string" == "password" == "third_string" + +# not! +"this_string" == "other_string" == "password" + + #----------------------------------------------------------------------------- # FUNCTION CALLS #----------------------------------------------------------------------------- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index d69248a2b..b58d7374c 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -168,8 +168,8 @@ def test_exec(self): def test_hardcoded_passwords(self): """Test for hard-coded passwords.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 29, "MEDIUM": 0, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 29, "HIGH": 0}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 43, "MEDIUM": 0, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 43, "HIGH": 0}, } self.check_example("hardcoded-passwords.py", expect) From 742bcf2d97006129a159308904e6842de49cd2e8 Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Mon, 1 Dec 2025 23:40:56 +0100 Subject: [PATCH 10/12] Fixed comparison checks for hardcoded passwords. --- bandit/plugins/general_hardcoded_password.py | 42 ++++++++++++++------ examples/hardcoded-passwords.py | 22 +++++----- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index 88b454aa5..3c2d89a2d 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -169,19 +169,35 @@ def hardcoded_password_string(context): 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 - ) and isinstance(comp.comparators[0].value, str): - 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.Constant - ) and isinstance(comp.comparators[0].value, str): - return _report(comp.comparators[0].value) + comp_node = node._bandit_parent + operand_nodes = [comp_node.left] + comp_node.comparators + for operand_node in operand_nodes: + if ( + isinstance(operand_node, ast.Name) + and RE_CANDIDATES.search(operand_node.id) + ): + # looks for "password == 'this_string'" + return _report(node.value) + elif ( + isinstance(operand_node, ast.Attribute) + and RE_CANDIDATES.search(operand_node.attr) + ): + # looks for "o.password == 'this_string'" + return _report(node.value) + elif isinstance(operand_node, ast.Subscript): + if ( + isinstance(operand_node.slice, ast.Constant) + and isinstance(operand_node.slice.value, str) + and RE_CANDIDATES.search(operand_node.slice.value) + ): + # looks for "d["candidate"] == 'this_str'" + return _report(node.value) + elif ( + isinstance(operand_node.slice, ast.Name) + and RE_CANDIDATES.search(operand_node.slice.id) + ): + # looks for "d[candidate] == 'some_str'" + return _report(node.value) @test.checks("Call") diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index cd7beb998..b94be2ba6 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -253,7 +253,7 @@ class MyConfig: # Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -a["password"] == "this_string" # TODO: false negative! +a["password"] == "this_string" # not! a["b"] == "password" @@ -264,7 +264,7 @@ class MyConfig: # Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -a[password] == "this_string" # TODO: false negative! +a[password] == "this_string" # not! a[b] == "password" @@ -280,7 +280,7 @@ class MyConfig: # Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -"this_string" == password # TODO: false negative! +"this_string" == password # not! "password" == b @@ -297,11 +297,11 @@ class MyConfig: # Possible hardcoded password: 'third_string' # Severity: Low Confidence: Medium -password == b == "third_string" # TODO: false negative! +password == b == "third_string" # Possible hardcoded password: 'third_string' # Severity: Low Confidence: Medium -a == password == "third_string" # TODO: false negative! +a == password == "third_string" # not! a == b == "password" @@ -315,13 +315,13 @@ class MyConfig: # Possible hardcoded password: 'other_string' # Severity: Low Confidence: Medium -a == "other_string" == password # TODO: false negative! +a == "other_string" == password # Possible hardcoded password: 'other_string' # Severity: Low Confidence: Medium # Possible hardcoded password: 'third_string' # Severity: Low Confidence: Medium -password == "other_string" == "third_string" # TODO: wrong password! +password == "other_string" == "third_string" # not! a == "password" == "third_string" @@ -334,11 +334,11 @@ class MyConfig: # Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -"this_string" == password == c # TODO: false negative! +"this_string" == password == c # Possible hardcoded password: 'this_string' # Severity: Low Confidence: Medium -"this_string" == b == password # TODO: false negative! +"this_string" == b == password # not! "password" == b == "third_string" @@ -347,7 +347,7 @@ class MyConfig: # Severity: Low Confidence: Medium # Possible hardcoded password: 'third_string' # Severity: Low Confidence: Medium -"this_string" == password == "third_string" # TODO: false negative! +"this_string" == password == "third_string" # not! "this_string" == b == "password" @@ -362,7 +362,7 @@ class MyConfig: # Severity: Low Confidence: Medium # Possible hardcoded password: 'other_string' # Severity: Low Confidence: Medium -"this_string" == "other_string" == password # TODO: false negative! +"this_string" == "other_string" == password # not! "password" == "other_string" == "third_string" From f973020203acacaa00678d642225ffb1b41c2cfd Mon Sep 17 00:00:00 2001 From: Alan Verresen Date: Tue, 2 Dec 2025 00:08:19 +0100 Subject: [PATCH 11/12] Add examples from issues of hardcoded password checks. --- examples/hardcoded-passwords.py | 100 ++++++++++++++++++++++------ tests/functional/test_functional.py | 4 +- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py index b94be2ba6..76eb1d4f5 100644 --- a/examples/hardcoded-passwords.py +++ b/examples/hardcoded-passwords.py @@ -183,13 +183,6 @@ class SomeClass: a[b]: "str" = "password" -# Possible hardcoded password: 'password' -# Severity: Low Confidence: Medium -# https://github.com/PyCQA/bandit/issues/642 -class MyConfig: - my_password: str = 'password' - - #----------------------------------------------------------------------------- # DICTIONARIES #----------------------------------------------------------------------------- @@ -210,21 +203,6 @@ class MyConfig: {a: "password"} -# Possible hardcoded password: 'pass' -# Severity: Low Confidence: Medium -# https://github.com/PyCQA/bandit/issues/313 -log({"server": server, "password": 'pass', "user": user}) - -# not! -log({"server": server, "password": password, "user": user}) - - -# Possible hardcoded password: '12345' -# Severity: Low Confidence: Medium -# https://github.com/PyCQA/bandit/issues/1267 -{"password": "12345"} - - #----------------------------------------------------------------------------- # COMPARISONS #----------------------------------------------------------------------------- @@ -403,6 +381,84 @@ def NoMatch3(a, b): pass +#----------------------------------------------------------------------------- +# REPORTED ISSUES +#----------------------------------------------------------------------------- + +# https://github.com/PyCQA/bandit/issues/313 + +# Possible hardcoded password: 'pass' +# Severity: Low Confidence: Medium +log({"server": server, "password": 'pass', "user": user}) + +# not! +log({"server": server, "password": password, "user": user}) + +# Possible hardcoded password: 'pass' +# Severity: Low Confidence: Medium +log(password='pass') + + +# https://github.com/PyCQA/bandit/issues/386 + +# Possible hardcoded password: 'secret' +# Severity: Low Confidence: Medium +EMAIL_PASSWORD = "secret" + +# Possible hardcoded password: 'emails_secret' +# Severity: Low Confidence: Medium +email_pwd = 'emails_secret' + + +# https://github.com/PyCQA/bandit/issues/551 + +# Possible hardcoded password: 'aaaaaaa' +# Severity: Low Confidence: Medium +app.config['SECRET_KEY'] = 'aaaaaaa' + + +# https://github.com/PyCQA/bandit/issues/605 + +# Possible hardcoded password: 'root' +# Severity: Low Confidence: Medium +def fooBar(password): + if password == "root": + print("OK, logged in") + + +# https://github.com/PyCQA/bandit/issues/639 + +# Possible hardcoded password: '1238aoufhz8xyf3jr;' +# Severity: Low Confidence: Medium +password = "1238aoufhz8xyf3jr;" + + +# https://github.com/PyCQA/bandit/issues/642 + +# Possible hardcoded password: 'password' +# Severity: Low Confidence: Medium +class MyConfig: + my_password: str = 'password' + + +# https://github.com/PyCQA/bandit/issues/759 + +# Possible hardcoded password: '12123123' +# Severity: Low Confidence: Medium +password = "12123123" + +# Possible hardcoded password: '12123123' +# Severity: Low Confidence: Medium +self.password = "12123123" + + +# https://github.com/PyCQA/bandit/issues/1267 + +# Possible hardcoded password: '12345' +# Severity: Low Confidence: Medium +{"password": "12345"} + + #----------------------------------------------------------------------------- # OTHER #----------------------------------------------------------------------------- diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index b58d7374c..0b8e9006b 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -168,8 +168,8 @@ def test_exec(self): def test_hardcoded_passwords(self): """Test for hard-coded passwords.""" expect = { - "SEVERITY": {"UNDEFINED": 0, "LOW": 43, "MEDIUM": 0, "HIGH": 0}, - "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 43, "HIGH": 0}, + "SEVERITY": {"UNDEFINED": 0, "LOW": 51, "MEDIUM": 0, "HIGH": 0}, + "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 51, "HIGH": 0}, } self.check_example("hardcoded-passwords.py", expect) From c39fe4ea7526b1a900684d186a4aa93c0c4be8c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 23:47:32 +0000 Subject: [PATCH 12/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- bandit/plugins/general_hardcoded_password.py | 61 ++++++++------------ 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py index 3c2d89a2d..4e6071d34 100644 --- a/bandit/plugins/general_hardcoded_password.py +++ b/bandit/plugins/general_hardcoded_password.py @@ -83,15 +83,11 @@ def hardcoded_password_string(context): if isinstance(node._bandit_parent, ast.Assign): # multiple targets? "a = b.d = c[e] = ..." for targ in node._bandit_parent.targets: - if ( - isinstance(targ, ast.Name) - and RE_CANDIDATES.search(targ.id) - ): + if isinstance(targ, ast.Name) and RE_CANDIDATES.search(targ.id): # looks for "candidate='this_string'" return _report(node.value) - elif ( - isinstance(targ, ast.Attribute) - and RE_CANDIDATES.search(targ.attr) + elif isinstance(targ, ast.Attribute) and RE_CANDIDATES.search( + targ.attr ): # looks for "o.candidate='this_string'" return _report(node.value) @@ -103,9 +99,8 @@ def hardcoded_password_string(context): ): # looks for "d['candidate'] = 'this_string'" return _report(node.value) - elif ( - isinstance(targ.slice, ast.Name) - and RE_CANDIDATES.search(targ.slice.id) + elif isinstance(targ.slice, ast.Name) and RE_CANDIDATES.search( + targ.slice.id ): # looks for "d[candidate] = 'this_string'" return _report(node.value) @@ -118,15 +113,13 @@ def hardcoded_password_string(context): target_node = node._bandit_parent.target - if ( - isinstance(target_node, ast.Name) - and RE_CANDIDATES.search(target_node.id) + if isinstance(target_node, ast.Name) and RE_CANDIDATES.search( + target_node.id ): # looks for "candidate: str = 'this_str'" return _report(node.value) - elif ( - isinstance(target_node, ast.Attribute) - and RE_CANDIDATES.search(target_node.attr) + elif isinstance(target_node, ast.Attribute) and RE_CANDIDATES.search( + target_node.attr ): # looks for "o.candidate: str = 'this_str'" return _report(node.value) @@ -138,10 +131,9 @@ def hardcoded_password_string(context): ): # looks for "d['candidate']: str = 'this_str'" return _report(node.value) - elif ( - isinstance(target_node.slice, ast.Name) - and RE_CANDIDATES.search(target_node.slice.id) - ): + elif isinstance( + target_node.slice, ast.Name + ) and RE_CANDIDATES.search(target_node.slice.id): # looks for "d[candidate]: str = 'some_str'" return _report(node.value) @@ -154,15 +146,13 @@ def hardcoded_password_string(context): pos = dict_node.values.index(node) key_node = dict_node.keys[pos] - if ( - isinstance(key_node, ast.Constant) - and RE_CANDIDATES.search(key_node.value) + if isinstance(key_node, ast.Constant) and RE_CANDIDATES.search( + key_node.value ): # looks for "{'candidate': 'some_string'}" return _report(node.value) - elif ( - isinstance(key_node, ast.Name) - and RE_CANDIDATES.search(key_node.id) + elif isinstance(key_node, ast.Name) and RE_CANDIDATES.search( + key_node.id ): # looks for "{candidate: 'some_string'}" return _report(node.value) @@ -172,16 +162,14 @@ def hardcoded_password_string(context): comp_node = node._bandit_parent operand_nodes = [comp_node.left] + comp_node.comparators for operand_node in operand_nodes: - if ( - isinstance(operand_node, ast.Name) - and RE_CANDIDATES.search(operand_node.id) + if isinstance(operand_node, ast.Name) and RE_CANDIDATES.search( + operand_node.id ): # looks for "password == 'this_string'" return _report(node.value) - elif ( - isinstance(operand_node, ast.Attribute) - and RE_CANDIDATES.search(operand_node.attr) - ): + elif isinstance( + operand_node, ast.Attribute + ) and RE_CANDIDATES.search(operand_node.attr): # looks for "o.password == 'this_string'" return _report(node.value) elif isinstance(operand_node, ast.Subscript): @@ -192,10 +180,9 @@ def hardcoded_password_string(context): ): # looks for "d["candidate"] == 'this_str'" return _report(node.value) - elif ( - isinstance(operand_node.slice, ast.Name) - and RE_CANDIDATES.search(operand_node.slice.id) - ): + elif isinstance( + operand_node.slice, ast.Name + ) and RE_CANDIDATES.search(operand_node.slice.id): # looks for "d[candidate] == 'some_str'" return _report(node.value)