Add a Content Security Policy as defense in depth
Severity: Low
OWASP category: A02:2025 - Security Misconfiguration
Summary
The production site does not send a Content-Security-Policy header. A tested, enforcing policy would restrict the scripts and other resources that browsers may load if unsafe HTML is published.
The project renders stored HTML with Django's safe filter. CSP would provide a fallback if unsafe content passes the publishing workflow, but it would not replace server-side HTML sanitization.
Evidence
The response from https://python.ph/ did not contain a Content-Security-Policy or Content-Security-Policy-Report-Only header.
Stored HTML is rendered with safe in these public templates:
app/landing/templates/landing/header/index.html
app/landing/templates/landing/our_aim.html
app/landing/templates/landing/why_python.html
app/landing/templates/landing/what_we_do/index.html
app/landing/templates/landing/code_of_conduct.html
app/landing/templates/landing/mailing_list.html
app/events/templates/events/event_detail.html
app/jobs/templates/jobs/detail.html
Sections and events can be changed through Django admin or data imports. An authenticated user can submit a job description, but it is not public until the job is approved. These fields use tinymce.models.HTMLField, and the project does not sanitize their HTML on the server.
The lock file resolves Django 6.0.7. Django 6 has built-in CSP support, but config/settings.py does not enable it.
Reason for the hardening change
The safe filter disables Django's normal output escaping. If script-capable HTML is stored and published, the browser has no site policy blocking inline JavaScript, event handlers, or scripts loaded from an unapproved origin.
An enforcing CSP can reduce the effect of such a publishing mistake. The missing header is not an exploitable vulnerability by itself, and report-only mode does not block content.
Recommended change
Add Django's CSP middleware to MIDDLEWARE:
"django.middleware.csp.ContentSecurityPolicyMiddleware",
Define the initial policy in SECURE_CSP_REPORT_ONLY. Review at least default-src, script-src, style-src, img-src, font-src, connect-src, object-src, base-uri, form-action, and frame-ancestors.
The policy must account for Google Fonts, the configured R2 media origin, the Mailchimp subscription form, and the scripts used by Django admin, Unfold, and TinyMCE.
Move inline JavaScript to static files where practical. If an inline <script> must remain, add CSP.NONCE to script-src, add django.template.context_processors.csp, and set nonce="{{ csp_nonce }}" on the script element. Replace the onclick handler in the event detail template with an event listener because a script nonce does not authorize inline event handlers.
After testing public, authentication, and admin pages, move the reviewed policy to SECURE_CSP so Django sends an enforcing header. If reports are to be collected, add a reporting directive and configure a receiver.
Continue to sanitize stored HTML according to the elements, attributes, and URL schemes required by each field.
Affected configuration
config/settings.py
- Templates containing inline scripts or event handlers
- Templates that render stored HTML with
safe
Add a Content Security Policy as defense in depth
Severity: Low
OWASP category: A02:2025 - Security Misconfiguration
Summary
The production site does not send a
Content-Security-Policyheader. A tested, enforcing policy would restrict the scripts and other resources that browsers may load if unsafe HTML is published.The project renders stored HTML with Django's
safefilter. CSP would provide a fallback if unsafe content passes the publishing workflow, but it would not replace server-side HTML sanitization.Evidence
The response from
https://python.ph/did not contain aContent-Security-PolicyorContent-Security-Policy-Report-Onlyheader.Stored HTML is rendered with
safein these public templates:app/landing/templates/landing/header/index.htmlapp/landing/templates/landing/our_aim.htmlapp/landing/templates/landing/why_python.htmlapp/landing/templates/landing/what_we_do/index.htmlapp/landing/templates/landing/code_of_conduct.htmlapp/landing/templates/landing/mailing_list.htmlapp/events/templates/events/event_detail.htmlapp/jobs/templates/jobs/detail.htmlSections and events can be changed through Django admin or data imports. An authenticated user can submit a job description, but it is not public until the job is approved. These fields use
tinymce.models.HTMLField, and the project does not sanitize their HTML on the server.The lock file resolves Django 6.0.7. Django 6 has built-in CSP support, but
config/settings.pydoes not enable it.Reason for the hardening change
The
safefilter disables Django's normal output escaping. If script-capable HTML is stored and published, the browser has no site policy blocking inline JavaScript, event handlers, or scripts loaded from an unapproved origin.An enforcing CSP can reduce the effect of such a publishing mistake. The missing header is not an exploitable vulnerability by itself, and report-only mode does not block content.
Recommended change
Add Django's CSP middleware to
MIDDLEWARE:"django.middleware.csp.ContentSecurityPolicyMiddleware",Define the initial policy in
SECURE_CSP_REPORT_ONLY. Review at leastdefault-src,script-src,style-src,img-src,font-src,connect-src,object-src,base-uri,form-action, andframe-ancestors.The policy must account for Google Fonts, the configured R2 media origin, the Mailchimp subscription form, and the scripts used by Django admin, Unfold, and TinyMCE.
Move inline JavaScript to static files where practical. If an inline
<script>must remain, addCSP.NONCEtoscript-src, adddjango.template.context_processors.csp, and setnonce="{{ csp_nonce }}"on the script element. Replace theonclickhandler in the event detail template with an event listener because a script nonce does not authorize inline event handlers.After testing public, authentication, and admin pages, move the reviewed policy to
SECURE_CSPso Django sends an enforcing header. If reports are to be collected, add a reporting directive and configure a receiver.Continue to sanitize stored HTML according to the elements, attributes, and URL schemes required by each field.
Affected configuration
config/settings.pysafe