Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added option in Flask Talisman to add Adobe Typekit CSP rules with `allow_typekit_content_security_policy=True`
- Added `extra_headers` parameter in `Talisman` to update or add any global response headers
- New pagination functions for populating pagination components
- New Flask decorators for specifying `Cache-Control` headers in response

### Changed

Expand Down
38 changes: 36 additions & 2 deletions docs/flask.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
# Flask

> Added in `v1.4.0`.
## Cache control

> Added in `v1.5.0`.

A set of decorators to manage the `Cache-Control` header of a route.

### Examples

```python
from flask import Flask
from tna_utilities.flask import cacheable_duration, do_not_cache, set_cache_control

app = Flask(__name__)

@app.route("/default/")
def default():
return "No cache instructions given"

@app.route("/cacheable/")
@cacheable_duration(3600)
def cachable_for_up_to_1h():
return "You can cache me for up to an hour"

@app.route("/not-cacheable/")
@do_not_cache()
def not_cachable():
return "Don't cache me!"

@app.route("/not-cacheable/")
@set_cache_control("private, max-age=120")
def private_cache():
return "Cache me in private caches for up to 2 minutes"
```

## `Talisman`

> Added in `v1.4.0`.

A stripped-down and opinionated reproduction of [wntrblm/flask-talisman](https://github.com/wntrblm/flask-talisman) which is a fork of [GoogleCloudPlatform/flask-talisman](https://github.com/GoogleCloudPlatform/flask-talisman).

### Examples

```python
from flask import Flask
from tna_utilities.flask.talisman import Talisman
from tna_utilities.flask import Talisman

app = Flask(__name__)
Talisman(app)
Expand Down
80 changes: 80 additions & 0 deletions tests/test_flask_cache_control.py
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",
)
4 changes: 2 additions & 2 deletions tests/test_flask_talisman.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import unittest

from flask import Flask, session
from tna_utilities.flask.talisman import Talisman
from tna_utilities.flask import Talisman


class TestTalisman(unittest.TestCase):
class TestFlaskTalisman(unittest.TestCase):
def setUp(self):
self.app = Flask(__name__)
self.app.config["SECRET_KEY"] = "my_secret_key"
Expand Down
6 changes: 6 additions & 0 deletions tna_utilities/flask/__init__.py
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
57 changes: 57 additions & 0 deletions tna_utilities/flask/cache_control.py
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))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low SAST Finding

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, use flask.jsonify() instead.

Attribute Value
Impact Medium
Likelihood Low

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, use flask.render_template() for rendering HTML templates, as it automatically escapes user input to prevent XSS attacks. If you're returning data from an API, use flask.jsonify() to serialize data to JSON format.

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

  • Follow the principle of least privilege and sanitize all user input before rendering or processing it.
  • Implement input validation and output encoding to prevent injection attacks.
  • Adhere to the OWASP Application Security Verification Standard (ASVS) for secure coding practices.
  • Consider using Content Security Policy (CSP) headers to mitigate XSS risks further.
  • Regularly update Flask and its dependencies to ensure you have the latest security patches.

Rule ID: WS-I013-PYTHON-00095


To ignore this finding as an exception, reply to this conversation with #wiz_ignore reason

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 #wiz remediate

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))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low SAST Finding

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, use flask.jsonify() instead.

Attribute Value
Impact Medium
Likelihood Low

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, use flask.render_template() for rendering HTML templates, as it automatically escapes user input to prevent XSS attacks. If you're returning data from an API, use flask.jsonify() to serialize data to JSON format.

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

  • Follow the principle of least privilege and sanitize all user input before rendering or processing it.
  • Implement input validation and output encoding to prevent injection attacks.
  • Adhere to the OWASP Application Security Verification Standard (ASVS) for secure coding practices.
  • Consider using Content Security Policy (CSP) headers to mitigate XSS risks further.
  • Regularly update Flask and its dependencies to ensure you have the latest security patches.

Rule ID: WS-I013-PYTHON-00095


To ignore this finding as an exception, reply to this conversation with #wiz_ignore reason

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 #wiz remediate

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))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low SAST Finding

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, use flask.jsonify() instead.

Attribute Value
Impact Medium
Likelihood Low

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, use flask.render_template() for rendering HTML templates, as it automatically escapes user input to prevent XSS attacks. If you're returning data from an API, use flask.jsonify() to serialize data to JSON format.

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

  • Follow the principle of least privilege and sanitize all user input before rendering or processing it.
  • Implement input validation and output encoding to prevent injection attacks.
  • Adhere to the OWASP Application Security Verification Standard (ASVS) for secure coding practices.
  • Consider using Content Security Policy (CSP) headers to mitigate XSS risks further.
  • Regularly update Flask and its dependencies to ensure you have the latest security patches.

Rule ID: WS-I013-PYTHON-00095


To ignore this finding as an exception, reply to this conversation with #wiz_ignore reason

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 #wiz remediate

headers = response.headers
headers["Cache-Control"] = instructions
return response

return decorated_function

return decorator
Loading