Skip to content

Add a Content-Security-Policy in Report-Only mode (refs #3041)#3042

Open
tonghuaroot wants to merge 1 commit into
python:mainfrom
tonghuaroot:csp-report-only
Open

Add a Content-Security-Policy in Report-Only mode (refs #3041)#3042
tonghuaroot wants to merge 1 commit into
python:mainfrom
tonghuaroot:csp-report-only

Conversation

@tonghuaroot

Copy link
Copy Markdown

Part of #3041 (adds the CSP in Report-Only mode; enforcing and origin tuning to follow).

What this does

www.python.org currently sends no Content-Security-Policy header. This adds one via django-csp as defense-in-depth against XSS.

It ships in Report-Only mode: responses carry Content-Security-Policy-Report-Only and no enforcing Content-Security-Policy, so nothing is blocked for users. The point of this first step is to collect violation reports and tune the allowlist before switching to an enforced policy.

Changes

  • pyproject.toml / uv.lock: add django-csp==4.0.
  • pydotorg/settings/base.py:
    • add csp.middleware.CSPMiddleware to MIDDLEWARE, alongside the existing XFrameOptionsMiddleware (which is how the site already sets a security header from Django).
    • add a CONTENT_SECURITY_POLICY_REPORT_ONLY policy with a strict starting set of directives.

Starting policy

default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;
font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self';
object-src 'none'; form-action 'self'

These are intentionally strict so the Report-Only telemetry surfaces every inline script/style and third-party origin that needs a nonce, hash, or allowlist entry. Follow-ups from #3041: add a report endpoint, tune the origins, wire nonces into the base templates, then flip Report-Only to enforcing.

Verification

Smoke-tested the config against django-csp 4.0 in isolation: it emits the Content-Security-Policy-Report-Only header with the directives above and no enforcing Content-Security-Policy header, so pages are never blocked.

@tonghuaroot tonghuaroot requested a review from JacobCoffee as a code owner July 2, 2026 15:09
www.python.org currently sends no Content-Security-Policy header. This
adds one via django-csp as defense-in-depth against XSS, starting in
Report-Only mode so violations can be collected and the allowlist tuned
before the policy is enforced.

The middleware follows the existing XFrameOptionsMiddleware pattern.
Refs python#3041.

@sethmlarson sethmlarson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, two comments:

Comment thread pydotorg/settings/base.py
X_FRAME_OPTIONS = "SAMEORIGIN"
SILENCED_SYSTEM_CHECKS = ["security.W019"]

### django-csp (Content-Security-Policy)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment to remove this and our dependency on django-csp when we upgrade to Django 6.0.

Comment thread pydotorg/settings/base.py

# Report-Only first: collect violations and tune the allowlist before
# enforcing. Rollout tracked in #3041.
CONTENT_SECURITY_POLICY_REPORT_ONLY = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test case that checks which HTTP header is emitted? Want to make sure Content-Security-Policy-Report-Only is emitted, not Content-Security-Policy so we don't break everyone on rollout.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants