-
Notifications
You must be signed in to change notification settings - Fork 1
Add Flask decorators for Cache-Control headers #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import unittest | ||
|
|
||
| from flask import Flask | ||
| from tna_utilities.flask import cacheable_duration, do_not_cache, set_cache_control | ||
|
|
||
|
|
||
| class TestFlaskCacheControl(unittest.TestCase): | ||
| def setUp(self): | ||
| self.app = Flask(__name__) | ||
| self.test_client = self.app.test_client() | ||
|
|
||
| def test_naked_route(self): | ||
| @self.app.route("/") | ||
| def index(): | ||
| return "OK" | ||
|
|
||
| rv = self.test_client.get("/") | ||
|
|
||
| self.assertEqual(rv.status_code, 200) | ||
| self.assertNotIn("Cache-Control", rv.headers) | ||
|
|
||
| def test_do_not_cache_route(self): | ||
| @self.app.route("/") | ||
| @do_not_cache() | ||
| def index(): | ||
| return "OK" | ||
|
|
||
| rv = self.test_client.get("/") | ||
|
|
||
| self.assertEqual(rv.status_code, 200) | ||
| self.assertIn("Cache-Control", rv.headers) | ||
| self.assertEqual( | ||
| rv.headers["Cache-Control"], | ||
| "no-store", | ||
| ) | ||
|
|
||
| def test_cacheable_duration_route(self): | ||
| @self.app.route("/") | ||
| @cacheable_duration() | ||
| def index(): | ||
| return "OK" | ||
|
|
||
| rv = self.test_client.get("/") | ||
|
|
||
| self.assertEqual(rv.status_code, 200) | ||
| self.assertIn("Cache-Control", rv.headers) | ||
| self.assertEqual( | ||
| rv.headers["Cache-Control"], | ||
| "public, max-age=3600", | ||
| ) | ||
|
|
||
| def test_cacheable_duration_custom_duration_route(self): | ||
| @self.app.route("/") | ||
| @cacheable_duration(60) | ||
| def index(): | ||
| return "OK" | ||
|
|
||
| rv = self.test_client.get("/") | ||
|
|
||
| self.assertEqual(rv.status_code, 200) | ||
| self.assertIn("Cache-Control", rv.headers) | ||
| self.assertEqual( | ||
| rv.headers["Cache-Control"], | ||
| "public, max-age=60", | ||
| ) | ||
|
|
||
| def test_set_cache_control_route(self): | ||
| @self.app.route("/") | ||
| @set_cache_control("private, max-age=120") | ||
| def index(): | ||
| return "OK" | ||
|
|
||
| rv = self.test_client.get("/") | ||
|
|
||
| self.assertEqual(rv.status_code, 200) | ||
| self.assertIn("Cache-Control", rv.headers) | ||
| self.assertEqual( | ||
| rv.headers["Cache-Control"], | ||
| "private, max-age=120", | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from tna_utilities.flask.cache_control import ( # noqa: F401 | ||
| cacheable_duration, | ||
| do_not_cache, | ||
| set_cache_control, | ||
| ) | ||
| from tna_utilities.flask.talisman import Talisman # noqa: F401 |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||||
| from functools import wraps | ||||||||
|
|
||||||||
| from flask import make_response | ||||||||
|
|
||||||||
|
|
||||||||
| def do_not_cache(): | ||||||||
| """ | ||||||||
| Decorator to set Cache-Control headers to prevent caching of the response. | ||||||||
| """ | ||||||||
|
|
||||||||
| def decorator(f): | ||||||||
| @wraps(f) | ||||||||
| def decorated_function(*args, **kwargs): | ||||||||
| response = make_response(f(*args, **kwargs)) | ||||||||
| headers = response.headers | ||||||||
| headers["Cache-Control"] = "no-store" | ||||||||
| return response | ||||||||
|
|
||||||||
| return decorated_function | ||||||||
|
|
||||||||
| return decorator | ||||||||
|
|
||||||||
|
|
||||||||
| def cacheable_duration(seconds: int = 3600): | ||||||||
| """ | ||||||||
| Decorator to set Cache-Control headers to allow caching of the response for a specified duration. | ||||||||
| """ | ||||||||
|
|
||||||||
| def decorator(f): | ||||||||
| @wraps(f) | ||||||||
| def decorated_function(*args, **kwargs): | ||||||||
| response = make_response(f(*args, **kwargs)) | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe Usage of flask.make_response() with Unescaped Content (CWE-79) More DetailsThe
RemediationThe To fix this issue securely, you should avoid using Code examples# VULNERABLE CODE - Directly passing user input to make_response() without sanitization
user_input = request.args.get('input')
response = flask.make_response(user_input)# SECURE CODE - Using render_template() to render HTML templates with automatic escaping
user_input = request.args.get('input')
response = flask.render_template('template.html', user_input=user_input)# SECURE CODE - Using jsonify() to return data from an API
data = {'key': request.args.get('input')}
response = flask.jsonify(data)Additional recommendations
To ignore this finding as an exception, reply to this conversation with If you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more). To get more details on how to remediate this issue using AI, reply to this conversation with |
||||||||
| headers = response.headers | ||||||||
| headers["Cache-Control"] = f"public, max-age={seconds}" | ||||||||
| return response | ||||||||
|
|
||||||||
| return decorated_function | ||||||||
|
|
||||||||
| return decorator | ||||||||
|
|
||||||||
|
|
||||||||
| def set_cache_control(instructions: str): | ||||||||
| """ | ||||||||
| Decorator to set Cache-Control headers with custom instructions provided as a string. | ||||||||
| """ | ||||||||
|
|
||||||||
| def decorator(f): | ||||||||
| @wraps(f) | ||||||||
| def decorated_function(*args, **kwargs): | ||||||||
| response = make_response(f(*args, **kwargs)) | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe Usage of flask.make_response() with Unescaped Content (CWE-79) More DetailsThe
RemediationThe To fix this issue securely, you should avoid using Code examples# VULNERABLE CODE - Directly passing user input to make_response() without sanitization
user_input = request.args.get('input')
response = flask.make_response(user_input)# SECURE CODE - Using render_template() to render HTML templates with automatic escaping
user_input = request.args.get('input')
response = flask.render_template('template.html', user_input=user_input)# SECURE CODE - Using jsonify() to return data from an API
data = {'key': request.args.get('input')}
response = flask.jsonify(data)Additional recommendations
To ignore this finding as an exception, reply to this conversation with If you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more). To get more details on how to remediate this issue using AI, reply to this conversation with |
||||||||
| headers = response.headers | ||||||||
| headers["Cache-Control"] = instructions | ||||||||
| return response | ||||||||
|
|
||||||||
| return decorated_function | ||||||||
|
|
||||||||
| return decorator | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsafe Usage of flask.make_response() with Unescaped Content (CWE-79)
More Details
The
flask.make_response()function is used to create a response object without properly escaping the content. If the response contains user-supplied data or untrusted content, it can lead to a Cross-Site Scripting (XSS) vulnerability. XSS vulnerabilities allow attackers to inject malicious scripts into web pages, which can lead to data theft, session hijacking, and other security risks.When rendering HTML content, it is crucial to escape or sanitize any user-supplied data to prevent XSS attacks. Failing to do so can allow attackers to inject malicious scripts that will be executed in the user's browser, potentially compromising their data or session. The consequences of an XSS attack can be severe, including data theft, account takeover, and the spread of malware. To avoid this issue, use
flask.render_template()for rendering HTML templates, as it automatically escapes user input. For returning data from an API, useflask.jsonify()instead.Remediation
The
flask.make_response()function in Flask can lead to cross-site scripting (XSS) vulnerabilities if the response content is not properly sanitized or escaped. XSS vulnerabilities allow attackers to inject malicious scripts into web pages, potentially compromising user data, hijacking sessions, or performing other malicious actions.To fix this issue securely, you should avoid using
flask.make_response()with untrusted or user-supplied data. Instead, useflask.render_template()for rendering HTML templates, as it automatically escapes user input to prevent XSS attacks. If you're returning data from an API, useflask.jsonify()to serialize data to JSON format.Code examples
Additional recommendations
To ignore this finding as an exception, reply to this conversation with
#wiz_ignore reasonIf you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more).
To get more details on how to remediate this issue using AI, reply to this conversation with
#wiz remediate