Repository: PyCQA/bandit (python)

Issue:
Check  for class attributes 
**Is your feature request related to a problem? Please describe.**
Bandit does not throw an error for self.password = "12123123" but does so for password = "12123123"

**Describe the solution you'd like**
Make it so B105:hardcoded_password_string also checks class attributes


Pull request description:
Check for hardcoded passwords in class attributes
B105:hardcoded_password_string currently throws an error for string
literal variables, dictionary keys, and comparisons which look like
passwords, but does not create an error for class attributes which look
like passwords.

For example password = "mypassword" and password == "mypassword"
would create an error, but my_object.password = "mypassword",
and my_object.password == "mypassword" would not.
This behavior is unintuitive.

Resolves #759

Patch:
diff --git a/bandit/plugins/general_hardcoded_password.py b/bandit/plugins/general_hardcoded_password.py
index dbff53c..9a162b4 100644
--- a/bandit/plugins/general_hardcoded_password.py
+++ b/bandit/plugins/general_hardcoded_password.py
@@ -36,6 +36,7 @@ def hardcoded_password_string(context):
 
     - assigned to a variable that looks like a password
     - assigned to a dict key that looks like a password
+    - assigned to a class attribute that looks like a password
     - used in a comparison with a variable that looks like a password
 
     Variables are considered to look like a password if they have match any one
@@ -84,6 +85,10 @@ def hardcoded_password_string(context):
         for targ in node._bandit_parent.targets:
             if isinstance(targ, ast.Name) and RE_CANDIDATES.search(targ.id):
                 return _report(node.s)
+            elif isinstance(targ, ast.Attribute) and RE_CANDIDATES.search(
+                targ.attr
+            ):
+                return _report(node.s)
 
     elif isinstance(
         node._bandit_parent, ast.Subscript
@@ -114,6 +119,10 @@ def hardcoded_password_string(context):
             if RE_CANDIDATES.search(comp.left.id):
                 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.Str):
+                    return _report(comp.comparators[0].s)
 
 
 @test.checks("Call")
diff --git a/examples/hardcoded-passwords.py b/examples/hardcoded-passwords.py
index d7cbdc5..2b501c7 100644
--- a/examples/hardcoded-passwords.py
+++ b/examples/hardcoded-passwords.py
@@ -1,3 +1,8 @@
+# Possible hardcoded password: 'class_password'
+# Severity: Low   Confidence: Medium
+class SomeClass:
+    password = "class_password"
+
 # Possible hardcoded password: 'Admin'
 # Severity: Low   Confidence: Medium
 def someFunction(user, password="Admin"):
@@ -21,6 +26,13 @@ def NoMatch2(password):
     if password == "ajklawejrkl42348swfgkg":
         print("Nice 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: 'blerg'
 # Severity: Low   Confidence: Medium
 def doLogin(password="blerg"):


Test patch:
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index ecfe878..fe535a4 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -157,8 +157,8 @@ class FunctionalTests(testtools.TestCase):
     def test_hardcoded_passwords(self):
         """Test for hard-coded passwords."""
         expect = {
-            "SEVERITY": {"UNDEFINED": 0, "LOW": 12, "MEDIUM": 0, "HIGH": 0},
-            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 12, "HIGH": 0},
+            "SEVERITY": {"UNDEFINED": 0, "LOW": 14, "MEDIUM": 0, "HIGH": 0},
+            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 14, "HIGH": 0},
         }
         self.check_example("hardcoded-passwords.py", expect)