Al desarrollar aplicaciones web, mostrar datos ingresados por el usuario directamente en la página HTML es muy peligroso. Esto es como abrir las puertas a los ataques de XSS (Cross-Site Scripting). Si un usuario malintencionado envía datos que incluyen una etiqueta <script>, y esos datos se representan tal cual en el navegador de otro usuario, se pueden robar las cookies de sesión o ejecutar código malicioso.

Django proporciona un conjunto robusto de herramientas para combatir estas amenazas de seguridad y manejar HTML de manera segura, conocido como django.utils.html. 🛡️


1. Clave para la defensa contra XSS: escape()



Esta es la función más básica y fundamental de este módulo. escape() convierte ciertos caracteres especiales de HTML en entidades HTML, lo que permite que el navegador los reconozca como texto normal en lugar de etiquetas.

  • < se convierte en &lt;
  • > se convierte en &gt;
  • ' (comilla simple) se convierte en &#39;
  • " (comilla doble) se convierte en &quot;
  • & se convierte en &amp;

Ejemplo:

from django.utils.html import escape

# Entrada maliciosa del usuario
malicious_input = "<script>alert('¡Ataque XSS!');</script>"

# Procesar con escape
safe_output = escape(malicious_input)

print(safe_output)
# Resultados:
# &lt;script&gt;alert(&#39;¡Ataque XSS!&#39;);&lt;/script&gt;

Así, la cadena convertida no se ejecuta como script en el navegador; en su lugar, se muestra el texto <script>alert('¡Ataque XSS!');</script>.

[Importante] Autoescaping en plantillas de Django

Por fortuna, el motor de plantillas de Django automáticamente escapa todas las variables por defecto.

{{ user_input }}

Por lo tanto, la función escape() se utiliza principalmente cuando se necesita procesar HTML manualmente desde fuera de la plantilla (por ejemplo, en la lógica de vistas o al crear respuestas de API).


2. Eliminar todas las etiquetas HTML: strip_tags()

En ocasiones, puede que desees eliminar todas las etiquetas y extraer solo texto plano. Por ejemplo, si deseas quitar todas las etiquetas HTML del cuerpo de un blog para usarlas en un resumen de resultados de búsqueda.

strip_tags() hace precisamente eso.

Ejemplo:

from django.utils.html import strip_tags

html_content = "<p>Este es un <strong>anuncio muy importante</strong>.</p>"

plain_text = strip_tags(html_content)

print(plain_text)
# Resultado:
# Este es un anuncio muy importante.
# (Los espacios entre las etiquetas también se limpian)

3. Crear HTML de forma segura: format_html()



Es una de las funciones más poderosas e importantes.

Puede que necesites generar HTML de manera dinámica desde código Python (por ejemplo, en views.py o models.py). Por ejemplo, es posible que desees que un método de un modelo devuelva un enlace de un formato específico en la página de administración.

Si ensamblas cadenas con un f-string o el operador +, serás muy vulnerable a ataques XSS.

La función format_html(format_string, *args, **kwargs) escapa automáticamente todos los argumentos (args, kwargs) excepto el format_string y los inserta en la cadena. Luego, marca el resultado como "este HTML es seguro" (mark_safe) para que se renderice tal cual en la plantilla sin escaparse.

Ejemplo: (Creación de un enlace para la página de administración desde un método de modelo)

from django.db import models
from django.utils.html import format_html
from django.utils.text import slugify

class Post(models.Model):
    title = models.CharField(max_length=100)

    def get_edit_link(self):
        # [Mal ejemplo] f-string: si hay <script> en self.title, se producirá un XSS
        # return f'<a href="/admin/blog/post/{self.id}/change/">{self.title}</a>'

        # [Buen ejemplo] usando format_html
        # self.id y self.title se escapan automáticamente.
        url = f"/admin/blog/post/{self.id}/change/"
        return format_html(
            '<a href="{}">{} (editar)</a>',
            url,
            self.title  # Si title es "My<script>..." se convierte en "&lt;script&gt;"
        )

4. Ayudantes de formato de texto: linebreaks y urlize

Estas funciones son las funciones originales de los filtros de plantillas (|linebreaks, |urlize) y son útiles para convertir texto plano a formato HTML.

  • linebreaks(text): Transforma los caracteres de nueva línea en texto plano (\n) a las etiquetas <p> o <br> en HTML. Es útil para mostrar el texto que un usuario ha ingresado en un textarea manteniendo el formato.
  • urlize(text): Identifica patrones de URL como http://..., https://..., www... en el texto y los envuelve automáticamente en etiquetas <a>.

Ejemplo:

from django.utils.html import linebreaks, urlize

raw_text = """Hola.
Estoy probando django.utils.html.

Sitio visitado: https://www.djangoproject.com
"""

# 1. Aplicar saltos de línea
html_with_breaks = linebreaks(raw_text)
# Resultados (aproximadamente):
# <p>Hola.<br>Estoy probando django.utils.html.</p>
# <p>Sitio visitado: https://www.djangoproject.com</p>

# 2. Aplicar enlaces URL
html_with_links = urlize(html_with_breaks)
# Resultados (aproximadamente):
# ...
# <p>Sitio visitado: <a href="https://www.djangoproject.com" rel="nofollow">https://www.djangoproject.com</a></p>

5. Combinar múltiples elementos de forma segura en HTML: format_html_join()

Si format_html() formatea un solo elemento, format_html_join() se utiliza para combinar varios elementos (listas, tuplas, etc.) de manera segura en HTML.

Se usa en el formato format_html_join(separator, format_string, args_list).

  • separator: HTML que separa cada elemento (por ejemplo, '\n', <br>)
  • format_string: Formato HTML que se aplicará a cada elemento (por ejemplo, <li>{}</li>)
  • args_list: Lista de datos que se ingresarán secuencialmente en format_string

Ejemplo: (Convertir una lista de Python en etiquetas <ul>)

from django.utils.html import format_html_join
from django.utils.safestring import mark_safe

options = [
    ('item1', 'Elemento 1'),
    ('item2', '<strong>Elemento peligroso 2</strong>'),
]

# En format_string, {} representa cada tupla completa de args_list.
# {0} se refiere al primer elemento de la tupla, {1} al segundo elemento.
# 'Elemento 1' y la parte '<strong>...' se escapan automáticamente.
list_items = format_html_join(
    '\n',  # Separar cada elemento con nueva línea
    '<li><input type="radio" value="{0}">{1}</li>', # Formato aplicado a cada elemento
    options  # Lista de datos
)

# list_items se convierte en un fragmento de HTML 'seguro'.
final_html = format_html('<ul>\n{}\n</ul>', list_items)

# Cuando se renderiza final_html en la plantilla {{ final_html }}...

Resultados (Código HTML):

<ul>
<li><input type="radio" value="item1">Elemento 1</li>
<li><input type="radio" value="item2">&lt;strong&gt;Elemento peligroso 2&lt;/strong&gt;</li>
</ul>

6. Pasar datos de forma segura en un : json_script()

Frecuentemente es necesario pasar datos de Python a variables de JavaScript en las plantillas de Django. En este caso, es muy conveniente y seguro usar json_script(data, element_id).

Esta función convierte diccionarios o listas de Python en cadenas JSON y las inserta en una etiqueta <script> de tipo application/json.

Ejemplo: (Pasando datos desde una vista)

# views.py
from django.utils.html import json_script

def my_view(request):
    user_data = {
        'id': request.user.id,
        'username': request.user.username,
        'isAdmin': request.user.is_superuser,
    }
    # Insertar user_data como JSON dentro de <script id="user-data-json">
    context = {
        'user_data_json': json_script(user_data, 'user-data-json')
    }
    return render(request, 'my_template.html', context)

Plantilla (my_template.html):

{{ user_data_json }}

<script>
    const dataElement = document.getElementById('user-data-json');
    const userData = JSON.parse(dataElement.textContent);

    console.log(userData.username); // "admin"
</script>

Este método previene errores de sintaxis o vulnerabilidades XSS que podrían ocurrir al intentar insertar datos manualmente como var user = {{ user_data }}; debido a caracteres como " o '.


7. [Avanzado] Especificar que HTML es seguro: mark_safe() / html_safe

A veces, los desarrolladores pueden desear intencionadamente crear HTML y estar seguros de que este HTML es 100% seguro, y quieren apagar la función de autoescapado de Django.

Funciones como format_html() o json_script() realizan este manejo automáticamente.

  • mark_safe(s): Devuelve la cadena s con una etiqueta de "este es HTML seguro, no lo escapes". Esta función en sí no realiza ningún tipo de escapado. Por lo tanto, no debe usarse nunca con datos no confiables.

  • @html_safe (decorador): Se utiliza para especificar que la cadena devuelta por un método de modelo o función de etiqueta personalizada es HTML seguro. Es útil para generar HTML con lógica compleja donde no es conveniente usar format_html.

Ejemplo: (Aplicado a un método de modelo)

from django.db import models
from django.utils.html import format_html, html_safe

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField()

    # Este método ya es seguro porque utiliza format_html (enfoque recomendado)
    def get_username_display(self):
        return format_html("<strong>{}</strong>", self.user.username)

    # Este método se marca como seguro tras lógica compleja (enfoque avanzado)
    @html_safe
    def get_complex_display(self):
        # ... (lógica de combinación de HTML compleja asegurada por el desarrollador) ...
        html_string = f"<div>{self.user.username}</div><p>{self.bio}</p>"
        # Este método se vuelve vulnerable a XSS si bio tiene <script>.
        # @html_safe debe ser usado con mucha precaución.
        return html_string

Resumen

El módulo django.utils.html es una herramienta esencial que permite implementar la filosofía de seguridad fundamental de Django (Autoescaping) a nivel de código Python.

  • Para prevenir XSS, usa escape(). (Automáticamente en las plantillas)
  • Para eliminar todas las etiquetas, usa strip_tags().
  • Para generar HTML de forma segura en el código Python, siempre debes usar format_html().
  • Cuando combines datos de lista en HTML, usa format_html_join().
  • Al pasar datos de Python a JavaScript, json_script() es el método más seguro y estándar.
  • mark_safe o @html_safe deben evitarse a menos que realmente sea necesario, en cuyo caso debes optar por format_html.

Al comprender y utilizar correctamente estas herramientas, puedes crear aplicaciones Django con una sólida seguridad.