Skip to content

Add a Content Security Policy as defense in depth #111

Description

@patzj

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions