Skip to content

Commit 35dc516

Browse files
author
cak
committed
framework and csp documentation updates
- Added documentation for using nonce with strict-dynamic in Content-Security-Policy. - Updated async frameworks to use async set_headers method. - Added CherryPy, Masonite, and Responder integration examples to the documentation.
1 parent 1bbdf46 commit 35dc516

4 files changed

Lines changed: 233 additions & 43 deletions

File tree

README.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,24 @@ In today's web landscape, security is paramount. **secure.py** is a lightweight
2828

2929
**secure.py** supports the following Python web frameworks:
3030

31-
| Framework | Documentation |
32-
| --------------------------------------------- | ---------------------------------------------------- |
33-
| [aiohttp](https://docs.aiohttp.org) | [Integration Guide](./docs/frameworks.md#aiohttp) |
34-
| [Bottle](https://bottlepy.org) | [Integration Guide](./docs/frameworks.md#bottle) |
35-
| [CherryPy](https://cherrypy.org) | [Integration Guide](./docs/frameworks.md#cherrypy) |
36-
| [Django](https://www.djangoproject.com) | [Integration Guide](./docs/frameworks.md#django) |
37-
| [Falcon](https://falconframework.org) | [Integration Guide](./docs/frameworks.md#falcon) |
38-
| [FastAPI](https://fastapi.tiangolo.com) | [Integration Guide](./docs/frameworks.md#fastapi) |
39-
| [Flask](http://flask.pocoo.org) | [Integration Guide](./docs/frameworks.md#flask) |
40-
| [Masonite](https://docs.masoniteproject.com/) | [Integration Guide](./docs/frameworks.md#masonite) |
41-
| [Morepath](https://morepath.readthedocs.io) | [Integration Guide](./docs/frameworks.md#morepath) |
42-
| [Pyramid](https://trypyramid.com) | [Integration Guide](./docs/frameworks.md#pyramid) |
43-
| [Quart](https://pgjones.gitlab.io/quart/) | [Integration Guide](./docs/frameworks.md#quart) |
44-
| [Responder](https://python-responder.org) | [Integration Guide](./docs/frameworks.md#responder) |
45-
| [Sanic](https://sanicframework.org) | [Integration Guide](./docs/frameworks.md#sanic) |
46-
| [Starlette](https://www.starlette.io/) | [Integration Guide](./docs/frameworks.md#starlette) |
47-
| [Tornado](https://www.tornadoweb.org/) | [Integration Guide](./docs/frameworks.md#tornado) |
48-
| [TurboGears](https://turbogears.org/) | [Integration Guide](./docs/frameworks.md#turbogears) |
31+
| Framework | Documentation |
32+
| ----------------------------------------------------- | ---------------------------------------------------- |
33+
| [aiohttp](https://docs.aiohttp.org) | [Integration Guide](./docs/frameworks.md#aiohttp) |
34+
| [Bottle](https://bottlepy.org) | [Integration Guide](./docs/frameworks.md#bottle) |
35+
| [CherryPy](https://cherrypy.dev/) | [Integration Guide](./docs/frameworks.md#cherrypy) |
36+
| [Django](https://www.djangoproject.com) | [Integration Guide](./docs/frameworks.md#django) |
37+
| [Falcon](https://falconframework.org) | [Integration Guide](./docs/frameworks.md#falcon) |
38+
| [FastAPI](https://fastapi.tiangolo.com) | [Integration Guide](./docs/frameworks.md#fastapi) |
39+
| [Flask](http://flask.pocoo.org) | [Integration Guide](./docs/frameworks.md#flask) |
40+
| [Masonite](https://docs.masoniteproject.com/) | [Integration Guide](./docs/frameworks.md#masonite) |
41+
| [Morepath](https://morepath.readthedocs.io) | [Integration Guide](./docs/frameworks.md#morepath) |
42+
| [Pyramid](https://trypyramid.com) | [Integration Guide](./docs/frameworks.md#pyramid) |
43+
| [Quart](https://quart.palletsprojects.com/en/latest/) | [Integration Guide](./docs/frameworks.md#quart) |
44+
| [Responder](https://responder.kennethreitz.org/) | [Integration Guide](./docs/frameworks.md#responder) |
45+
| [Sanic](https://sanicframework.org) | [Integration Guide](./docs/frameworks.md#sanic) |
46+
| [Starlette](https://www.starlette.io/) | [Integration Guide](./docs/frameworks.md#starlette) |
47+
| [Tornado](https://www.tornadoweb.org/) | [Integration Guide](./docs/frameworks.md#tornado) |
48+
| [TurboGears](https://turbogears.org/) | [Integration Guide](./docs/frameworks.md#turbogears) |
4949

5050
---
5151

@@ -236,7 +236,7 @@ secure_headers = Secure.with_default_headers()
236236
@app.middleware("http")
237237
async def add_security_headers(request, call_next):
238238
response = await call_next(request)
239-
secure_headers.set_headers(response)
239+
await secure_headers.set_headers_async(response)
240240
return response
241241

242242

docs/README.md

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,24 @@ For detailed configuration options, see the [Configuration Guide](./configuratio
3232

3333
Secure Headers is compatible with many popular Python web frameworks. Below are the integration guides for each supported framework, consolidated in the [Frameworks Integration Guide](./frameworks.md):
3434

35-
| Framework | Documentation |
36-
| ------------------------------------------- | ----------------------------------------------- |
37-
| [aiohttp](https://docs.aiohttp.org) | [Integration Guide](./frameworks.md#aiohttp) |
38-
| [Bottle](https://bottlepy.org) | [Integration Guide](./frameworks.md#bottle) |
39-
| [Django](https://www.djangoproject.com) | [Integration Guide](./frameworks.md#django) |
40-
| [Falcon](https://falconframework.org) | [Integration Guide](./frameworks.md#falcon) |
41-
| [FastAPI](https://fastapi.tiangolo.com) | [Integration Guide](./frameworks.md#fastapi) |
42-
| [Flask](https://flask.palletsprojects.com/) | [Integration Guide](./frameworks.md#flask) |
43-
| [Pyramid](https://trypyramid.com) | [Integration Guide](./frameworks.md#pyramid) |
44-
| [Quart](https://pgjones.gitlab.io/quart/) | [Integration Guide](./frameworks.md#quart) |
45-
| [Sanic](https://sanicframework.org) | [Integration Guide](./frameworks.md#sanic) |
46-
| [Starlette](https://www.starlette.io/) | [Integration Guide](./frameworks.md#starlette) |
47-
| [Tornado](https://www.tornadoweb.org/) | [Integration Guide](./frameworks.md#tornado) |
48-
| [TurboGears](https://turbogears.org/) | [Integration Guide](./frameworks.md#turbogears) |
49-
| [Web2py](http://www.web2py.com/) | [Integration Guide](./frameworks.md#web2py) |
50-
| [Morepath](https://morepath.readthedocs.io) | [Integration Guide](./frameworks.md#morepath) |
35+
| Framework | Documentation |
36+
| ----------------------------------------------------- | ----------------------------------------------- |
37+
| [aiohttp](https://docs.aiohttp.org) | [Integration Guide](./frameworks.md#aiohttp) |
38+
| [Bottle](https://bottlepy.org) | [Integration Guide](./frameworks.md#bottle) |
39+
| [CherryPy](https://cherrypy.dev/) | [Integration Guide](./frameworks.md#cherrypy) |
40+
| [Django](https://www.djangoproject.com) | [Integration Guide](./frameworks.md#django) |
41+
| [Falcon](https://falconframework.org) | [Integration Guide](./frameworks.md#falcon) |
42+
| [FastAPI](https://fastapi.tiangolo.com) | [Integration Guide](./frameworks.md#fastapi) |
43+
| [Flask](http://flask.pocoo.org) | [Integration Guide](./frameworks.md#flask) |
44+
| [Masonite](https://docs.masoniteproject.com/) | [Integration Guide](./frameworks.md#masonite) |
45+
| [Morepath](https://morepath.readthedocs.io) | [Integration Guide](./frameworks.md#morepath) |
46+
| [Pyramid](https://trypyramid.com) | [Integration Guide](./frameworks.md#pyramid) |
47+
| [Quart](https://quart.palletsprojects.com/en/latest/) | [Integration Guide](./frameworks.md#quart) |
48+
| [Responder](https://responder.kennethreitz.org/) | [Integration Guide](./frameworks.md#responder) |
49+
| [Sanic](https://sanicframework.org) | [Integration Guide](./frameworks.md#sanic) |
50+
| [Starlette](https://www.starlette.io/) | [Integration Guide](./frameworks.md#starlette) |
51+
| [Tornado](https://www.tornadoweb.org/) | [Integration Guide](./frameworks.md#tornado) |
52+
| [TurboGears](https://turbogears.org/) | [Integration Guide](./frameworks.md#turbogears) |
5153

5254
If your framework is not listed here, Secure Headers can likely still be integrated. Refer to the [Custom Framework Integration Guide](./frameworks.md#custom-frameworks) for general integration tips.
5355

@@ -100,9 +102,6 @@ Secure Headers supports many critical HTTP security headers. Below is a list of
100102
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/)
101103
Learn about security best practices for HTTP headers from OWASP.
102104

103-
- [Hardenize - HTTP Security Headers](https://www.hardenize.com/blog/https-security-headers)
104-
A guide on the importance of HTTP security headers and how to use them effectively.
105-
106105
- [Mozilla Observatory](https://observatory.mozilla.org/)
107106
A security tool to check the implementation of security headers on your site.
108107

docs/frameworks.md

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66

77
- [aiohttp](#aiohttp)
88
- [Bottle](#bottle)
9+
- [CherryPy](#cherrypy)
910
- [Django](#django)
1011
- [Falcon](#falcon)
1112
- [FastAPI](#fastapi)
1213
- [Flask](#flask)
14+
- [Masonite](#masonite)
1315
- [Morepath](#morepath)
1416
- [Pyramid](#pyramid)
1517
- [Quart](#quart)
18+
- [Responder](#responder)
1619
- [Sanic](#sanic)
1720
- [Starlette](#starlette)
1821
- [Tornado](#tornado)
@@ -41,7 +44,7 @@ async def handle(request):
4144
@web.middleware
4245
async def add_security_headers(request, handler):
4346
response = await handler(request)
44-
secure_headers.set_headers(response)
47+
await secure_headers.set_headers_async(response)
4548
return response
4649

4750

@@ -82,6 +85,32 @@ run(app, host="localhost", port=8080)
8285

8386
---
8487

88+
## CherryPy
89+
90+
**[CherryPy](https://cherrypy.dev)** is a minimalist, object-oriented web framework that allows developers to build web applications in a way similar to building other Python applications.
91+
92+
```python
93+
import cherrypy
94+
95+
from secure import Secure
96+
97+
secure_headers = Secure.with_default_headers()
98+
99+
100+
class HelloWorld:
101+
@cherrypy.expose
102+
def index(self):
103+
response = b"Hello, world"
104+
cherrypy.response.headers.update(secure_headers.headers)
105+
return response
106+
107+
108+
if __name__ == "__main__":
109+
cherrypy.quickstart(HelloWorld())
110+
```
111+
112+
---
113+
85114
## Django
86115

87116
**[Django](https://www.djangoproject.com)** is a high-level Python web framework that encourages rapid development and clean, pragmatic design. It's widely used and has a rich ecosystem.
@@ -142,7 +171,7 @@ secure_headers = Secure.with_default_headers()
142171
@app.middleware("http")
143172
async def add_security_headers(request, call_next):
144173
response = await call_next(request)
145-
secure_headers.set_headers(response)
174+
await secure_headers.set_headers_async(response)
146175
return response
147176

148177

@@ -177,6 +206,39 @@ def home():
177206
return "Hello, world"
178207

179208

209+
if __name__ == "__main__":
210+
app.run()
211+
```
212+
213+
---
214+
215+
## Masonite
216+
217+
**[Masonite](https://docs.masoniteproject.com/)** is a modern and developer-friendly Python web framework. It is designed for fast development, easy database management, and an MVC architecture.
218+
219+
```python
220+
from masonite.foundation import Application
221+
from masonite.request import Request
222+
from masonite.response import Response
223+
224+
from secure import Secure
225+
226+
app = Application()
227+
228+
# Configure default secure headers
229+
secure_headers = Secure.with_default_headers()
230+
231+
232+
def add_security_headers(response: Response):
233+
secure_headers.set_headers(response)
234+
return response
235+
236+
237+
@app.route("/")
238+
def home(request: Request, response: Response):
239+
return add_security_headers(response.view("Hello, world"))
240+
241+
180242
if __name__ == "__main__":
181243
app.run()
182244
```
@@ -253,7 +315,7 @@ if __name__ == "__main__":
253315

254316
## Quart
255317

256-
**[Quart](https://pgjones.gitlab.io/quart/)** is an async Python web framework and is a drop-in replacement for Flask, offering the same API but with async capabilities.
318+
**[Quart](https://quart.palletsprojects.com/en/latest/)** is an async Python web framework and is a drop-in replacement for Flask, offering the same API but with async capabilities.
257319

258320
```python
259321
from quart import Quart, Response
@@ -266,7 +328,7 @@ secure_headers = Secure.with_default_headers()
266328

267329
@app.after_request
268330
async def add_security_headers(response: Response):
269-
secure_headers.set_headers(response)
331+
await secure_headers.set_headers_async(response)
270332
return response
271333

272334

@@ -280,6 +342,31 @@ app.run()
280342

281343
---
282344

345+
## Responder
346+
347+
**[Responder](https://responder.kennethreitz.org)** is a web framework for quickly building APIs. It combines speed and developer-friendly features with the power of Starlette and ASGI.
348+
349+
```python
350+
import responder
351+
352+
from secure import Secure
353+
354+
api = responder.API()
355+
secure_headers = Secure.with_default_headers()
356+
357+
358+
@api.route("/")
359+
async def home(req, resp):
360+
resp.text = "Hello, world"
361+
await secure_headers.set_headers_async(resp)
362+
363+
364+
if __name__ == "__main__":
365+
api.run()
366+
```
367+
368+
---
369+
283370
## Sanic
284371

285372
**[Sanic](https://sanicframework.org)** is a Python 3.7+ web server and web framework that's written to go fast. It allows for the handling of asynchronous requests.
@@ -330,7 +417,7 @@ async def homepage(request):
330417
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
331418
async def dispatch(self, request, call_next):
332419
response = await call_next(request)
333-
secure_headers.set_headers(response)
420+
await secure_headers.set_headers_async(response)
334421
return response
335422

336423

docs/headers/content_security_policy.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,110 @@ This can then be applied as part of your Secure headers configuration.
5454
secure_headers = Secure(csp=csp)
5555
```
5656

57+
## Using Nonce and `strict-dynamic` in Content-Security-Policy
58+
59+
Content Security Policy (CSP) allows you to enhance the security of your web application by specifying the allowed sources of content. Using a nonce with the `strict-dynamic` directive improves protection against Cross-Site Scripting (XSS) attacks by dynamically allowing only scripts that are explicitly marked with a nonce. This is especially useful when you want to allow some inline scripts while ensuring only those scripts and their dynamically loaded dependencies are executed.
60+
61+
### Example: Using Nonce with `strict-dynamic`
62+
63+
Here’s how to set a CSP with a nonce and `strict-dynamic` using `secure.py`:
64+
65+
```python
66+
import uuid
67+
68+
from flask import Flask, Response
69+
70+
from secure import ContentSecurityPolicy, Secure
71+
72+
app = Flask(__name__)
73+
74+
75+
def generate_nonce():
76+
# Create a unique nonce for each request
77+
return uuid.uuid4().hex
78+
79+
80+
secure_headers = Secure(
81+
csp=ContentSecurityPolicy()
82+
.default_src("'self'")
83+
.script_src(ContentSecurityPolicy().nonce(generate_nonce()), "'strict-dynamic'")
84+
.style_src("'self'")
85+
.object_src("'none'")
86+
)
87+
88+
89+
@app.after_request
90+
def add_security_headers(response: Response):
91+
# Apply the security headers with a new nonce for each response
92+
nonce = generate_nonce()
93+
secure_headers.set_headers(response)
94+
# Ensure the nonce is passed in the CSP for inline scripts
95+
response.headers["Content-Security-Policy"] = response.headers[
96+
"Content-Security-Policy"
97+
].replace("'nonce-'", f"'nonce-{nonce}'")
98+
return response
99+
100+
101+
@app.route("/")
102+
def home():
103+
# Example HTML with an inline script using the nonce
104+
nonce = generate_nonce()
105+
html = f"""
106+
<html>
107+
<head>
108+
<title>Secure.py with CSP</title>
109+
<script nonce='{nonce}'>
110+
console.log('This script is allowed because it has a nonce!');
111+
</script>
112+
</head>
113+
<body>
114+
Hello, world!
115+
</body>
116+
</html>
117+
"""
118+
return Response(html, content_type="text/html")
119+
120+
121+
if __name__ == "__main__":
122+
app.run()
123+
```
124+
125+
### Example Output Headers
126+
127+
This example sets the following HTTP headers on the response:
128+
129+
```http
130+
Content-Security-Policy: default-src 'self'; script-src 'nonce-<generated-nonce>' 'strict-dynamic'; style-src 'self'; object-src 'none'
131+
```
132+
133+
```html
134+
<html>
135+
<head>
136+
<title>Secure.py with CSP</title>
137+
<script nonce="<generated-nonce>">
138+
console.log("This script is allowed because it has a nonce!");
139+
</script>
140+
</head>
141+
<body>
142+
Hello, world!
143+
</body>
144+
</html>
145+
```
146+
147+
- **`default-src 'self'`**: Only content from the same origin is allowed by default.
148+
- **`script-src 'nonce-<generated-nonce>' 'strict-dynamic'`**: Only scripts with the nonce or dynamically loaded by trusted scripts are allowed.
149+
- **`style-src 'self'`**: Only CSS from the same origin is allowed.
150+
- **`object-src 'none'`**: The `<object>` element is disabled for additional security.
151+
152+
### Why Use `strict-dynamic`?
153+
154+
The `strict-dynamic` directive allows the CSP to trust dynamically created scripts as long as they are loaded by scripts with a nonce or from a trusted source. This reduces the need to explicitly list external sources in the CSP, improving security by ensuring only scripts with a nonce or trusted dynamic scripts are executed.
155+
156+
For more details on `Content-Security-Policy` and the `nonce` attribute, refer to the following resources:
157+
158+
- [MDN Web Docs: Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
159+
- [OWASP Secure Headers Project: Content-Security-Policy](https://owasp.org/www-project-secure-headers/#content-security-policy)
160+
57161
## **Attribution**
58162

59163
This library implements security recommendations from trusted sources:

0 commit comments

Comments
 (0)