Giter VIP home page Giter VIP logo

flask-talisman's Introduction

Talisman: HTTP security headers for Flask

Build Status Coverage Status PyPI Version

Talisman is a small Flask extension that handles setting HTTP headers that can help protect against a few common web application security issues.

The default configuration:

  • Forces all connects to https, unless running with debug enabled.
  • Enables HTTP Strict Transport Security.
  • Sets Flask's session cookie to secure, so it will never be set if your application is somehow accessed via a non-secure connection.
  • Sets Flask's session cookie to httponly, preventing JavaScript from being able to access its content. CSRF via Ajax uses a separate cookie and should be unaffected.
  • Sets X-Frame-Options to SAMEORIGIN to avoid clickjacking.
  • Sets X-XSS-Protection to enable a cross site scripting filter for IE and Safari (note Chrome has removed this and Firefox never supported it).
  • Sets X-Content-Type-Options to prevent content type sniffing.
  • Sets a strict Content Security Policy of default-src: 'self'. This is intended to almost completely prevent Cross Site Scripting (XSS) attacks. This is probably the only setting that you should reasonably change. See the Content Security Policy section.
  • Sets a strict Referrer-Policy of strict-origin-when-cross-origin that governs which referrer information should be included with requests made.

In addition to Talisman, you should always use a cross-site request forgery (CSRF) library. It's highly recommended to use Flask-SeaSurf, which is based on Django's excellent library.

Installation & Basic Usage

Install via pip:

pip install flask-talisman

After installing, wrap your Flask app with a Talisman:

from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)
Talisman(app)

There is also a full Example App.

Options

  • feature_policy, default {}, see the Feature Policy section.
  • force_https, default True, forces all non-debug connects to https.
  • force_https_permanent, default False, uses 301 instead of 302 for https redirects.
  • frame_options, default SAMEORIGIN, can be SAMEORIGIN, DENY, or ALLOWFROM.
  • frame_options_allow_from, default None, a string indicating the domains that are allowed to embed the site via iframe.
  • strict_transport_security, default True, whether to send HSTS headers.
  • strict_transport_security_preload, default False, enables HSTS preloading If you register your application with Google's HSTS preload list, Firefox and Chrome will never load your site over a non-secure connection.
  • strict_transport_security_max_age, default ONE_YEAR_IN_SECS, length of time the browser will respect the HSTS header.
  • strict_transport_security_include_subdomains, default True, whether subdomains should also use HSTS.
  • content_security_policy, default default-src: 'self', see the Content Security Policy section.
  • content_security_policy_nonce_in, default []. Adds a per-request nonce value to the flask request object and also to the specified CSP header section. I.e. ['script-src', 'style-src']
  • content_security_policy_report_only, default False, whether to set the CSP header as "report-only" (as Content-Security-Policy-Report-Only) to ease deployment by disabling the policy enforcement by the browser, requires passing a value with the content_security_policy_report_uri parameter
  • content_security_policy_report_uri, default None, a string indicating the report URI used for CSP violation reports
  • referrer_policy, default strict-origin-when-cross-origin, a string that sets the Referrer Policy header to send a full URL when performing a same-origin request, only send the origin of the document to an equally secure destination (HTTPS->HTTPS), and send no header to a less secure destination (HTTPS->HTTP).
  • session_cookie_secure, default True, set the session cookie to secure, preventing it from being sent over plain http.
  • session_cookie_http_only, default True, set the session cookie to httponly, preventing it from being read by JavaScript.
  • force_file_save, default False, whether to set the X-Download-Options header to noopen to prevent IE >= 8 to from opening file downloads directly and only save them instead.

Per-view options

Sometimes you want to change the policy for a specific view. The force_https, frame_options, frame_options_allow_from, and content_security_policy options can be changed on a per-view basis.

from flask import Flask
from flask_talisman import Talisman, ALLOW_FROM

app = Flask(__name__)
talisman = Talisman(app)

@app.route('/normal')
def normal():
    return 'Normal'

@app.route('/embeddable')
@talisman(frame_options=ALLOW_FROM, frame_options_allow_from='*')
def embeddable():
    return 'Embeddable'

Content Security Policy

The default content security policy is extremely strict and will prevent loading any resources that are not in the same domain as the application. Most web applications will need to change this policy.

A slightly more permissive policy is available at flask_talisman.GOOGLE_CSP_POLICY, which allows loading Google-hosted JS libraries, fonts, and embeding media from YouTube and Maps.

You can and should create your own policy to suit your site's needs. Here's a few examples adapted from MDN:

Example 1

This is the default policy. A web site administrator wants all content to come from the site's own origin (this excludes subdomains.)

csp = {
    'default-src': '\'self\''
}
talisman = Talisman(app, content_security_policy=csp)

Example 2

A web site administrator wants to allow content from a trusted domain and all its subdomains (it doesn't have to be the same domain that the CSP is set on.)

csp = {
    'default-src': [
        '\'self\'',
        '*.trusted.com'
    ]
}

Example 3

A web site administrator wants to allow users of a web application to include images from any origin in their own content, but to restrict audio or video media to trusted providers, and all scripts only to a specific server that hosts trusted code.

csp = {
    'default-src': '\'self\'',
    'img-src': '*',
    'media-src': [
        'media1.com',
        'media2.com',
    ],
    'script-src': 'userscripts.example.com'
}

In this example content is only permitted from the document's origin with the following exceptions:

  • Images may loaded from anywhere (note the * wildcard).
  • Media is only allowed from media1.com and media2.com (and not from subdomains of those sites).
  • Executable script is only allowed from userscripts.example.com.

Example 4

A web site administrator for an online banking site wants to ensure that all its content is loaded using SSL, in order to prevent attackers from eavesdropping on requests.

csp = {
    'default-src': 'https://onlinebanking.jumbobank.com'
}

The server only permits access to documents being loaded specifically over HTTPS through the single origin onlinebanking.jumbobank.com.

Example 5

A web site administrator of a web mail site wants to allow HTML in email, as well as images loaded from anywhere, but not JavaScript or other potentially dangerous content.

csp = {
    'default-src': [
        '\'self\'',
        '*.mailsite.com',
    ],
    'img-src': '*'
}

Note that this example doesn't specify a script-src; with the example CSP, this site uses the setting specified by the default-src directive, which means that scripts can be loaded only from the originating server.

Example 6

A web site administrator wants to allow embedded scripts (which might be generated dynamicially).

csp = {
    'default-src': '\'self\'',
    'script-src': '\'self\'',
}
talisman = Talisman(
    app,
    content_security_policy=csp,
    content_security_policy_nonce_in=['script-src']
)

The nonce needs to be added to the script tag in the template:

<script nonce="{{ csp_nonce() }}">
    //...
</script>

Note that the CSP directive (script-src in the example) to which the nonce-... source should be added needs to be defined explicitly.

Example 7

A web site adminstrator wants to override the CSP directives via an environment variable which doesn't support specifying the policy as a Python dictionary, e.g.:

export CSP_DIRECTIVES="default-src 'self'; image-src *"
python app.py

Then in the app code you can read the CSP directives from the environment:

import os
from flask_talisman import Talisman, DEFAULT_CSP_POLICY

talisman = Talisman(
    app,
    content_security_policy=os.environ.get("CSP_DIRECTIVES", DEFAULT_CSP_POLICY),
)

As you can see above the policy can be defined simply just like the official specification requires the HTTP header to be set: As a semicolon separated list of individual CSP directives.

Feature Policy

The default feature policy is empty, as this is the default expected behaviour. Note that the Feature Policy is still a draft https://wicg.github.io/feature-policy/ but is supported in some form in most browsers. Please note this has been renamed Permissions Policy in the latest draft by at this writing, browsers and this extension only supports the Feature-Policy HTTP Header name.

Geolocation Example

Disable access to Geolocation interface.

feature_policy = {
    'geolocation': '\'none\''
}
talisman = Talisman(app, feature_policy=feature_policy)

Disclaimer

This is not an official Google product, experimental or otherwise.

There is no silver bullet for web application security. Talisman can help, but security is more than just setting a few headers. Any public-facing web application should have a comprehensive approach to security.

Contributing changes

Licensing

flask-talisman's People

Contributors

asmith26 avatar clach04 avatar dhermes avatar heisendev avatar jelly avatar jezdez avatar kmturley avatar lipis avatar moser avatar nealedj avatar picardparis avatar rfinck avatar theacodes avatar tidalf avatar tunetheweb avatar valueshimoda avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-talisman's Issues

no-referrer ineffective

csp = {
  'referrer_policy': 'no-referrer',
}
Talisman(app, content_security_policy=csp)

still gives

still gives:
Referrer-Policy: strict-origin-when-cross-origin
in browser.

flask-talisman==0.7.0

Talisman causing Flask test_client post(), put(), or delete() requests to fail

I hope there is a parameter that I'm missing to fix this or I may be doing something wrong, but I don't believe that Flask Talisman works when making post(), put(), or delete() requests with the Flask test_client(). If that is the case, please consider this as a feature request if you deem it appropriate behavior for Flask Talisman.

I have observed that after adding Taliasman(app) to my Flask app I had to change all of my test cases to follow_redirects=True because apparently Talisman redirects every request. The problem is that it breaks all POST, PUT, and DELETE requests which get redirect and become GET requests.

Sample that shows problem

Given this simple Flask app: (app.py)

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/test1', methods=['GET'])
def get_test():
    return jsonify(message='200 OK'), 200

@app.route('/test2', methods=['POST'])
def create_test():
    return jsonify(message='201 Created'), 201

and these test cases: (test_case.py

from unittest import TestCase
from app import app

class TalismanTestCase(TestCase):
    def setUp(self):
        self.client = app.test_client()

    def test_get(self):
        resp = self.client.get('/test1')
        self.assertEqual(resp.status_code, 200)

    def test_post(self):
        resp = self.client.post('/test2')
        self.assertEqual(resp.status_code, 201)

When I run the tests, they execute correctly as expected:

$ python -m unittest -v test_case.py 
test_get (test_case.TalismanTestCase) ... ok
test_post (test_case.TalismanTestCase) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK

However when I add Talisman(app) to my code:

from flask import Flask, jsonify
from flask_talisman import Talisman

app = Flask(__name__)

Talisman(app)

... same code here ...

I get these test results:

python -m unittest -v test_case.py 
test_get (test_case.TalismanTestCase) ... FAIL
test_post (test_case.TalismanTestCase) ... FAIL

======================================================================
FAIL: test_get (test_case.TalismanTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/rofrano/tmp/talisman-test/test_case.py", line 13, in test_get
    self.assertEqual(resp.status_code, 200)
AssertionError: 302 != 200

======================================================================
FAIL: test_post (test_case.TalismanTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/rofrano/tmp/talisman-test/test_case.py", line 18, in test_post
    self.assertEqual(resp.status_code, 201)
AssertionError: 302 != 201

----------------------------------------------------------------------
Ran 2 tests in 0.006s

FAILED (failures=2)

So I tell the Flask test_client() to follow redirects by adding the following to my test cases:

    def test_get(self):
        resp = self.client.get('/test1', follow_redirects=True)
        self.assertEqual(resp.status_code, 200)

    def test_post(self):
        resp = self.client.post('/test2', follow_redirects=True)
        self.assertEqual(resp.status_code, 201)

and now I get the following test results:

$ python -m unittest -v test_case.py 
test_get (test_case.TalismanTestCase) ... ok
test_post (test_case.TalismanTestCase) ... FAIL

======================================================================
FAIL: test_post (test_case.TalismanTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/rofrano/tmp/talisman-test/test_case.py", line 18, in test_post
    self.assertEqual(resp.status_code, 201)
AssertionError: 405 != 201

----------------------------------------------------------------------
Ran 2 tests in 0.009s

FAILED (failures=1)

The first test case passed because the redirect performed a GET on the Location header that was returned but the second test failed because the POST was turned into a GET which returned a 405 Method Not Allowed. I don't know if this is something the Flask test_client() should fix but using curl I observed the same behavior.

Impact to developers

This makes it impossible to post form data in a test case when Talisman is being used. Do you consider this a bug or a limitation? If a limitation can I request that this capability be added? Thanks!

Nonce length is incorrect

flask-talisman uses the following code to generate a random nonce (as added in #17 and #18):

NONCE_LENGTH = 16
...
            flask.request.csp_nonce = get_random_string(NONCE_LENGTH)
...
try:
    import secrets
    get_random_string = secrets.token_urlsafe  # pragma: no cover

except ImportError:  # pragma: no cover
    import random
    import string
    rnd = random.SystemRandom()

    def get_random_string(length):
        allowed_chars = (
            string.ascii_lowercase +
            string.ascii_uppercase +
            string.digits)
        return ''.join(
            rnd.choice(allowed_chars)
            for _ in range(length))

That is it sets the NONCE_LENGTH to 16, then it tries to generate the NONCE (of length 16) using secrets.token_urlsafe and falls back to it's own random generator if that library is not available.

The problem is that secrets.token_urlsafe does not generate a value of that length:

Return a random URL-safe text string, containing nbytes random bytes. The text is Base64 encoded, so on average each byte results in approximately 1.3 characters.

This means we typically get a nonce of length 22 instead of one of length 16 that we asked for.

If you just use the manually defined function you do get a nonce of length 16 each time.

I always wondered by the nu validator complained about this, and finally got round to investigating:

Warning: Content-Security-Policy HTTP header: Bad content security policy: Invalid base64-value (should be multiple of 4 bytes: 22).

Other than that it doesn't seem to cause a problem. This StackOverflow answer seems to give a reason why, but not sure it really affects CSPs, but still... it's technically an incorrect base64 number so think we should at least consider fixing.

Do you need to use secrets.token_urlsafe given you are restricting the allowed characters to URL safe characters anyway?

I see this was added in #28 by as part of performance improvements but not sure if the overhead was that significant or if it is preferable to generate a valid base64 and take the slight performance hit? Especially given the other performance improvement in that pull request (which was to make the SystemRandom call once, rather than once per character) will have greatly reduced the performance anyway.

The alternative is to switch secrets.token_urlsafe with secrets.token_hex which will generate a 32-length nonce but with a reduced character set (0-9a-f) for an input of 16. Is the doubling in the length sufficient to make up from the greatly reduced character set? I'd guess not as going from 62-ish characters to just 16.

So I'd suggest removing the secrets.token_urlsafecall and instead using the fallback function.

@nealedj tagging you in this for any comments as the added the original code, and the performance improvement.

preload should not be a default setting

From README:

"Enables HSTS preloading. If you register your application with Google's HSTS preload list, Firefox and Chrome will never load your site over a non-secure connection."

See hstspreload.org opt in note - https://hstspreload.org/#opt-in - please remove preload from the default settings and expose it as a flag you can set.

Rules based policy switching

I have a need to use flask-admin in a CSP protected app. As flask-admin is package it is not practicable to apply a csp exception annotation to each of it's routes. In my current application I have this bit of code:

csp = {
    'default-src': [
        '\'self\'',
        '*.cloudflare.com',
        'storage.googleapis.com',
        'unpkg.com',
    ],
    'style-src': ["'self'", '*.cloudflare.com'],
    'img-src': '*',
}

# allow in line javascript and inline style for flask-admin
admin_csp = {
    'default-src': [
        '\'self\'',
        '*.cloudflare.com',
        'unpkg.com',
        'storage.googleapis.com',
        '\'unsafe-inline\'',
    ],
    'style-src': ["'self'", '*.cloudflare.com', '\'unsafe-inline\''],
    'img-src': '*',
}

talisman = Talisman()
admin = Admin(app=None, name=Config.TITLE, template_mode='bootstrap3')

def before_request_modifier():
    if request.path.startswith('/admin'):
        # disable strict talisman on the flask-admin pages
        talisman.content_security_policy = admin_csp
    else:
        talisman.content_security_policy = csp

I'd like to add a policy with an associated regex to match against the route it should be implemented against.

Is this sensible to add into talisman as a feature enhancement via pull request I'll author?

On using flask-talisman with application factory pattern

I tried the following in my app.py:

from flask_talisman import Talisman
from flask_main import create_app

app = create_app()
Talisman(app)

if __name__ == "main":
    app.run()

It still does not work. Any request coming to https:// returns SSL_ERROR_RX_RECORD_TOO_LONG. I've tried both commands to start the app: flask run and python app.py, nothing changes.

Per this issue #66, doing this in create_app won't work.

from flask import Flask
from flask_talisman import Talisman
from flask_main.configuration import Configuration

talisman = Talisman()

def create_app():
    app = Flask(__name__)
    app.config.from_object(Configuration)
    talisman.init_app(app)

Is there any way to make flask-talisman work with application factory pattern?

Per-view option: force_https

Hi, Thank you for great library!

Is there a way to change 'force_https' option per view?
Because I am using GAE and GAE's cron cannot handle HTTPS so I would like to exclude some endpoints.

aria-label /s does not show

When i activate flask-talisman with proper rules. Bootstrap5 aria-label icon dosent show on that buttons.

[FR] option to remove 'Server' from resp header

Just discovered there is a huge information leak in the Response Header:

Server: Werkzeug/0.0.1 Python/3.1.7

Please add option to drop this, or maybe to modify it.

Something like

@app.after_request
def add_header(response):
response.headers['Server'] = 'dummy'
return response

init_app() overrides kwargs passed via Talisman.__init__()

A common Flask application extension pattern is to first create the extension object, e.g.:

csp = {
    'default-src': '\'self\'',
    'script-src': '\'self\'',
}
talisman = Talisman(
    content_security_policy=csp,
    content_security_policy_nonce_in=['script-src']
)

and then register it with the Flask app in an application factory, e.g.:

def create_app(config_filename):
    app = Flask(__name__)
    app.config.from_pyfile(config_filename)

    from yourapplication.security import talisman
    talisman.init_app(app)

This way, the extension object is not initially bound to the app, and one extension object could be used for multiple apps.

However, when this pattern is used with Flask-Talisman, the Talisman constructor (i.e. Talisman.__init__), requires that a Flask app object is passed, otherwise the kwargs are ignored (https://github.com/GoogleCloudPlatform/flask-talisman/blob/master/flask_talisman/talisman.py#L57). I'm just curious, is there a particular reason for this design? If not, I would be happy to submit a PR to modify this behavior.

How to set frame-ancestors?

Hi,
newbie to CSP. I get the following error in my app which has iframes,

Refused to display 'http://localhost:8088/auth/' in a frame because an ancestor 
violates the following Content Security Policy directive: "frame-ancestors 'self'".

How do I specify the above link i.e. http://localhost:8088/auth/ to be valid and legit? Do I use Talisman or something else?
I tried the following but it did not work.

csp = {

    'default-src': ["'self'", "https://fonts.googleapis.com", 'https://fonts.gstatic.com', local_keycloak],
    ...
    'frame-ancestors': ["'self'", 'http://localhost:8088/auth/']
}

FYI: This project has been forked by the contributors

Since the primary maintainer of this repository is no longer at Google and there hasn't been any activity on this repository in over a year, myself and several contributors have forked the project over to wntrblm/flask-talisman. We will continue to maintain it there.

If you're a Googler with access to this repository, you are welcome to update the README to point to the community fork and archive this repository. Or don't, I'm a random person on the internet, not your manager. ๐Ÿ˜›

AttributeError: frame_options

Hello,

We have a Flask app with Talisman and we initialize the app by default values:

csp = {
        'default-src': '\'self\'',
        'img-src': '\'self\' data:',
        'media-src': [
            '*',
        ],
        'style-src': '\'unsafe-inline\' \'self\'',
        'script-src': '\'unsafe-inline\' \'self\'',
        'font-src' : '*'
    }
    Talisman(app, content_security_policy=csp)

But sometimes, we are not sure why, it's hard to reproduce we have the following error and stacktrace :asd

Traceback (most recent call last):
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask/app.py", line 2000, in __call__
    return self.wsgi_app(environ, start_response)
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask/app.py", line 1991, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask/app.py", line 1567, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask/app.py", line 1988, in wsgi_app
    response = self.full_dispatch_request()
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask/app.py", line 1643, in full_dispatch_request
    response = self.process_response(response)
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask/app.py", line 1862, in process_response
    response = handler(response)
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask_talisman/talisman.py", line 210, in _set_response_headers
    self._set_frame_options_headers(response.headers)
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/flask_talisman/talisman.py", line 217, in _set_frame_options_headers
    headers['X-Frame-Options'] = self.local_options.frame_options
  File "/root/19032018/asd/venv/lib/python3.6/site-packages/werkzeug/local.py", line 72, in __getattr__
    raise AttributeError(name)
AttributeError: frame_options

Can you help why this happens and why it happens at seemingly random times?
Talisman version is 0.4.1

Thanks in advance!

Talisman is not working when overwriting the process_response

In the following (minimal) app the CSP headers are not present when running the app.

from flask import Flask
from flask_talisman import Talisman


class MyFlask(Flask):
  def process_response(self, response):
    return response

# Replace MyFlask with Flask and the CPH headers will work
application = MyFlask(__name__)
talisman = Talisman(application)


@application.route('/')
def index():
  return 'Hello, world!'


if __name__ == '__main__':
  application.run(host='0.0.0.0', port=8080)

What would be the best way too modify the response and have the Talisman support? Is that a bug in Talisman? Am I holding it wrong?

Referrer-Policy release

Is there any chance to get a new release that includes the Referrer-Policy?
That would be super awesome โค๏ธ

README X-Download-Options conflict

The README says "Sets X-Download-Options to prevent file downloads opening for IE >= 8" in the bulleted list near the top. This conflicts with the description for the force_file_save option further down, which says it defaults to False. The code seems to agree with the latter.

Should not send x-content-security-policy by default

x-content-security-policy was previously supported by some browsers before content-security-policy was fully supported. It is poorly documented and does not support the full feature-set of the standardised content-security-policy.

IE11 is the only commonly in use browser now supporting this, however it only support the sandbox attribute.

We don't support X-Webkit-CSP which was the other older name used by Safari.

I think it's wrong to have this turned on by default and to use the same CSP as the standardised one. Website owners may not notice it's on by default, may assume it has same support as CSP, and will be less likely to test older browsers to see if it breaks.

I'd suggest removing it from the code completely as the standard CSP header is now well supported and standardised. We could also leave it there but in but with a default off status, but I'd really question the value of this. The alternative would be to be able to specify its setting separately to CSP but again I think it's of little value so I say get rid.

This would technically be a breaking change, in that anyone depending on this header will need to change their config to enable it. However, given its poor support, its complete lack of documentation and, the fact that CSP is used in preference to it anyway on any browser that supports that, I think the risk is low and it's preferable to leaving it in place.

Happy to submit a PR for this but wanted to open an issue for discussion first in case anyone disagreed.

//

This was a module conflict issue

Sha for inline style

I'm having a hard time getting certain inline styles (that are applied by 3rd party libraries) to pass the csp policy.

According to chrome dev-tools; the styles that are blocked have sha's like; sha256-0UkRl4R6Fr5miGC0EaTxGlIAO7+50jtyjTAIPPnWl7k=

And should be added as such to pass the csp; however when I add them exactly like that; they are not getting through.

To me they don't look like sha's at all, but (adjusting at the trailing =) they are something else; could somebody give me some pointers on how to get styles / scripts / functions to pass the csp via a sha?

Any good examples for instance?

Thanks a lot!

Should force_https allow localhost over HTTP?

Should _force_https allow localhost over HTTP? I terminate SSL at the reverse proxy so issuing curl commands doesn't work against the local WSGI server without adding -H 'X-Forwarded-Proto: https'. I don't see this as a big deal but seemed like a reasonable suggestion.

CSP header image-src should be img-src

In this example of your documentation image-src should actually be img-src:

csp = {
    'default-src': '\'self\'',
    'image-src': '*',
    'media-src': [
        'media1.com',
        'media2.com',
    ],
    'script-src': 'userscripts.example.com'
}

Also would be useful to show the way to pass in the settings e.g:

csp = {
    'default-src': '\'self\'',
    'img-src': '*',
    'media-src': [
        'media1.com',
        'media2.com',
    ],
    'script-src': 'userscripts.example.com'
}
talisman = Talisman(app, content_security_policy=csp)

I had to search the source to find how to pass it through :)

Cannot override CSP Nonce settings per route

Currently you can override the Content Security Policy per route like this:

@app.route('/example')
@talisman(
    content_security_policy = { 'default-src': ['\'self\''] }
)
def sitemap():
    ...

However it is not possible to similarly override whether a nonce is used:

@app.route('/example')
@talisman(
    content_security_policy = { 'default-src': ['\'self\''], 'style-src': ['\'self\'', '\'unsafe-inline\''] },
    content_security_policy_nonce_in=None
)
def sitemap():
    ...

This is necessary as sometimes necessary to set unsafe-inline and this is ignored when nonce is supplied, so if you want to use unsafe-inline for a particular route, then need to be able to turn the nonce off.

There is a workaround in that you can do this to delete the nonce before Talisma processes it:

@app.route('/example')
@talisman(
    content_security_policy = { 'default-src': ['\'self\''], 'style-src': ['\'self\'', '\'unsafe-inline\''] }
)
def sitemap():
    delattr(request,'csp_nonce')

But it means defining the override in two places so not the best. Plus it means you cannot remove the nonce from just style-src but leave it in place for script-src for example.

We should allow content_security_policy_nonce_in to be overridden similar to content_security_policy.

Invalid Response

Hello,
I am currently trying to open the webpage that's hosting my flask script but whenever I try opening the link google (and every other browser) just said

"This site cant provide a secure connection,
127.0.0.1 sent an invalid response.
ERR_SSL_PROTOCOL_ERROR"
and whenever I look back at my terminal it says

"
127.0.0.1 - - [25/Jan/2021 15:59:16] code 400, message Bad request version
"

and a bunch of UTF-8 encoded things which im not gonna show since it may be confidential (And it also has unknown characters so I cant decode).
I am sure that talisman is causing this because when I remove the Talisman(app) it works perfectly fine but when I do add it, the same error occurs until I remove it and restart my laptop.
My script:
`
from flask import Flask

from flask import render_template

from flask_talisman import Talisman

app = Flask(name)

Talisman(app)

@app.route('/')

def index():
return render_template("index.html")

if name == "main":
app.run(debug=True)

`

Google Analytics does not work

With Talisman applied to an app, Google Analytics does not work. I have confirmed functionality without using Talisman. I have used the following in my code:

In Python, applying the Talisman extension:

app = Flask(__name__)
...

SELF = "'self'"
csp = {
    'font-src': [
        'themes.googleusercontent.com',
        '*.gstatic.com',
    ],
    'img-src': [
        SELF,
        '*.bootstrapcdn.com',
        '*.googleapis.com',
    ],
    'style-src': [
        SELF,
        'stackpath.bootstrapcdn.com',
        'fonts.googleapis.com',
        'ajax.googleapis.com',
        '*.gstatic.com',
    ],
    'script-src': [
        SELF,
        'https://maxcdn.bootstrapcdn.com',
        'https://code.jquery.com',
        'https://www.google.com', 
        'ajax.googleapis.com',
        'www.googletagmanager.com',
        '*.googleanalytics.com',
        '*.google-analytics.com',
        '*',
    ],
    'frame-src': [
        SELF,
        'www.google.com',
        'www.youtube.com',
    ],
    'default-src': [
        SELF,
    ],
}

Talisman(app,
         content_security_policy=csp,
         content_security_policy_nonce_in=['script-src']
         )

In HTML immediately after <head>, and obviously using my analytics property ID value in both places marked as <ANALYTICS_ID>:

<script async src="https://www.googletagmanager.com/gtag/js?id=<ANALYTICS_ID>"</script>`
<script src="{{url_for('static', filename='js/analytics.js')}}" type="text/javascript"></script>

In statics/js/analytics.js, we have:

window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '<ANALYTICS_ID>');

Importantly, I am able to get Analytics to work by using the following csp (but this would not be wise for production, and largely defeat the purpose of Talisman):

csp = {'default-src': "*"}

To me, this suggests I am missing something in my csp that is required by Google Analytics, but I thought I included everything it should need. Perhaps I'm missing something, or there's an issue here?

Typo in img-src CSP directive example

In line 289 of README.rst

the example says image-src, and I'm pretty sure it should be img-src.

This commit over here has the fix:

pierce403@69bc2bc

If someone want's to just fix it quickly rather than me going through the whole legal paperwork + PR thing, that's fine :-)

How about renaming the package to flask-talisman?

For various reasons (conventions, OCD and more):


Not sure where I read it, but I think there is a convention for Flask extensions to name them with flask prefix.

For example one of my requirements.txt looks like this (which is pretty cool, because I know exactly which one has a dependency on Flask)

...
flask==0.11.1
flask-babel==0.11.1
flask-login==0.4.0
flask-oauthlib==0.9.3
flask-restful==0.3.5
flask-wtf==0.13.1
...
talisman==0.1.0
...

and directories look like this:

screen shot 2016-12-02 at 21 09 06


Even though there is no real conflict, there might be some confusion from the example in README.

screen shot 2016-12-02 at 21 21 06


Another reason is to avoid conflicts when importing modules instead of classes when we will have to import the talisman object to add extra rules to the endpoints. Because there is no better name to name this object, other than talisman.

If the coding convention is to import module (instead of from module import something) then the suggested example will have to be adjusted like this (I agree with Martijn Pieters):

from flask import Flask
import talisman as flask_talisman

app = Flask(__name__)
# Just to avoid having a different name of that variable for alter usages.
talisman = flask_talisman.Talisman(app) 

@app.route('/normal')
def normal():
    return 'Normal'

@app.route('/embeddable')
@talisman(frame_options=ALLOW_FROM, frame_options_allow_from='*')
def embeddable():
    return 'Embeddable'

I understand that this will break a bunch of things, but in a long run this will be better, plus the actual name of this repo is already flask-talisman, so we should be able to install it like the rest of the Flask extensions: pip install flask-talisman.

Per view options with blueprint pattern

It's possible I'm doing something wrong, but I do not believe the per-view option for Flask Talisman works with the Flask blueprint pattern. If that is the case, please consider this is as a feature request to have talisman work with views defined using Flask blueprints.

For example, your app may have a users/views.py file to define the views for a users blueprint. That file could look like the example below, but talisman is not processing the per-view CSP in my case.

from flask import Blueprint, render_template
from app import talisman

bp = Blueprint('users', __name__)

csp_user_profile = {'default-src': '*'}

@bp.route('/profile', methods=['GET'])
@talisman(content_security_policy=csp_user_profile)
def profile():
    return render_template('users/profile.html')

CSP report handler in example app

Hi, I just started using flask-talisman for enforcing HTTPS on my site, but now I'm eager to take advantage of CSP. A couple of questions about CSP reporting:

Regarding content_security_policy_report_uri, MDN says that it's deprecated despite being widely supported, in favor of report-to which isn't even supported anywhere. Is this considered a stable/supported feature of flask-talisman?

Assuming I set up a report-uri directive, I'm not sure what to do with it on the server side. Could you add a demo to the example app showing how to ingest CSP reports and do something useful with them, like logging to Stackdriver, etc?

Do you have any plans to provide a default built-in CSP report handler?

Add python 3.7 to nox testing and to setup.py

Add pythjon 3.7 to nox testing and to setup.py

Caveat. Flask running in python 3.7 emits a warning message

.nox/tests-3-7/lib/python3.7/site-packages/jinja2/utils.py:485
  /app/.nox/tests-3-7/lib/python3.7/site-packages/jinja2/utils.py:485: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    from collections import MutableMapping

.nox/tests-3-7/lib/python3.7/site-packages/jinja2/runtime.py:318
  /app/.nox/tests-3-7/lib/python3.7/site-packages/jinja2/runtime.py:318: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    from collections import Mapping

-- Docs: https://docs.pytest.org/en/latest/warnings.html

csp_nonce() is empty

Hi, I might be doing something really stupid but I can't find much documentation or examples, other than the main page on GitHub and the example about CSP.

My issue is that csp_nonce() is evaluating to an empty string. What am I doing wrong?

I include the relevant parts of my code (it is a much bigger project so I am trying to post only relevant parts, but if you need anything more, please let me know).

<!doctype html>
<html lang="en">
<head>
    [...]
    <link href="/static/css/main.68b8b5e7.chunk.css" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>[...] </script>
<script src="/static/js/2.389a3736.chunk.js" nonce="{{ csp_nonce() }}"></script>
<script src="/static/js/main.f39b6155.chunk.js" nonce="{{ csp_nonce() }}"></script>
</body>
</html>

While the CSP header does contain the nonce:

Content-Security-Policy | style-src 'self' https://fonts.googleapis.com 'nonce-XleICcqjjVeXsgKoEn6gLA'; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; script-src 'self' 'nonce-XleICcqjjVeXsgKoEn6gLA'

Flask app:

man = Talisman()
man.init_app(app, content_security_policy={
            "style-src": ["\'self\'", 'https://fonts.googleapis.com'],
            "font-src": ["\'self\'", 'https://fonts.gstatic.com'],
            "img-src": "'self' data:",
            "script-src":  ["\'self\'"],
        }, content_security_policy_nonce_in=['script-src', 'style-src']) 

@app.route('/')
def index():
       return render_template('index.html')

Page in the browser (notice how the nonce is empty):

<html lang="en">
<head>
    <link href="/static/css/main.68b8b5e7.chunk.css" rel="stylesheet">
<style data-jss="" data-meta="MuiGrid" nonce=""> [...]</style>
<style data-jss="" data-meta="MuiBox" nonce=""></style>
<style data-jss="" data-meta="MuiBox" nonce=""></style>
<style data-jss="" data-meta="makeStyles" nonce="">[...]</style>
</head>
<body>
<div id="root"></div>
<script nonce="">[...]</script>
<script src="/static/js/2.389a3736.chunk.js" nonce=""></script>
<script src="/static/js/main.f39b6155.chunk.js" nonce=""></script>
</body></html>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.