diff --git a/pydotorg/settings/base.py b/pydotorg/settings/base.py index 5d3f53f86..093dbb575 100644 --- a/pydotorg/settings/base.py +++ b/pydotorg/settings/base.py @@ -152,6 +152,7 @@ MIDDLEWARE = [ "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", + "csp.middleware.CSPMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "pydotorg.middleware.AdminNoCaching", "pydotorg.middleware.GlobalSurrogateKey", @@ -297,6 +298,25 @@ X_FRAME_OPTIONS = "SAMEORIGIN" SILENCED_SYSTEM_CHECKS = ["security.W019"] +### django-csp (Content-Security-Policy) + +# Report-Only first: collect violations and tune the allowlist before +# enforcing. Rollout tracked in #3041. +CONTENT_SECURITY_POLICY_REPORT_ONLY = { + "DIRECTIVES": { + "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'"], + }, +} + ### django-rest-framework REST_FRAMEWORK = { diff --git a/pyproject.toml b/pyproject.toml index 8ea6e6acd..a48fccc98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ version = "0.1.0" requires-python = ">=3.14" dependencies = [ "dj-database-url==0.5.0", + "django-csp==4.0", "django-pipeline==4.1.0", "django-sitetree==1.18.0", "django-apptemplates==1.5", diff --git a/uv.lock b/uv.lock index fb62e4a43..74e858c78 100644 --- a/uv.lock +++ b/uv.lock @@ -478,6 +478,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/3c/9ebd7ed021b7c519bac954bc88146bc870e7d3c8db2580fa67268464fd2e/django_countries-8.2.0-py3-none-any.whl", hash = "sha256:2b2617bec7c15dc735bdec38ae89f0058e38fddfffdb19a7f6b75ef1e3d5380f", size = 3776079, upload-time = "2025-11-24T19:57:05.576Z" }, ] +[[package]] +name = "django-csp" +version = "4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/db/23567744dff2169aadb095f726088074476b24dfff5a759ffcf6389cf401/django_csp-4.0.tar.gz", hash = "sha256:b27010bb702eb20a3dad329178df2b61a2b82d338b70fbdc13c3a3bd28712833", size = 20028, upload-time = "2025-04-02T16:41:46.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/a4/736ab90ee527a23e194d1ce7358c6bcced66381ced83c28268b4aea236f7/django_csp-4.0-py3-none-any.whl", hash = "sha256:d5a0a05463a6b75a4f1fc1828c58c89af8db9364d09fc6e12f122b4d7f3d00dc", size = 25689, upload-time = "2025-04-02T16:41:45.689Z" }, +] + [[package]] name = "django-debug-toolbar" version = "6.2.0" @@ -1258,6 +1271,7 @@ dependencies = [ { name = "django-apptemplates" }, { name = "django-celery-beat" }, { name = "django-countries" }, + { name = "django-csp" }, { name = "django-extensions" }, { name = "django-filter" }, { name = "django-haystack" }, @@ -1337,6 +1351,7 @@ requires-dist = [ { name = "django-apptemplates", specifier = "==1.5" }, { name = "django-celery-beat", specifier = "==2.8.1" }, { name = "django-countries", specifier = "==8.2.0" }, + { name = "django-csp", specifier = "==4.0" }, { name = "django-extensions", specifier = "==3.2.3" }, { name = "django-filter", specifier = "==25.1" }, { name = "django-haystack", specifier = "==3.3.0" },