Qué resuelve CSP
Content Security Policy permite indicar al navegador desde qué orígenes puede cargar scripts, estilos, imágenes y otros recursos. Su objetivo es reducir el impacto de ataques de inyección de contenido, sobre todo cuando un fragmento JavaScript llega al HTML por una entrada que no debía ejecutarse.

Una política CSP bien ajustada no sustituye al escape de plantillas ni a la validación de entradas, pero sí añade una capa defensiva muy útil en aplicaciones expuestas a internet.
En proyectos Django, la política suele definirse desde settings.py y aplicarse mediante un middleware. Esto permite centralizar las reglas y desplegarlas de forma coherente en todos los entornos.
Configuración en Django
Desde Django 6.0 (diciembre 2025) la gestion de CSP esta integrada en el framework mediante el middleware ContentSecurityPolicyMiddleware, el context processor csp y los settings SECURE_CSP / SECURE_CSP_REPORT_ONLY. Para versiones anteriores (Django 5.2 LTS y previas) sigue siendo necesario el paquete externo django-csp, mantenido por Mozilla.
El siguiente diagrama resume visualmente los conceptos clave introducidos en esta seccion:
graph TD
A[Headers de seguridad] --> B[CSP]
A --> C[X-Frame-Options]
A --> D[Strict-Transport-Security]
A --> E[X-Content-Type-Options]
A --> F[Referrer-Policy]
A --> G[Permissions-Policy]
B --> H[default-src self]
B --> I[script-src nonce]
B --> J[style-src self]
B --> K[img-src https]
A --> L[django-csp middleware]
Una configuración inicial razonable suele partir de una política restrictiva y ampliar solo los origenes que de verdad necesita la aplicación:
from django.utils.csp import CSP
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.middleware.csp.ContentSecurityPolicyMiddleware",
]
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.template.context_processors.csp",
],
},
},
]
SECURE_CSP = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF, CSP.NONCE],
"style-src": [CSP.SELF, CSP.NONCE],
"img-src": [CSP.SELF, "https:", "data:"],
"connect-src": [CSP.SELF],
"frame-ancestors": [CSP.NONE],
"object-src": [CSP.NONE],
"base-uri": [CSP.SELF],
"form-action": [CSP.SELF],
}
Las directivas frame-ancestors, base-uri y form-action reducen vectores de ataque que no se cubren con script-src (clickjacking, inyección de <base>, hijacking de formularios). Mantener object-src 'none' evita ejecución de plugins legacy.
Cuando una plantilla necesita un bloque <script> o <style> generado por el servidor, conviene usar un nonce:
<script nonce="{{ request.csp_nonce }}">
window.appConfig = { debug: false };
</script>
Si una página depende de varios servicios externos, la política debe documentar con precisión cada dominio permitido. Anadir origenes sin revisar suele terminar debilitando la protección.
Endpoints de reporte y modo report-only
Antes de bloquear recursos en producción, es habitual publicar la política en modo report only para observar que peticiones incumplen las reglas y ajustar la configuración sin interrumpir la experiencia del usuario.
SECURE_CSP_REPORT_ONLY = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF, "https://cdn.example.com"],
"report-uri": ["/csp-report/"],
"report-to": ["csp-endpoint"],
}
El endpoint receptor puede ser una vista propia o un servicio externo (Sentry, Datadog, Report URI):
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
import json, logging
logger = logging.getLogger("csp")
@csrf_exempt
def csp_report(request):
if request.method != "POST":
return HttpResponseBadRequest()
try:
report = json.loads(request.body)
except json.JSONDecodeError:
return HttpResponseBadRequest()
logger.warning("csp-violation", extra={"report": report})
return HttpResponse(status=204)
Las nuevas implementaciones de navegadores estan migrando de
report-uri(legado) a la cabeceraReporting-Endpointsconreport-to. Conviene declarar ambas durante la transición.
Cabeceras complementarias
CSP cubre solo una parte de la postura de seguridad. Sin estas cabeceras, una política CSP rigurosa pierde efectividad:
SECURE_HSTS_SECONDS = 31536000 # Strict-Transport-Security 1 ano
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True # X-Content-Type-Options: nosniff
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"
X_FRAME_OPTIONS = "DENY" # complementa frame-ancestors
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
Estas cabeceras protegen contra MIME sniffing, downgrade a HTTP, fuga de Referer entre orgs y exploits Spectre/Meltdown que explotan COOP relajado.
Trampas comunes
- Bibliotecas que inyectan inline scripts: Stripe, Google Maps o Tag Manager suelen requerir aceptar dominios concretos en
script-srcyconnect-src. Documentar cada origen con su propósito evita que la lista crezca sin control. - Imagenes de blob/data en
img-src: si la app procesa avatares o capturas, anadirdata:yblob:evita romper canvas/file readers. unsafe-inlinesolo como último recurso: anula la protección más fuerte que aporta CSP. Si una libreria solo funciona así, considerar reemplazarla o sandbox en un iframe con política propia.- Inconsistencia entre entornos: si dev usa una política permisiva y prod una estricta, los problemas aparecen tras desplegar. Mejor compartir intención y solo variar dominios concretos.
- Olvidar
frame-ancestors: sin esta directiva la página puede ser embebida en iframes maliciosos para clickjacking, incluso conX-Frame-Optionsconfigurado de forma inconsistente.
Despliegue gradual y criterios de equipo
Este enfoque encaja bien con equipos que trabajan con frontend, analitica, monitorización y seguridad al mismo tiempo, porque permite pactar una política común y eliminar dependencias innecesarias antes de endurecerla.
En una revisión técnica conviene comprobar al menos estos puntos:
- 1. Origenes mínimos: solo deben permitirse los dominios estrictamente necesarios.
- 2. Scripts inline: es preferible resolverlos con nonces o reubicarlos en ficheros propios.
- 3. Consistencia por entorno: desarrollo, staging y producción deben compartir la misma intención de política, aunque cambien algunos dominios.
- 4. Endpoint de reporte activo: incluso con política enforcing conviene mantener
report-to/report-uripara detectar regresiones. - 5. Validación automática: incorporar tests que comprueben las cabeceras servidas, así una refactorización no las elimina por error.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Configurar una política CSP, usar nonces en plantillas y desplegarla de forma gradual sin romper la aplicación.