Skip to content

Commit 5ff73ff

Browse files
mschfhericwb
andauthored
add check for "requests" calls without timeout (#743)
* add check for "requests" calls without timeout * change request_without_timeout confidence to low * Update bandit/plugins/request_without_timeout.py Co-authored-by: Eric Brown <ericwb@users.noreply.github.com> * Update bandit/plugins/request_without_timeout.py Co-authored-by: Eric Brown <ericwb@users.noreply.github.com> * Update doc/source/plugins/b113_request_without_timeout.rst Co-authored-by: Eric Brown <ericwb@users.noreply.github.com> * Update doc/source/plugins/b113_request_without_timeout.rst Co-authored-by: Eric Brown <ericwb@users.noreply.github.com> * Update bandit/plugins/request_without_timeout.py Co-authored-by: Eric Brown <ericwb@users.noreply.github.com> * remove utf-8 * fix confidence in comment * Apply suggestions from code review * Update issue.py * Apply suggestions from code review * Apply suggestions from code review * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Eric Brown <ericwb@users.noreply.github.com>
1 parent def9928 commit 5ff73ff

File tree

8 files changed

+129
-16
lines changed

8 files changed

+129
-16
lines changed

bandit/core/issue.py

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Cwe:
2525
BROKEN_CRYPTO = 327
2626
INSUFFICIENT_RANDOM_VALUES = 330
2727
INSECURE_TEMP_FILE = 377
28+
UNCONTROLLED_RESOURCE_CONSUMPTION = 400
2829
DESERIALIZATION_OF_UNTRUSTED_DATA = 502
2930
MULTIPLE_BINDS = 605
3031
IMPROPER_CHECK_OF_EXCEPT_COND = 703
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
r"""
3+
=======================================
4+
B113: Test for missing requests timeout
5+
=======================================
6+
7+
This plugin test checks for ``requests`` calls without a timeout specified.
8+
9+
Nearly all production code should use this parameter in nearly all requests,
10+
Failure to do so can cause your program to hang indefinitely.
11+
12+
When request methods are used without the timeout parameter set,
13+
Bandit will return a MEDIUM severity error.
14+
15+
16+
:Example:
17+
18+
.. code-block:: none
19+
20+
>> Issue: [B113:request_without_timeout] Requests call without timeout
21+
Severity: Medium Confidence: Low
22+
CWE: CWE-400 (https://cwe.mitre.org/data/definitions/400.html)
23+
More Info: https://bandit.readthedocs.io/en/latest/plugins/b113_request_without_timeout.html
24+
Location: examples/requests-missing-timeout.py:3:0
25+
2
26+
3 requests.get('https://gmail.com')
27+
4 requests.get('https://gmail.com', timeout=None)
28+
29+
--------------------------------------------------
30+
>> Issue: [B113:request_without_timeout] Requests call with timeout set to None
31+
Severity: Medium Confidence: Low
32+
CWE: CWE-400 (https://cwe.mitre.org/data/definitions/400.html)
33+
More Info: https://bandit.readthedocs.io/en/latest/plugins/b113_request_without_timeout.html
34+
Location: examples/requests-missing-timeout.py:4:0
35+
3 requests.get('https://gmail.com')
36+
4 requests.get('https://gmail.com', timeout=None)
37+
5 requests.get('https://gmail.com', timeout=5)
38+
39+
.. seealso::
40+
41+
- https://2.python-requests.org/en/master/user/quickstart/#timeouts
42+
43+
.. versionadded:: 1.7.5
44+
45+
""" # noqa: E501
46+
import bandit
47+
from bandit.core import issue
48+
from bandit.core import test_properties as test
49+
50+
51+
@test.checks("Call")
52+
@test.test_id("B113")
53+
def request_without_timeout(context):
54+
http_verbs = ("get", "options", "head", "post", "put", "patch", "delete")
55+
if (
56+
"requests" in context.call_function_name_qual
57+
and context.call_function_name in http_verbs
58+
):
59+
# check for missing timeout
60+
if context.check_call_arg_value("timeout") is None:
61+
return bandit.Issue(
62+
severity=bandit.MEDIUM,
63+
confidence=bandit.LOW,
64+
cwe=issue.Cwe.UNCONTROLLED_RESOURCE_CONSUMPTION,
65+
text="Requests call without timeout",
66+
)
67+
# check for timeout=None
68+
if context.check_call_arg_value("timeout", "None"):
69+
return bandit.Issue(
70+
severity=bandit.MEDIUM,
71+
confidence=bandit.LOW,
72+
cwe=issue.Cwe.UNCONTROLLED_RESOURCE_CONSUMPTION,
73+
text="Requests call with timeout set to None",
74+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----------------------------
2+
B113: request_without_timeout
3+
-----------------------------
4+
5+
.. automodule:: bandit.plugins.request_without_timeout

examples/httpoxy_cgihandler.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import wsgiref.handlers
33

44
def application(environ, start_response):
5-
r = requests.get('https://192.168.0.42/private/api/foobar')
5+
r = requests.get('https://192.168.0.42/private/api/foobar', timeout=30)
66
start_response('200 OK', [('Content-Type', 'text/plain')])
77
return [r.content]
88

examples/requests-missing-timeout.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import requests
2+
3+
requests.get('https://gmail.com')
4+
requests.get('https://gmail.com', timeout=None)
5+
requests.get('https://gmail.com', timeout=5)
6+
requests.post('https://gmail.com')
7+
requests.post('https://gmail.com', timeout=None)
8+
requests.post('https://gmail.com', timeout=5)
9+
requests.put('https://gmail.com')
10+
requests.put('https://gmail.com', timeout=None)
11+
requests.put('https://gmail.com', timeout=5)
12+
requests.delete('https://gmail.com')
13+
requests.delete('https://gmail.com', timeout=None)
14+
requests.delete('https://gmail.com', timeout=5)
15+
requests.patch('https://gmail.com')
16+
requests.patch('https://gmail.com', timeout=None)
17+
requests.patch('https://gmail.com', timeout=5)
18+
requests.options('https://gmail.com')
19+
requests.options('https://gmail.com', timeout=None)
20+
requests.options('https://gmail.com', timeout=5)
21+
requests.head('https://gmail.com')
22+
requests.head('https://gmail.com', timeout=None)
23+
requests.head('https://gmail.com', timeout=5)

examples/requests-ssl-verify-disabled.py

+14-15
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import httpx
22
import requests
33

4-
5-
requests.get('https://gmail.com', verify=True)
6-
requests.get('https://gmail.com', verify=False)
7-
requests.post('https://gmail.com', verify=True)
8-
requests.post('https://gmail.com', verify=False)
9-
requests.put('https://gmail.com', verify=True)
10-
requests.put('https://gmail.com', verify=False)
11-
requests.delete('https://gmail.com', verify=True)
12-
requests.delete('https://gmail.com', verify=False)
13-
requests.patch('https://gmail.com', verify=True)
14-
requests.patch('https://gmail.com', verify=False)
15-
requests.options('https://gmail.com', verify=True)
16-
requests.options('https://gmail.com', verify=False)
17-
requests.head('https://gmail.com', verify=True)
18-
requests.head('https://gmail.com', verify=False)
4+
requests.get('https://gmail.com', timeout=30, verify=True)
5+
requests.get('https://gmail.com', timeout=30, verify=False)
6+
requests.post('https://gmail.com', timeout=30, verify=True)
7+
requests.post('https://gmail.com', timeout=30, verify=False)
8+
requests.put('https://gmail.com', timeout=30, verify=True)
9+
requests.put('https://gmail.com', timeout=30, verify=False)
10+
requests.delete('https://gmail.com', timeout=30, verify=True)
11+
requests.delete('https://gmail.com', timeout=30, verify=False)
12+
requests.patch('https://gmail.com', timeout=30, verify=True)
13+
requests.patch('https://gmail.com', timeout=30, verify=False)
14+
requests.options('https://gmail.com', timeout=30, verify=True)
15+
requests.options('https://gmail.com', timeout=30, verify=False)
16+
requests.head('https://gmail.com', timeout=30, verify=True)
17+
requests.head('https://gmail.com', timeout=30, verify=False)
1918

2019
httpx.request('GET', 'https://gmail.com', verify=True)
2120
httpx.request('GET', 'https://gmail.com', verify=False)

setup.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ bandit.plugins =
6060
# bandit/plugins/crypto_request_no_cert_validation.py
6161
request_with_no_cert_validation = bandit.plugins.crypto_request_no_cert_validation:request_with_no_cert_validation
6262

63+
# bandit/plugins/request_without_timeout.py
64+
request_without_timeout = bandit.plugins.request_without_timeout:request_without_timeout
65+
6366
# bandit/plugins/exec.py
6467
exec_used = bandit.plugins.exec:exec_used
6568

tests/functional/test_functional.py

+8
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,14 @@ def test_requests_ssl_verify_disabled(self):
393393
}
394394
self.check_example("requests-ssl-verify-disabled.py", expect)
395395

396+
def test_requests_without_timeout(self):
397+
"""Test for the `requests` library missing timeouts."""
398+
expect = {
399+
"SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 14, "HIGH": 0},
400+
"CONFIDENCE": {"UNDEFINED": 0, "LOW": 14, "MEDIUM": 0, "HIGH": 0},
401+
}
402+
self.check_example("requests-missing-timeout.py", expect)
403+
396404
def test_skip(self):
397405
"""Test `#nosec` and `#noqa` comments."""
398406
expect = {

0 commit comments

Comments
 (0)