Seguridad y supervisiónActualizado 2026-08-25 · Versión 1.0

Codificación en la frontera de salida

Trata todo lo que el modelo emite como entrada hostil para quien lo consuma. Codifica y valida en cada destino —renderizador, shell, consulta, agente aguas abajo— con las reglas de ese destino. Un saneador global no puede hacerlo: el escapado correcto para HTML no significa nada para un shell.

Evidencia: Observación del sectorConfianza: MediaFuente: Observación del sectorFuente: Paper

Definición

La codificación en la frontera de salida es la práctica de aplicar codificación y validación específicas de cada destino a la salida del modelo en todo punto donde cruza hacia un sistema que va a interpretarla, en lugar de filtrarla una sola vez, de forma genérica, al salir del modelo. Su ausencia es la debilidad que OWASP llama improper output handling: manejo indebido de la salida.

Problema

Los equipos endurecen el lado de la entrada contra la inyección de prompts y dejan abierto el de la salida. El texto del modelo llega entonces a un renderizador, un shell, una base de datos o el contexto de otro agente, donde se interpreta como instrucción en vez de mostrarse como dato. El atacante no necesita llegar al modelo directamente. Dicho de otro modo: la salida del modelo hay que tratarla como entrada no confiable.

Cuándo usarlo

Cualquier agente cuya salida llegue a algo que la parsea: una interfaz de chat que renderiza markdown, un agente de código que ejecuta un comando sugerido, una llamada a herramienta construida con argumentos generados, un resumen que se inyecta en el prompt de un segundo agente, o un webhook que reenvía el texto.

Solución

Enumera los destinos antes de escribir ningún filtro. Cada sitio donde aterriza la salida del modelo y se interpreta —renderizador HTML, shell, consulta, ruta de fichero, descargador de URLs, contexto de otro agente, webhook aguas abajo— es una frontera distinta con reglas distintas.

Codifica en el destino, no en el origen. Escapa HTML para el renderizador, parametriza la consulta, pasa un array argv al proceso. La codificación pertenece a donde ocurre la interpretación, porque solo ahí sabes qué se va a interpretar.

Prefiere salida estructurada antes que prosa que luego hay que volver a parsear. Una llamada a herramienta con argumentos tipados tiene un esquema contra el que validar; una frase a la que le aplicas una expresión regular para sacar un nombre de fichero, no.

Valida el valor, no solo la sintaxis. Una ruta codificada sigue siendo una ruta: comprueba que resuelve dentro del directorio que querías antes de abrirla.

Trata las URLs salientes como un destino propio. Las imágenes y los enlaces renderizados provocan una petición sin que nadie pulse, así que sacan datos; pon en lista de permitidos los hosts que puede alcanzar un enlace renderizado.

Haz que la frontera sea el único camino. Si alguna ruta de código puede consumir salida cruda del modelo sin pasar por un codificador de destino, el control es orientativo, no real.

Componentes

Un inventario de destinos: cada consumidor de la salida del modelo, con el codificador que necesita cada uno.Codificadores por destino —HTML, argv de proceso, parámetros de consulta, resolución de rutas, lista de URLs permitidas— en lugar de un saneador compartido.Salida estructurada validada contra esquema para las llamadas a herramientas, de modo que los argumentos estén tipados y no extraídos de la prosa.Una lista de egreso permitido que cubra los hosts alcanzables desde enlaces e imágenes renderizados.Pruebas de contrato que emiten un payload por destino y comprueban que se neutraliza en la frontera.Registro de cuándo un codificador neutraliza algo, para que la frontera pueda decirte que está en el camino.

Beneficios

  • Rompe la cadena de inyección donde importa: ni un modelo completamente persuadido puede hacer que un sistema aguas abajo actúe, porque ese sistema nunca interpreta su texto.
  • Independiente del comportamiento del modelo. Sigue funcionando entre versiones, jailbreaks nuevos y cambios de prompt, porque no depende de que el modelo se niegue a nada.
  • Comprobable. Cada destino tiene un payload y un resultado de pasa o falla, así que el control produce evidencia en vez de garantías.
  • Barato si se aplica pronto. Añadir un codificador es un cambio de frontera; ponerlo cuando el destino ya está por todas partes es una refactorización.

Riesgos

  • Un solo saneador para todos los destinos. Parece un control, satisface la lista de verificación y está mal en todas las fronteras menos en aquella para la que se escribió.
  • Codificación que rompe el producto: escapar de más convierte markdown legítimo, bloques de código y texto no latino en ruido, y la presión por aflojar recae sobre el codificador en vez de sobre la lista de destinos.
  • Un inventario de destinos que envejece. Cada integración nueva añade consumidores, y no falla nada cuando se olvida uno.
  • Confundir detección con codificación. Escanear la salida buscando cadenas sospechosas caza los payloads del año pasado; la codificación no necesita reconocer el ataque.

Cuándo no usarlo

  • Salida que nunca se interpreta: una puntuación, un enum, un booleano que el llamante compara. Restringe el tipo en su lugar; un codificador sobre un conjunto cerrado de valores es ceremonia.
  • Herramientas locales de un solo usuario, sin renderizado ni ejecución de procesos, donde el único consumidor es una persona leyendo texto.
  • Donde el destino ya parametriza por construcción, como el binding de un ORM o un motor de plantillas que escapa por defecto. Un segundo codificador no aporta nada y puede provocar doble codificación.

Tecnologías

Context-aware output encodersMarkdown renderers with raw HTML disabledParameterised queries and ORM bindingsargv-array process executionURL and egress allowlistsJSON Schema validation of tool-call arguments

Ejemplos

  • Un agente de soporte resume un ticket cuyo cuerpo contiene una imagen markdown apuntando al host de un atacante. La consola la renderiza, el navegador pide la URL, y la conversación se filtra sin que nadie pulse nada. Deshabilitar HTML crudo y permitir solo ciertos hosts de imagen lo cierra.
  • Un agente de código propone un comando de shell. El ejecutor pasa la cadena a un shell, así que un nombre de fichero con un separador de comandos se ejecuta. Pasar un array argv elimina por completo el paso de parseo del shell.
  • El resumen de un agente se coloca en el prompt de un segundo agente. El resumen contenía instrucciones y el segundo las siguió. Delimitar el fragmento no confiable y etiquetarlo como dato es la frontera en ese caso.

KPIs

Cobertura de destinos
Proporción de consumidores conocidos de la salida del modelo con codificador en la frontera. Por debajo del 100% el control tiene un agujero concreto, y nombrar el destino sirve más que un porcentaje que lo promedia.
Tasa de neutralización de payloads
Proporción de payloads de prueba por destino neutralizados en la frontera. El objetivo es 100%: cualquier otra cifra nombra un destino que arreglar, no un número que mejorar.
Tiempo hasta cubrir un destino nuevo
Cuánto pasa desde que una integración entra en producción hasta que existe su codificador. Mide si el inventario sigue el ritmo del producto, no si acertó una vez.
Volumen de disparos en la frontera
Con qué frecuencia los codificadores neutralizan algo en producción. Un cero plano suele significar que el codificador no está en el camino, no que no llegue nada hostil.

Modos de fallo observados

  • Exfiltración silenciosa por marcado renderizado: una imagen o un enlace provocan una petición sin pulsar, así que los datos salen sin acción del usuario y sin ningún error que alguien fuera a notar.
  • Inyección de segundo orden: la salida se codifica bien para la consola, se almacena y luego se renderiza en otro sitio —un visor de logs, un ticket, un correo resumen— donde esa codificación no aplica.
  • El codificador que solo está en el camino feliz. Las ramas de error, los reintentos y los repliegues emiten el mismo texto por otra ruta de código que no lleva nada.
  • Doble codificación. Dos capas escapan correctamente, los usuarios ven entidades escapadas en el producto, y el arreglo quita la capa equivocada.

Lecciones aprendidas

  • Enumera destinos antes de escribir filtros. Casi todo fallo real aquí es un consumidor que nadie listó, no un codificador mal escrito.
  • El destino que se olvida rara vez es una pantalla. Es un webhook, un visor de logs, una exportación o un correo resumen: algún sitio al que va la salida sin que nadie lo piense como un renderizado.
  • La codificación gana a la detección porque no necesita reconocer el ataque. Una lista negra de payloads es una descripción de los ataques que ya eran públicos.
  • No dejes que un único sanitize() se convierta en la respuesta. El nombre sugiere completitud y el comportamiento es correcto para exactamente un destino.

FAQs

¿No es lo mismo que filtrar entradas contra la inyección de prompts?
No: defienden extremos opuestos. El filtrado de entrada intenta que no persuadan al modelo, y eso depende del modelo. Esto asume que ya lo persuadieron e impide que se actúe sobre la salida, y eso no depende del modelo en absoluto. Un sistema que solo tenga lo primero falla en cuanto aparece un jailbreak nuevo.
¿Esto no es simplemente sanitization de la salida, con un saneador único para todo?
Porque la codificación es contextual. Escapar una comilla protege un renderizador HTML y no hace nada por un shell; entrecomillar para shell protege el shell y corrompe el texto mostrado. Una función compartida tiene que elegir un contexto, está mal en los demás, y parece cobertura mientras es un punto único de fallo.
El modelo es nuestro y el prompt está fijado. ¿Aun así hace falta?
Sí, si algo de lo que el modelo lee viene de fuera: una página descargada, un fichero del usuario, el resultado de una herramienta, un registro de memoria. La instrucción no tiene que llegar por tu prompt: llega por lo que el modelo lea, y que tu prompt esté fijado no restringe eso.

Referencias