plain 0.82.0__py3-none-any.whl → 0.83.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of plain might be problematic. Click here for more details.
- plain/CHANGELOG.md +11 -0
- plain/http/README.md +59 -0
- plain/utils/html.py +12 -7
- {plain-0.82.0.dist-info → plain-0.83.0.dist-info}/METADATA +1 -1
- {plain-0.82.0.dist-info → plain-0.83.0.dist-info}/RECORD +8 -8
- {plain-0.82.0.dist-info → plain-0.83.0.dist-info}/WHEEL +0 -0
- {plain-0.82.0.dist-info → plain-0.83.0.dist-info}/entry_points.txt +0 -0
- {plain-0.82.0.dist-info → plain-0.83.0.dist-info}/licenses/LICENSE +0 -0
plain/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# plain changelog
|
|
2
2
|
|
|
3
|
+
## [0.83.0](https://github.com/dropseed/plain/releases/plain@0.83.0) (2025-10-29)
|
|
4
|
+
|
|
5
|
+
### What's changed
|
|
6
|
+
|
|
7
|
+
- Added comprehensive Content Security Policy (CSP) documentation explaining how to use nonces with inline scripts and styles ([784f3dd972](https://github.com/dropseed/plain/commit/784f3dd972))
|
|
8
|
+
- The `json_script` utility function now accepts an optional `nonce` parameter for CSP-compliant inline JSON scripts ([784f3dd972](https://github.com/dropseed/plain/commit/784f3dd972))
|
|
9
|
+
|
|
10
|
+
### Upgrade instructions
|
|
11
|
+
|
|
12
|
+
- Any `|json_script` usages need to make sure the second argument is a nonce, not a custom encoder (which is now third)
|
|
13
|
+
|
|
3
14
|
## [0.82.0](https://github.com/dropseed/plain/releases/plain@0.82.0) (2025-10-29)
|
|
4
15
|
|
|
5
16
|
### What's changed
|
plain/http/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**HTTP request and response handling.**
|
|
4
4
|
|
|
5
5
|
- [Overview](#overview)
|
|
6
|
+
- [Content Security Policy (CSP)](#content-security-policy-csp)
|
|
6
7
|
|
|
7
8
|
## Overview
|
|
8
9
|
|
|
@@ -28,3 +29,61 @@ class ExampleView(View):
|
|
|
28
29
|
|
|
29
30
|
return response
|
|
30
31
|
```
|
|
32
|
+
|
|
33
|
+
## Content Security Policy (CSP)
|
|
34
|
+
|
|
35
|
+
Plain includes built-in support for Content Security Policy (CSP) through nonces, allowing you to use strict CSP policies without `'unsafe-inline'`.
|
|
36
|
+
|
|
37
|
+
Each request generates a unique cryptographically secure nonce available via [`request.csp_nonce`](request.py#Request.csp_nonce):
|
|
38
|
+
|
|
39
|
+
### Configuring CSP Headers
|
|
40
|
+
|
|
41
|
+
Set `DEFAULT_RESPONSE_HEADERS` as a callable function to generate dynamic CSP headers with nonces:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# app/settings.py
|
|
45
|
+
def DEFAULT_RESPONSE_HEADERS(request):
|
|
46
|
+
"""
|
|
47
|
+
Dynamic response headers with CSP nonces.
|
|
48
|
+
"""
|
|
49
|
+
nonce = request.csp_nonce
|
|
50
|
+
return {
|
|
51
|
+
"Content-Security-Policy": (
|
|
52
|
+
f"default-src 'self'; "
|
|
53
|
+
f"script-src 'self' 'nonce-{nonce}'; "
|
|
54
|
+
f"style-src 'self' 'nonce-{nonce}'; "
|
|
55
|
+
f"img-src 'self' data:; "
|
|
56
|
+
f"font-src 'self'; "
|
|
57
|
+
f"connect-src 'self'; "
|
|
58
|
+
f"frame-ancestors 'self'; "
|
|
59
|
+
f"base-uri 'self'; "
|
|
60
|
+
f"form-action 'self'"
|
|
61
|
+
),
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use tools like [Google's CSP Evaluator](https://csp-evaluator.withgoogle.com/) to analyze your CSP policy and identify potential security issues or misconfigurations.
|
|
66
|
+
|
|
67
|
+
### Using Nonces in Templates
|
|
68
|
+
|
|
69
|
+
Add the nonce attribute to inline scripts and styles in your templates:
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<!-- Inline script with nonce -->
|
|
73
|
+
<script nonce="{{ request.csp_nonce }}">
|
|
74
|
+
console.log("This script is allowed by CSP");
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<!-- Inline style with nonce -->
|
|
78
|
+
<style nonce="{{ request.csp_nonce }}">
|
|
79
|
+
.example { color: red; }
|
|
80
|
+
</style>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
External scripts and stylesheets loaded from `'self'` don't need nonces:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<!-- External scripts/styles work with 'self' directive -->
|
|
87
|
+
<script src="/assets/app.js"></script>
|
|
88
|
+
<link rel="stylesheet" href="/assets/app.css">
|
|
89
|
+
```
|
plain/utils/html.py
CHANGED
|
@@ -34,25 +34,30 @@ _json_script_escapes = {
|
|
|
34
34
|
def json_script(
|
|
35
35
|
value: Any,
|
|
36
36
|
element_id: str | None = None,
|
|
37
|
+
nonce: str = "",
|
|
37
38
|
encoder: type[json.JSONEncoder] | None = None,
|
|
38
39
|
) -> SafeString:
|
|
39
40
|
"""
|
|
40
41
|
Escape all the HTML/XML special characters with their unicode escapes, so
|
|
41
42
|
value is safe to be output anywhere except for inside a tag attribute. Wrap
|
|
42
43
|
the escaped JSON in a script tag.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
value: The data to encode as JSON
|
|
47
|
+
element_id: Optional ID attribute for the script tag
|
|
48
|
+
nonce: Optional CSP nonce for inline script tags
|
|
49
|
+
encoder: Optional custom JSON encoder class
|
|
43
50
|
"""
|
|
44
51
|
from plain.json import PlainJSONEncoder
|
|
45
52
|
|
|
46
53
|
json_str = json.dumps(value, cls=encoder or PlainJSONEncoder).translate(
|
|
47
54
|
_json_script_escapes
|
|
48
55
|
)
|
|
49
|
-
if element_id
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
args = (mark_safe(json_str),)
|
|
55
|
-
return format_html(template, *args)
|
|
56
|
+
id_attr = f' id="{element_id}"' if element_id else ""
|
|
57
|
+
nonce_attr = f' nonce="{nonce}"' if nonce else ""
|
|
58
|
+
return mark_safe(
|
|
59
|
+
f'<script{id_attr}{nonce_attr} type="application/json">{json_str}</script>'
|
|
60
|
+
)
|
|
56
61
|
|
|
57
62
|
|
|
58
63
|
def conditional_escape(text: Any) -> SafeString | str:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
plain/AGENTS.md,sha256=As6EFSWWHJ9lYIxb2LMRqNRteH45SRs7a_VFslzF53M,1046
|
|
2
|
-
plain/CHANGELOG.md,sha256=
|
|
2
|
+
plain/CHANGELOG.md,sha256=xY-6RVqtfACU0z3588U3ALK_B3tuaNuBbp1d2J8qFf8,33715
|
|
3
3
|
plain/README.md,sha256=VvzhXNvf4I6ddmjBP9AExxxFXxs7RwyoxdgFm-W5dsg,4072
|
|
4
4
|
plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
|
|
5
5
|
plain/debug.py,sha256=C2OnFHtRGMrpCiHSt-P2r58JypgQZ62qzDBpV4mhtFM,855
|
|
@@ -56,7 +56,7 @@ plain/forms/boundfield.py,sha256=PEquPRn1BoVd4ZkIin8tjkBzRDMv-41wO8qHV0S1shg,196
|
|
|
56
56
|
plain/forms/exceptions.py,sha256=MuMUF-33Qsc_HWk5zI3rcWzdvXzQbEOKAZvJKkbrL58,320
|
|
57
57
|
plain/forms/fields.py,sha256=GQSTI6-eaHivIXj3TQSw2LpyWMbN0NZ-SHjUO_oxqeA,37132
|
|
58
58
|
plain/forms/forms.py,sha256=GnJWzjIXOPByfrdiqjjo4xdKdkBw1gBD6Hq4mg73gHQ,11259
|
|
59
|
-
plain/http/README.md,sha256=
|
|
59
|
+
plain/http/README.md,sha256=8FLsZ12Gx6lLa0mFMDcqWYetM6ysCvfHMZLLh4dAAVI,2611
|
|
60
60
|
plain/http/__init__.py,sha256=gUTIGh-GbSIlh3SP-Db-XAOX4P_j2hh2445w8Cm-zKQ,1032
|
|
61
61
|
plain/http/cookie.py,sha256=x13G3LIr0jxnPK1NQRptmi0DrAq9PsivQnQTm4LKaW0,2191
|
|
62
62
|
plain/http/middleware.py,sha256=TPs585IIFjgp-5uUAJtIoigH6uwTS3FJqwFSsQdayd4,960
|
|
@@ -167,7 +167,7 @@ plain/utils/duration.py,sha256=QBFh-iLvRXuw0N0UUdFqabeJvZVqkgFME_c2gRSLwhI,1340
|
|
|
167
167
|
plain/utils/encoding.py,sha256=40siECldXUFyZ_IkyQCeOkyQuXb7i4Rpys3nFWFHnuc,4482
|
|
168
168
|
plain/utils/functional.py,sha256=HqRXyM6R3Sl9IbhGl6t-DdFwtfr05IYmq7rgFCWV_2U,14650
|
|
169
169
|
plain/utils/hashable.py,sha256=1Kh_SFxsaR2d3xQMNEtHb4eKSHugtNt66iZ3ZvKjt50,811
|
|
170
|
-
plain/utils/html.py,sha256=
|
|
170
|
+
plain/utils/html.py,sha256=ctgzowSi_NkytPOw3FvyqEIXgOE-52trkTFPjiGvGVg,4202
|
|
171
171
|
plain/utils/http.py,sha256=P7dkgWpC28Bm-_VNuj57VN8j8AdbaDu1CKxoggwBpKo,5847
|
|
172
172
|
plain/utils/inspect.py,sha256=wbWDAiVa4MW4lwJZz-mvU8Db-I0Nq5DhHh9ew5w81EE,1480
|
|
173
173
|
plain/utils/ipv6.py,sha256=TLOQVN0aqKGM4eS_HwarTgO-bGIql-k5AipDPgtQdCA,1352
|
|
@@ -188,8 +188,8 @@ plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
|
|
|
188
188
|
plain/views/objects.py,sha256=5y0PoPPo07dQTTcJ_9kZcx0iI1O7regsooAIK4VqXQ0,5579
|
|
189
189
|
plain/views/redirect.py,sha256=mIpSAFcaEyeLDyv4Fr6g_ektduG4Wfa6s6L-rkdazmM,2154
|
|
190
190
|
plain/views/templates.py,sha256=9LgDMCv4C7JzLiyw8jHF-i4350ukwgixC_9y4faEGu0,1885
|
|
191
|
-
plain-0.
|
|
192
|
-
plain-0.
|
|
193
|
-
plain-0.
|
|
194
|
-
plain-0.
|
|
195
|
-
plain-0.
|
|
191
|
+
plain-0.83.0.dist-info/METADATA,sha256=hiux2R0QdqLHhIroLLNQdEyMh4ZnKnfzJod0bGsGNw0,4516
|
|
192
|
+
plain-0.83.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
193
|
+
plain-0.83.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
|
|
194
|
+
plain-0.83.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
|
195
|
+
plain-0.83.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|