flask-secure-headers

Secure Header Wrapper for Flask Applications


Keywords
flask, security, header
License
MIT
Install
pip install flask-secure-headers==0.6

Documentation

flask-secure-headers

Secure Header Wrapper for Flask Applications. This is intended to be a simplified version of the Twitter SecureHeaders Ruby Gem

Installation

Install the extension with using pip, or easy_install. Pypi Link

$ pip install flask-secure-headers

Included 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]

Usage

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'

Configuration

Import

To load the headers into your flask app, import the function:

from flask_secure_headers.core import Secure_Headers
...
sh = Secure_Headers()
Policy Changes

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'
Policy Removal

A policy can also be removed by passing None as the value:

sh.rewrite({'CSP':None})
# there will be no CSP header
Policy parameter removal

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
Read Only

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})
Notes
  • 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

Creating the Wrapper

No Policy Updates

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():
  ...
With Policy Updates

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

Contact