Secure Header Wrapper for Flask Applications. This is intended to be a simplified version of the Twitter SecureHeaders Ruby Gem
Install the extension with using pip, or easy_install. Pypi Link
$ pip install flask-secure-headers
Header | Purpose | Default Policy |
---|---|---|
Content-Security-Policy (CSP) | Restrict rescources to prevent XSS/other attacks | default-src 'self'; report-uri /csp_report |
Strict-Transport-Security (HSTS) | Prevent downgrade attacks (https to http) | max-age=31536000; includeSubDomains |
X-Permitted-Cross-Domain-Policies | Restrict content loaded by flash | master-only |
X-Frame-Options | Prevent content from being framed and clickjacked | sameorigin |
X-XSS-Protection | IE 8+ XSS protection header | 1; mode=block |
X-Content-Type-Options | IE 9+ MIME-type verification | nosniff |
X-Download-Options | IE 10+ Prevent downloads from opening | noopen |
Public-Key-Pins (HPKP) | Associate host with expected CA or public key | max-age=5184000; includeSubDomains; report-uri=/hpkp_report [... no default pins] |
Each header policy is represented by a dict of paramaters. View default policies.
- Policies with a key/value pair are represented as {key:value}
- Ex: {'mode':'block'} becomes 'mode=block'
- Policies with just a string value are represented as {'value':parameter}
- Ex: {'value':'noopen'} becomes 'noopen'
- Policies with additional string values are represented as {value:Bool}
- Ex: {'max-age':1,'includeSubDomains':True,'preload':False} becomes 'max-age=1 includeSubDomains'
- CSP is represented as a list inside the dict {cspPolicy:[param,param]}.
- Ex: {'script-src':['self']} becomes "script-src 'self'"
- self, none, nonce-* ,sha*, unsafe-inline, etc are automatically encapsulated
- HPKP pins are represented by a list of dicts under the 'pins' paramter {'pins':[{hashType:hash}]}
- Ex: {'pins':[{'sha256':'1234'},{'sha256':'ABCD'}]} becomes 'pin-sha256=1234; pin-sha256=ABCD'
To load the headers into your flask app, import the function:
from flask_secure_headers.core import Secure_Headers
...
sh = Secure_Headers()
There are two methods to change the default policies that will persist throughout the application: update(), rewrite()
- Update will add to an existing policy
- Rewrite will replace a policy
To update/rewrite, pass a dict in of the desired values into the desired method:
""" update """
sh.update({'CSP':{'script-src':['self','code.jquery.com']}})
# Content-Security-Policy: script-src 'self' code.jquery.com; report-uri /csp_report; default-src 'self
sh.update(
{'X_Permitted_Cross_Domain_Policies':{'value':'all'}},
{'HPKP':{'pins':[{'sha256':'1234'}]}}
)
# X-Permitted-Cross-Domain-Policies: all
# Public-Key-Pins: max-age=5184000; includeSubDomains; report-uri=/hpkp_report; pin-sha256=1234
""" rewrite """
sh.rewrite({'CSP':{'default-src':['none']}})
# Content-Security-Policy: default-src 'none'
A policy can also be removed by passing None as the value:
sh.rewrite({'CSP':None})
# there will be no CSP header
For non-CSP headers that contain multiple paramaters (HSTS and X-XSS-Protection), any paramter other than the first can be removed by passing a value of False:
sh.update({'X-XSS-Protection':{'value':1,'mode':False}})
# will produce X-XSS-Protection: 1
sh.update({'HSTS':{'max-age':1,'includeSubDomains':True,'preload':False}})
# will produce Strict-Transport-Security: max-age=1; includeSubDomains
The HPKP and CSP Headers can be set to "-Read-Only" by passing "'read-only':True" into the policy dict. Examples:
sh.update({'CSP':{'script-src':['self','code.jquery.com']},'read-only':True})
sh.update({'HPKP':{'pins':[{'sha256':'1234'}]},'read-only':True})
- Header keys can be written using either '_' or '-', but are case sensitive
- Acceptable: 'X-XSS-Protection','X_XSS_Protection'
- Unacceptable: 'x-xss-protection'
- 3 headers are abreviated
- CSP = Content-Security-Policy
- HSTS = Strict-Transport-Security
- HPKP = Public-Key-Pins
Add the @sh.wrapper() decorator after your app.route(...) decorators for each route to create the headers based on the policy you have created using the update/remove methods (or the default policy if those were not used)
@app.route('/')
@sh.wrapper()
def index():
...
The wrapper() method can also be passed a dict in the same format as update/remove to change policies. These policy changes will only effect that specific route.
A couple notes:
- Changes here will always update the policy instead of rewrite
- CSP policy and HPKP pin lists will be merged, not overwritten. See comment below for example.
@app.route('/')
@sh.wrapper({
'CSP':{'script-src':['sha1-klsdjfkl232']},
'HPKP':{'pins':[{'sha256':'ABCD'}]}
})
def index():
...
# CSP will contain "script-src 'self' 'sha1-klsdjfkl232'"
# HPKP will contain "pins-sha256=1234; pins-sha256=ABCD;"
Policies can also be removed from a wrapper:
@app.route('/')
@sh.wrapper({'CSP':None,'X-XSS-Protection':None})
def index():
...
# this route will not include Content-Security-Policy or X-XSS-Protection Headers