Repository: PyCQA/bandit
Pull request #1151 (2024-06-24 03:57:00)

Add recent releases to version choice in bug report
Added 1.7.9 and 1.7.8 to bug template.


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 99399f2..e2c4673 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -44,7 +44,9 @@ body:
       label: Bandit version
       description: Run "bandit --version" if unsure of version number
       options:
-        - 1.7.7 (Default)
+        - 1.7.9 (Default)
+        - 1.7.8
+        - 1.7.7
         - 1.7.6
         - 1.7.5
         - 1.7.4
diff --git a/.github/workflows/build-publish-image.yml b/.github/workflows/build-publish-image.yml
index bbe2d77..b961bf6 100644
--- a/.github/workflows/build-publish-image.yml
+++ b/.github/workflows/build-publish-image.yml
@@ -51,7 +51,7 @@ jobs:
 
     - name: Build and push Docker image
       id: build-and-push
-      uses: docker/build-push-action@c382f710d39a5bb4e430307530a720f50c2d3318 # v6
+      uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6
       with:
         context: .
         file: ./docker/Dockerfile
diff --git a/bandit/core/node_visitor.py b/bandit/core/node_visitor.py
index 27a4de5..938e873 100644
--- a/bandit/core/node_visitor.py
+++ b/bandit/core/node_visitor.py
@@ -19,7 +19,6 @@ class BanditNodeVisitor:
     ):
         self.debug = debug
         self.nosec_lines = nosec_lines
-        self.seen = 0
         self.scores = {
             "SEVERITY": [0] * len(constants.RANKING),
             "CONFIDENCE": [0] * len(constants.RANKING),
@@ -209,7 +208,6 @@ class BanditNodeVisitor:
         self.context["filename"] = self.fname
         self.context["file_data"] = self.fdata
 
-        self.seen += 1
         LOG.debug(
             "entering: %s %s [%s]", hex(id(node)), type(node), self.depth
         )
diff --git a/bandit/plugins/crypto_request_no_cert_validation.py b/bandit/plugins/crypto_request_no_cert_validation.py
index 223d421..11791ed 100644
--- a/bandit/plugins/crypto_request_no_cert_validation.py
+++ b/bandit/plugins/crypto_request_no_cert_validation.py
@@ -54,8 +54,8 @@ from bandit.core import test_properties as test
 @test.checks("Call")
 @test.test_id("B501")
 def request_with_no_cert_validation(context):
-    HTTP_VERBS = ("get", "options", "head", "post", "put", "patch", "delete")
-    HTTPX_ATTRS = ("request", "stream", "Client", "AsyncClient") + HTTP_VERBS
+    HTTP_VERBS = {"get", "options", "head", "post", "put", "patch", "delete"}
+    HTTPX_ATTRS = {"request", "stream", "Client", "AsyncClient"} | HTTP_VERBS
     qualname = context.call_function_name_qual.split(".")[0]
 
     if (
diff --git a/bandit/plugins/request_without_timeout.py b/bandit/plugins/request_without_timeout.py
index a418b6c..d571a49 100644
--- a/bandit/plugins/request_without_timeout.py
+++ b/bandit/plugins/request_without_timeout.py
@@ -4,7 +4,8 @@ r"""
 B113: Test for missing requests timeout
 =======================================
 
-This plugin test checks for ``requests`` calls without a timeout specified.
+This plugin test checks for ``requests`` or ``httpx`` calls without a timeout
+specified.
 
 Nearly all production code should use this parameter in nearly all requests,
 Failure to do so can cause your program to hang indefinitely.
@@ -17,7 +18,7 @@ Bandit will return a MEDIUM severity error.
 
 .. code-block:: none
 
-    >> Issue: [B113:request_without_timeout] Requests call without timeout
+    >> Issue: [B113:request_without_timeout] Call to requests without timeout
        Severity: Medium   Confidence: Low
        CWE: CWE-400 (https://cwe.mitre.org/data/definitions/400.html)
        More Info: https://bandit.readthedocs.io/en/latest/plugins/b113_request_without_timeout.html
@@ -27,7 +28,7 @@ Bandit will return a MEDIUM severity error.
     4	requests.get('https://gmail.com', timeout=None)
 
     --------------------------------------------------
-    >> Issue: [B113:request_without_timeout] Requests call with timeout set to None
+    >> Issue: [B113:request_without_timeout] Call to requests with timeout set to None
        Severity: Medium   Confidence: Low
        CWE: CWE-400 (https://cwe.mitre.org/data/definitions/400.html)
        More Info: https://bandit.readthedocs.io/en/latest/plugins/b113_request_without_timeout.html
@@ -42,6 +43,9 @@ Bandit will return a MEDIUM severity error.
 
 .. versionadded:: 1.7.5
 
+.. versionchanged:: 1.7.10
+    Added check for httpx module
+
 """  # noqa: E501
 import bandit
 from bandit.core import issue
@@ -51,17 +55,23 @@ from bandit.core import test_properties as test
 @test.checks("Call")
 @test.test_id("B113")
 def request_without_timeout(context):
-    http_verbs = ("get", "options", "head", "post", "put", "patch", "delete")
+    HTTP_VERBS = {"get", "options", "head", "post", "put", "patch", "delete"}
+    HTTPX_ATTRS = {"request", "stream", "Client", "AsyncClient"} | HTTP_VERBS
     qualname = context.call_function_name_qual.split(".")[0]
 
-    if qualname == "requests" and context.call_function_name in http_verbs:
+    if (
+        qualname == "requests"
+        and context.call_function_name in HTTP_VERBS
+        or qualname == "httpx"
+        and context.call_function_name in HTTPX_ATTRS
+    ):
         # check for missing timeout
         if context.check_call_arg_value("timeout") is None:
             return bandit.Issue(
                 severity=bandit.MEDIUM,
                 confidence=bandit.LOW,
                 cwe=issue.Cwe.UNCONTROLLED_RESOURCE_CONSUMPTION,
-                text="Requests call without timeout",
+                text=f"Call to {qualname} without timeout",
             )
         # check for timeout=None
         if context.check_call_arg_value("timeout", "None"):
@@ -69,5 +79,5 @@ def request_without_timeout(context):
                 severity=bandit.MEDIUM,
                 confidence=bandit.LOW,
                 cwe=issue.Cwe.UNCONTROLLED_RESOURCE_CONSUMPTION,
-                text="Requests call with timeout set to None",
+                text=f"Call to {qualname} with timeout set to None",
             )
diff --git a/examples/requests-missing-timeout.py b/examples/requests-missing-timeout.py
index 38f2444..fa71c4b 100644
--- a/examples/requests-missing-timeout.py
+++ b/examples/requests-missing-timeout.py
@@ -1,27 +1,68 @@
+import httpx
 import requests
 import not_requests
 
+# Errors
 requests.get('https://gmail.com')
 requests.get('https://gmail.com', timeout=None)
-requests.get('https://gmail.com', timeout=5)
 requests.post('https://gmail.com')
 requests.post('https://gmail.com', timeout=None)
-requests.post('https://gmail.com', timeout=5)
 requests.put('https://gmail.com')
 requests.put('https://gmail.com', timeout=None)
-requests.put('https://gmail.com', timeout=5)
 requests.delete('https://gmail.com')
 requests.delete('https://gmail.com', timeout=None)
-requests.delete('https://gmail.com', timeout=5)
 requests.patch('https://gmail.com')
 requests.patch('https://gmail.com', timeout=None)
-requests.patch('https://gmail.com', timeout=5)
 requests.options('https://gmail.com')
 requests.options('https://gmail.com', timeout=None)
-requests.options('https://gmail.com', timeout=5)
 requests.head('https://gmail.com')
 requests.head('https://gmail.com', timeout=None)
-requests.head('https://gmail.com', timeout=5)
+httpx.get('https://gmail.com')
+httpx.get('https://gmail.com', timeout=None)
+httpx.post('https://gmail.com')
+httpx.post('https://gmail.com', timeout=None)
+httpx.put('https://gmail.com')
+httpx.put('https://gmail.com', timeout=None)
+httpx.delete('https://gmail.com')
+httpx.delete('https://gmail.com', timeout=None)
+httpx.patch('https://gmail.com')
+httpx.patch('https://gmail.com', timeout=None)
+httpx.options('https://gmail.com')
+httpx.options('https://gmail.com', timeout=None)
+httpx.head('https://gmail.com')
+httpx.head('https://gmail.com', timeout=None)
+httpx.Client()
+httpx.Client(timeout=None)
+httpx.AsyncClient()
+httpx.AsyncClient(timeout=None)
+with httpx.Client() as client:
+    client.get('https://gmail.com')
+with httpx.Client(timeout=None) as client:
+    client.get('https://gmail.com')
+async with httpx.AsyncClient() as client:
+    await client.get('https://gmail.com')
+async with httpx.AsyncClient(timeout=None) as client:
+    await client.get('https://gmail.com')
 
 # Okay
 not_requests.get('https://gmail.com')
+requests.get('https://gmail.com', timeout=5)
+requests.post('https://gmail.com', timeout=5)
+requests.put('https://gmail.com', timeout=5)
+requests.delete('https://gmail.com', timeout=5)
+requests.patch('https://gmail.com', timeout=5)
+requests.options('https://gmail.com', timeout=5)
+requests.head('https://gmail.com', timeout=5)
+httpx.get('https://gmail.com', timeout=5)
+httpx.post('https://gmail.com', timeout=5)
+httpx.put('https://gmail.com', timeout=5)
+httpx.delete('https://gmail.com', timeout=5)
+httpx.patch('https://gmail.com', timeout=5)
+httpx.options('https://gmail.com', timeout=5)
+httpx.head('https://gmail.com', timeout=5)
+httpx.Client(timeout=5)
+httpx.AsyncClient(timeout=5)
+with httpx.Client(timeout=5) as client:
+    client.get('https://gmail.com')
+async with httpx.AsyncClient(timeout=5) as client:
+    await client.get('https://gmail.com')
diff --git a/examples/requests-ssl-verify-disabled.py b/examples/requests-ssl-verify-disabled.py
index 25f5ef4..c45b9e9 100644
--- a/examples/requests-ssl-verify-disabled.py
+++ b/examples/requests-ssl-verify-disabled.py
@@ -1,6 +1,7 @@
 import httpx
 import requests
 
+# Errors
 requests.get('https://gmail.com', timeout=30, verify=True)
 requests.get('https://gmail.com', timeout=30, verify=False)
 requests.post('https://gmail.com', timeout=30, verify=True)
@@ -16,25 +17,26 @@ requests.options('https://gmail.com', timeout=30, verify=False)
 requests.head('https://gmail.com', timeout=30, verify=True)
 requests.head('https://gmail.com', timeout=30, verify=False)
 
-httpx.request('GET', 'https://gmail.com', verify=True)
-httpx.request('GET', 'https://gmail.com', verify=False)
-httpx.get('https://gmail.com', verify=True)
-httpx.get('https://gmail.com', verify=False)
-httpx.options('https://gmail.com', verify=True)
-httpx.options('https://gmail.com', verify=False)
-httpx.head('https://gmail.com', verify=True)
-httpx.head('https://gmail.com', verify=False)
-httpx.post('https://gmail.com', verify=True)
-httpx.post('https://gmail.com', verify=False)
-httpx.put('https://gmail.com', verify=True)
-httpx.put('https://gmail.com', verify=False)
-httpx.patch('https://gmail.com', verify=True)
-httpx.patch('https://gmail.com', verify=False)
-httpx.delete('https://gmail.com', verify=True)
-httpx.delete('https://gmail.com', verify=False)
-httpx.stream('https://gmail.com', verify=True)
-httpx.stream('https://gmail.com', verify=False)
-httpx.Client()
-httpx.Client(verify=False)
-httpx.AsyncClient()
-httpx.AsyncClient(verify=False)
+# Okay
+httpx.request('GET', 'https://gmail.com', timeout=30, verify=True)
+httpx.request('GET', 'https://gmail.com', timeout=30, verify=False)
+httpx.get('https://gmail.com', timeout=30, verify=True)
+httpx.get('https://gmail.com', timeout=30, verify=False)
+httpx.options('https://gmail.com', timeout=30, verify=True)
+httpx.options('https://gmail.com', timeout=30, verify=False)
+httpx.head('https://gmail.com', timeout=30, verify=True)
+httpx.head('https://gmail.com', timeout=30, verify=False)
+httpx.post('https://gmail.com', timeout=30, verify=True)
+httpx.post('https://gmail.com', timeout=30, verify=False)
+httpx.put('https://gmail.com', timeout=30, verify=True)
+httpx.put('https://gmail.com', timeout=30, verify=False)
+httpx.patch('https://gmail.com', timeout=30, verify=True)
+httpx.patch('https://gmail.com', timeout=30, verify=False)
+httpx.delete('https://gmail.com', timeout=30, verify=True)
+httpx.delete('https://gmail.com', timeout=30, verify=False)
+httpx.stream('https://gmail.com', timeout=30, verify=True)
+httpx.stream('https://gmail.com', timeout=30, verify=False)
+httpx.Client(timeout=30)
+httpx.Client(timeout=30, verify=False)
+httpx.AsyncClient(timeout=30)
+httpx.AsyncClient(timeout=30, verify=False)


Test patch:
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
index 4597f70..681e45e 100644
--- a/tests/functional/test_functional.py
+++ b/tests/functional/test_functional.py
@@ -411,8 +411,8 @@ class FunctionalTests(testtools.TestCase):
     def test_requests_without_timeout(self):
         """Test for the `requests` library missing timeouts."""
         expect = {
-            "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 14, "HIGH": 0},
-            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 14, "MEDIUM": 0, "HIGH": 0},
+            "SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 36, "HIGH": 0},
+            "CONFIDENCE": {"UNDEFINED": 0, "LOW": 36, "MEDIUM": 0, "HIGH": 0},
         }
         self.check_example("requests-missing-timeout.py", expect)