← Todo el material
Curso Avanzado 24 de agosto de 2026

Seguridad de IA — Módulo 6: guardrails — la capa de defensa fuera del modelo

Sexto módulo del Curso de Seguridad de IA: por qué la defensa robusta no vive dentro del modelo sino en una capa determinista que lo rodea. Filtrado de entrada y salida, clasificadores de seguridad (Llama Guard, Prompt Guard), validación estructural, moderación, y los límites reales de los guardrails.

☰ Índice del contenido

En el módulo 5 endurecimos la arquitectura del agente: mínimo privilegio en cada tool, separación de lectura y escritura, confirmación humana para acciones irreversibles, allowlists de dominios y destinos. Todos esos controles reducen el daño de un ataque que se cuela: aunque el modelo se deje manipular, la tool destructiva no existe o pide permiso. Pero reducir el daño no es lo mismo que atrapar el ataque. Este módulo agrega la última capa: la red que atrapa lo que se coló, y que corre fuera del modelo. Un guardrail no le pide permiso al modelo para actuar; se sienta antes y después de él, con reglas y clasificadores propios, y decide si el texto entra o sale.

Como todo el curso, esto es material defensivo: sirve para proteger tus propios sistemas. Los ejemplos de contenido malicioso están para construir el filtro que lo detecta, nunca para operarlo contra terceros.

La tesis del módulo

Un guardrail es un control determinista que no le pide permiso al modelo: valida entrada y salida con reglas y clasificadores propios, porque un control que depende del mismo modelo que intenta proteger no es un control. Si le preguntas al LLM “¿esta respuesta que acabas de generar es segura?”, estás confiando el juicio al mismo sistema que puede estar comprometido. La defensa robusta vive en código que corre aparte.

Por qué la defensa no puede vivir dentro del modelo

Un system prompt que dice “nunca reveles datos de otros usuarios” es una instrucción probabilística: el modelo la cumple casi siempre, hasta que un jailbreak lo convence de lo contrario. El problema estructural es que la instrucción y el ataque viven en el mismo lugar — el contexto — y compiten por la atención del mismo modelo. El atacante solo necesita ganar una vez.

Un guardrail rompe esa simetría sacando el control del contexto. La regla “ninguna respuesta puede contener un RUT que no sea el del usuario autenticado” no es un texto que el modelo pueda ser convencido de ignorar: es una expresión regular que corre sobre la salida, en un proceso que el prompt del atacante nunca toca. El modelo puede alucinar, puede ser jailbrekeado, puede confundirse — el guardrail no cambia de opinión porque no tiene opinión.

De ahí la arquitectura de doble anillo (dos capas concéntricas de validación): un guardrail de entrada que revisa lo que llega antes de que el modelo lo lea, y un guardrail de salida que revisa lo que el modelo produjo antes de que llegue al usuario o a una tool.

    ARQUITECTURA DE DOBLE ANILLO

 entrada
    │
    ▼
 ┌───────────────┐  bloquea  ┌─────────────┐
 │ guardrail-in  │ ────────► │  RECHAZO    │
 │ clasificador  │           │ (log + msg  │
 │ + reglas      │           │  seguro)    │
 └───────┬───────┘           └─────────────┘
         │ pasa                     ▲
         ▼                          │
 ┌───────────────┐                  │
 │ modelo/agente │                  │ bloquea
 └───────┬───────┘                  │
         │                          │
         ▼                          │
 ┌───────────────┐                  │
 │ guardrail-out │ ─────────────────┘
 │ esquema + PII │
 │ + denylist    │
 └───────┬───────┘
         │ pasa
         ▼
 salida al usuario / tool

El diagrama tiene una lección: hay dos puntos donde el flujo puede terminar en rechazo, y ninguno de los dos consulta al modelo para decidirlo. El anillo de entrada frena ataques antes de gastar un token; el de salida frena lo que el modelo produjo a pesar del primer anillo. Se necesitan los dos: el de entrada no ve las alucinaciones que el modelo inventa solo, y el de salida no ahorra el costo de procesar un ataque obvio.

Los tipos de guardrail

No todos los guardrails hacen lo mismo ni corren en el mismo anillo. Estos son los que cubren casi todos los casos.

Clasificadores de seguridad

Un modelo pequeño y especializado que clasifica texto como seguro o inseguro. Dos de referencia, ambos de la familia Llama:

  • Llama Guard (clasificador de contenido, meta-llama/Llama-Guard-3-8B). Es un modelo fino sobre Llama-3.1-8B que etiqueta un prompt o una respuesta como safe / unsafe según 14 categorías de daño: las 13 de la taxonomía MLCommons (S1 crímenes violentos, S2 no violentos, S7 privacidad, S11 autolesión, etc.) más S14, abuso del intérprete de código. Sirve en los dos anillos: clasifica el mensaje del usuario en la entrada y la respuesta del modelo en la salida.
  • Prompt Guard (clasificador de injection/jailbreak, meta-llama/Llama-Prompt-Guard-2-86M). Mucho más chico (86M de parámetros), un clasificador de texto que detecta prompt injection (instrucciones maliciosas metidas en datos de terceros) y jailbreaks (intentos de romper las reglas del sistema). Va en el anillo de entrada, y sobre todo sobre el contenido que traen las tools — el vector de injection indirecta que vimos en el módulo 5.

Ojo: un clasificador tiene ventana de contexto propia. Prompt Guard 2 soporta 512 tokens; para un documento largo hay que partirlo en segmentos y clasificar cada uno, porque la carga adversarial puede estar enterrada al medio de un PDF de veinte páginas y un solo pase truncado la deja pasar.

Ojo: existe también Llama Guard 4 (meta-llama/Llama-Guard-4-12B, abril de 2025), derivado de Llama 4 Scout y multimodal: clasifica texto e imágenes con la misma taxonomía. El patrón de uso es idéntico (safe / unsafe + categorías); aquí usamos el 3-8B porque el caso es solo texto y pesa menos.

Moderación de contenido

La OpenAI moderation API (omni-moderation-latest) es un clasificador gestionado, gratis, que etiqueta texto (e imágenes) en categorías como acoso, odio, autolesión, contenido sexual o violencia. Devuelve flagged: true/false y un score por categoría. Es el guardrail más barato de montar cuando el riesgo principal es contenido dañino genérico, no un ataque a tu lógica de negocio.

Detección y redacción de PII

PII (Personally Identifiable Information: datos que identifican a una persona — nombre, RUT, teléfono, correo, tarjeta). En el anillo de salida se busca PII con expresiones regulares o un detector, y se redacta (se reemplaza por [REDACTADO]) antes de entregar. No es para tapar la PII del propio usuario, sino para atrapar la fuga de datos de terceros que el retrieval trajo por error.

Validación estructural de la salida

Cuando el modelo debe devolver JSON, el guardrail valida contra un esquema (schema: la forma esperada del dato — qué campos, de qué tipo, en qué rango). Que el precio sea un número no negativo, que el estado sea uno de una lista cerrada, que no aparezcan campos que el modelo inventó. Un modelo alucinando un campo descuento_aplicado: 90 es un incidente si tu backend confía en él; el validador lo rechaza antes.

Allowlist / denylist de temas y groundedness

Una allowlist define los temas que el agente puede tratar; una denylist, los prohibidos (dar consejo médico, prometer descuentos, hablar de la competencia). Y la verificación de groundedness (anclaje: que cada afirmación de la respuesta esté respaldada por el contexto recuperado) es el guardrail anti-alucinación de un sistema RAG: si la respuesta afirma algo que no está en los documentos, se marca.

Código: un guardrail de salida

Esta es la red del anillo de salida. Valida el esquema, redacta PII y chequea contra una denylist antes de entregar. Es determinista de punta a punta: ni una llamada al modelo.

"""Guardrail de salida: valida esquema + redacta PII + denylist.

Corre DESPUÉS del modelo y ANTES de entregar al usuario o a una tool.
No consulta al LLM para decidir: son reglas propias. Si algo falla,
la respuesta no sale — se devuelve un mensaje seguro y se registra.
"""
import re
from pydantic import BaseModel, field_validator, ValidationError

PATRONES_PII = {
    "rut": re.compile(r"\b\d{1,2}\.?\d{3}\.?\d{3}-[\dkK]\b"),
    "email": re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.]+\b"),
    "tarjeta": re.compile(r"\b(?:\d[ -]?){13,16}\b"),
}
DENYLIST = ("garantizo", "descuento del", "consejo médico", "diagnostico")


class RespuestaAgente(BaseModel):
    """Esquema esperado. Campos extra hacen fallar la validación:
    el modelo NO puede inventar campos que el backend confíe."""
    model_config = {"extra": "forbid"}
    mensaje: str
    estado: str

    @field_validator("estado")
    @classmethod
    def estado_valido(cls, v: str) -> str:
        if v not in {"abierto", "resuelto", "escalado"}:
            raise ValueError(f"estado fuera de la lista cerrada: {v}")
        return v


def redactar_pii(texto: str, rut_autorizado: str | None) -> str:
    """Redacta toda PII salvo, si aplica, el RUT del propio usuario."""
    for nombre, patron in PATRONES_PII.items():
        def _sub(m):
            if nombre == "rut" and m.group() == rut_autorizado:
                return m.group()
            return f"[{nombre.upper()}_REDACTADO]"
        texto = patron.sub(_sub, texto)
    return texto


def guardrail_salida(payload: dict, rut_usuario: str | None) -> dict:
    # 1. Validación estructural: forma, tipos, campos permitidos.
    try:
        resp = RespuestaAgente.model_validate(payload)
    except ValidationError as e:
        return {"bloqueado": True, "motivo": f"esquema: {e.errors()[0]['msg']}"}

    # 2. Denylist de temas: frases que el negocio prohíbe emitir.
    bajo = resp.mensaje.lower()
    for aguja in DENYLIST:
        if aguja in bajo:
            return {"bloqueado": True, "motivo": f"denylist: {aguja!r}"}

    # 3. Redacción de PII de terceros antes de entregar.
    limpio = redactar_pii(resp.mensaje, rut_usuario)

    return {"bloqueado": False, "mensaje": limpio, "estado": resp.estado}

Los tres pasos son de dureza decreciente: el esquema es una garantía dura (o valida o no), la denylist es una heurística de frases, y la redacción de PII depende de qué tan buenos sean los patrones. Ninguno consulta al modelo, y por eso ninguno es vulnerable al prompt del atacante.

Código: un guardrail de entrada con clasificador

El anillo de entrada, con Prompt Guard para detectar injection y Llama Guard para contenido. Firmas verificadas contra las tarjetas de modelo de Hugging Face.

"""Guardrail de entrada: clasifica el mensaje antes de que el modelo
lo lea. Prompt Guard 2 (injection/jailbreak) por pipeline, y Llama
Guard 3 (contenido) por chat template. Ambos corren fuera del agente.
"""
from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM

# Prompt Guard 2: clasificador de injection/jailbreak, 86M, 512 tokens.
guard_injection = pipeline(
    "text-classification",
    model="meta-llama/Llama-Prompt-Guard-2-86M",
)

# Llama Guard 3: clasificador de contenido en 14 categorías (13 MLCommons + S14).
LG_ID = "meta-llama/Llama-Guard-3-8B"
lg_tok = AutoTokenizer.from_pretrained(LG_ID)
lg_model = AutoModelForCausalLM.from_pretrained(LG_ID)


def clasificar_contenido(texto: str) -> dict:
    """Devuelve safe/unsafe + categorías violadas. La salida de Llama
    Guard es 'safe' o 'unsafe\\nS1,S7' — se parsea por líneas."""
    chat = [{"role": "user", "content": texto}]
    ids = lg_tok.apply_chat_template(chat, return_tensors="pt")
    out = lg_model.generate(ids, max_new_tokens=64, pad_token_id=0)
    txt = lg_tok.decode(out[0][ids.shape[-1]:], skip_special_tokens=True)
    seguro = txt.strip().startswith("safe")
    cats = [] if seguro else txt.strip().split("\n")[1].split(",")
    return {"safe": seguro, "categorias": cats}


def guardrail_entrada(texto: str, es_dato_de_tool: bool) -> dict:
    # Injection: crítico sobre todo si el texto viene de una tool
    # (injection indirecta). Prompt Guard etiqueta el ataque.
    inj = guard_injection(texto, truncation=True)[0]
    if inj["label"] != "BENIGN" and inj["score"] > 0.9:
        return {"bloqueado": True, "motivo": f"injection ({inj['label']})",
                "origen": "tool" if es_dato_de_tool else "usuario"}

    # Contenido dañino: se aplica al mensaje del usuario.
    cont = clasificar_contenido(texto)
    if not cont["safe"]:
        return {"bloqueado": True, "motivo": f"contenido {cont['categorias']}"}

    return {"bloqueado": False}

Fíjate en el parámetro es_dato_de_tool: la injection más peligrosa no viene del usuario, viene dentro de un documento que una tool leyó. Por eso el guardrail de entrada no se aplica solo al primer mensaje, sino a cada bloque de texto que entra al contexto desde afuera.

Frameworks: cuándo sí, cuándo bastan 100 líneas

Hay dos frameworks maduros, con filosofías opuestas:

  • NeMo Guardrails (NVIDIA). Envuelve toda la conversación con rails programables escritos en Colang (un lenguaje propio para definir flujos de diálogo: temas permitidos, temas prohibidos, y las transiciones entre ellos). Fuerte en control conversacional y multi-turno; a cambio, acopla tu app a su modelo de flujo.
  • Guardrails AI. No decide la lógica de negocio: le pasas texto, corre validators (validadores componibles: PII, esquema, toxicidad, groundedness) y te devuelve el resultado para que tú decidas. Se integra en cualquier punto de una app existente, con Python o JavaScript.

¿Cuándo un guardrail propio de cien líneas basta? Igual que en el mensaje de evals sobre frameworks: cuando tus reglas son pocas, específicas de tu negocio y deterministas. El guardrail_salida de arriba —esquema + PII + denylist— son cuarenta líneas y no gana nada envuelto en un framework. Adoptas NeMo o Guardrails AI cuando necesitas un catálogo grande de validadores probados, control de flujo multi-turno, o un hub de validadores mantenido por terceros que no quieres reimplementar. El framework no te da seguridad que no tuvieras: te ahorra escribir y mantener los mismos controles.

Los límites, sin adornos

Un guardrail es una capa más, no una bala de plata. Sus límites, honestos:

  • Un guardrail basado en LLM también es atacable. Si tu clasificador de seguridad es a su vez un modelo generativo, hereda la misma vulnerabilidad a injection que intenta detectar. Por eso los clasificadores dedicados (Prompt Guard, Llama Guard) son más robustos que un “juez” improvisado con un prompt, pero ni ellos son inmunes.
  • Falsos positivos y falsos negativos. Todo clasificador se equivoca en las dos direcciones: bloquea texto legítimo (falso positivo, mata utilidad) y deja pasar ataques (falso negativo, mata seguridad). El umbral que elijas es un trade-off, no una verdad.
  • Latencia y costo extra. Cada anillo agrega una inferencia. Un Llama Guard de 8B en la entrada y otro en la salida pueden duplicar la latencia percibida. Los guardrails deterministas (regex, esquema) son gratis; los basados en modelo, no.
  • No reemplazan el mínimo privilegio del módulo 5. Un guardrail que falla y una tool sin allowlist es un incidente; un guardrail que falla y una tool con allowlist es un caso más de una defensa que atajó otra. Las capas se suman, no se sustituyen.

Cuidado: el guardrail es la última red, no la primera ni la única. Si tu único control contra la exfiltración es un clasificador de salida, tienes un solo punto de falla con tasa de error distinta de cero — y el atacante prueba cien variantes hasta encontrar la que el clasificador no marca. La defensa real es en capas: arquitectura de tools (módulo 5) más guardrails (este módulo) más la suite adversarial que verifica que ambos aguanten (red teaming).

Tabla: qué guardrail, qué detecta, dónde corre

GuardrailQué detectaAnilloCosto / latencia
Prompt GuardInjection / jailbreakEntrada (in)Bajo (86M)
Llama GuardContenido dañino (14 cat.)Entrada y salidaAlto (8B, x2)
Moderación (OpenAI)Odio, autolesión, sexualEntrada y salidaBajo (API gratis)
Detección de PIIDatos de terceros en salidaSalida (out)Nulo (regex)
Validación de esquemaJSON malformado / campos inventadosSalida (out)Nulo (regex/pydantic)
Allow/denylist temasTemas prohibidos por negocioSalida (out)Nulo o bajo
GroundednessAlucinación vs contexto RAGSalida (out)Medio (modelo juez)

La columna que decide tu diseño es la última. Los guardrails deterministas —PII, esquema, denylist— cuestan casi nada y no fallan a la manera de un modelo. Los basados en modelo cubren lo que las reglas no alcanzan (contenido matizado, injection ofuscada) pero pagan latencia y traen su propio error. La regla práctica: exprime primero lo determinista, y reserva el clasificador pesado para lo que de verdad lo necesita.

Errores comunes

  • Usar el mismo modelo como su propio guardrail. Preguntarle al LLM “¿tu respuesta fue segura?” confía el juicio al sistema que puede estar comprometido. El control tiene que correr aparte, en código o en un clasificador dedicado.
  • Solo anillo de salida. Sin guardrail de entrada procesas cada ataque obvio (gasto y latencia) y dejas pasar injection al contexto. Sin guardrail de salida no ves las alucinaciones que el modelo inventa solo. Se necesitan los dos.
  • No segmentar entradas largas. Prompt Guard corta en 512 tokens; un PDF largo con la carga al medio pasa entero si no lo partes y clasificas por segmentos.
  • Confundir guardrail con arquitectura. El guardrail atrapa lo que se coló; no reemplaza el mínimo privilegio de las tools. Un clasificador de salida no es excusa para una tool sin allowlist.
  • Ignorar los falsos positivos. Un guardrail muy estricto bloquea a usuarios legítimos y nadie mide cuántos. Resistencia y falsos positivos se reportan juntos, igual que en la suite adversarial.
  • Meter un framework por defecto. Si tus reglas son cuarenta líneas de regex y esquema, NeMo o Guardrails AI no agregan seguridad: agregan una dependencia. El framework se justifica por catálogo de validadores o control de flujo, no por moda.
  • Tratar el guardrail LLM como infalible. Un clasificador basado en modelo hereda la vulnerabilidad a injection. Es más robusto que un prompt improvisado, no inmune.

La frase para llevarse

“Un control que le pregunta al modelo si portarse bien no es un control: es una sugerencia con testigo. El guardrail decide fuera del modelo, con reglas que el prompt del atacante nunca toca.”

Qué viene

Módulo 7: datos, privacidad y PII. Vimos que redactar PII en la salida es un guardrail, pero eso es tapar la fuga al final del tubo. El módulo que viene sube por el tubo hasta el origen: cómo minimizar los datos que el sistema toca, cómo aislar por tenant para que el retrieval nunca traiga PII ajena, qué se puede y qué no se puede mandar al proveedor del modelo, y cómo se cumplen las obligaciones legales (retención, derecho a borrado, consentimiento) cuando el dato pasa por un LLM. El guardrail atrapa la fuga; la arquitectura de datos hace que no haya fuga que atrapar.

Material relacionado

SeguridadGuardrailsModeraciónLLMDefensa

Preguntas sobre este curso

Regístrate gratis para hacer preguntas y guardar tu progreso.

✉ Entérate de lo nuevo

Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.