Módulo 9 de 14

Módulo 9: Consumo sin límites y extracción del modelo

Módulo 9: Consumo sin límites y extracción del modelo

Para devolver cualquier respuesta, una aplicación con modelo de lenguaje ejecuta un cómputo que crece con lo que le pides: cuanto más largo el texto que entra y más largo el texto que sale, más caro sale procesar esa petición en concreto. La web que se construyó antes de esto casi nunca tenía ese problema. Servir una página, insertar una fila en una base de datos o comprobar una contraseña cuesta, en la práctica, lo mismo la petición número uno que la petición número diez mil. Tirar abajo esa web clásica con una denegación de servicio necesitaba volumen: muchísimas peticiones baratas para agotar algo. Contra una aplicación que responde con inferencia, el volumen ya no hace falta. Basta con una petición bien elegida, y su coste puede equivaler al de miles de las normales.

Vas a ver los cinco vectores que disparan ese coste, qué controles cortan cada uno y en qué capa viven (la pasarela que recibe la petición cruda, el orquestador que decide qué hacer con ella, o la cuenta del proveedor), qué significa que una única petición barata encadene muchas caras, y la diferencia exacta que traza NIST entre robar información sobre quién estaba en el entrenamiento de un modelo y robar el modelo en sí. Termina con la parte incómoda: quien publica una API abierta hacia un modelo acepta un grado de exposición que ninguna arquitectura elimina del todo, solo lo encarece o lo ralentiza.

Qué aprenderás

  • Por qué una aplicación con modelo tiene un coste marginal por petición que la web tradicional no tenía, y los cinco vectores que lo disparan: entradas largas, contexto inflado, bucles de agente, herramientas caras y peticiones concurrentes.
  • Qué cubre exactamente LLM10:2025 de OWASP, con sus siete ejemplos de vulnerabilidad y sus mitigaciones, citadas del propio documento.
  • Los controles concretos (cuotas por usuario y por clave, límite de longitud de entrada y de contexto, presupuesto por sesión y por día, límite de iteraciones del agente, tiempos de espera, degradación controlada y alertas de gasto) y en qué capa de la aplicación vive cada uno.
  • Qué es la amplificación: cómo una única petición de entrada barata puede disparar N búsquedas y M llamadas al modelo sin que nadie lo haya pedido en esos términos.
  • La diferencia exacta que traza la taxonomía de NIST AI 100-2 E2025 entre inferencia de pertenencia y extracción de modelo, y qué expone de verdad una API abierta según cómo la configures.
  • Cómo el uso masivo de una aplicación puede robar su lógica de negocio, no solo el modelo que hay detrás.
  • Qué parte de este riesgo es irreducible y por qué el control realista es económico y de detección, no una garantía matemática.
  • Cómo se detecta el abuso en producción a partir de patrones de uso, y por qué ese trabajo conecta con el de un centro de operaciones de seguridad.

Por qué una aplicación con modelo tiene un coste marginal que la web tradicional no tenía

Una petición HTTP a una aplicación web clásica activa, casi siempre, trabajo de coste fijo: parsear la petición, hacer una consulta indexada, montar una respuesta. El tamaño del texto que manda el usuario apenas afecta a cuánto cuesta atenderlo. Una petición de inferencia es distinta en la raíz: el modelo procesa cada token de entrada y genera cada token de salida con cómputo real, así que el coste crece con la longitud de ambas. OWASP lo resume en la descripción de LLM10:2025: Consumo ilimitado, las altas demandas computacionales de los modelos de lenguaje, sobre todo en la nube, los hacen vulnerables a la explotación de recursos y al uso no autorizado.

Esto no hace inmune a la web tradicional: el problema de fondo, un sistema que no limita cuántos recursos consume una sola petición, tiene su propia entrada en el catálogo de debilidades de software desde hace más de una década, CWE-400, «Uncontrolled Resource Consumption», y el OWASP Top 10 clásico de aplicaciones web ya cubre fallos donde una petición mal acotada agota memoria o conexiones. Lo que cambia con un modelo de por medio es el multiplicador: esa misma petición sin límite deja de ser una molestia que un firewall de aplicación corta con facilidad y pasa a ser cómputo real pagado por ti, con el proveedor del modelo facturando por uso en la mayoría de los casos.

Los cinco vectores

Cinco patrones concentran casi todos los escenarios de consumo descontrolado en producción.

  • Entradas largas: un atacante envía peticiones de longitud variable, algunas enormes, para forzar al sistema a procesar más texto del previsto. OWASP lo llama, en su primer ejemplo de vulnerabilidad, «inundación de entrada de longitud variable».
  • Contexto inflado: en vez de una entrada larga de una vez, el ataque llena la ventana de contexto poco a poco, acumulando mensajes en el historial hasta agotar el límite de tokens que el modelo admite por llamada. Cada modelo tiene ese límite fijado: Llama 3.2, en sus variantes de 1B y 3B, admite 128k tokens de contexto según su ficha técnica oficial. Es un techo generoso, pero sigue siendo un techo, y una conversación que lo alcanza fuerza a truncar, resumir o rechazar, con coste de cómputo en los tres casos.
  • Bucles de agente: cada iteración de un agente que razona y actúa es, como mínimo, una llamada más al modelo. El módulo 6 ya desarrolló por qué un bucle sin límite de iteraciones es peligroso por la acción que ejecuta; aquí el mismo bucle es peligroso por el gasto que acumula, aunque cada acción individual sea inofensiva.
  • Herramientas caras: si un agente puede invocar una búsqueda de pago, otra API facturada por uso, o una operación de cómputo pesado, cada llamada a esa herramienta arrastra su propio coste, encima del coste de la llamada al modelo que decidió invocarla.
  • Peticiones concurrentes: un solo usuario, o una cuenta comprometida, que abre muchas conexiones a la vez multiplica el efecto de cualquiera de los cuatro vectores anteriores. OWASP lo llama «solicitudes repetidas» en sus escenarios de ataque, y es el que menos depende de que una petición individual sea sofisticada.

LLM10:2025: qué cubre Consumo ilimitado según OWASP

OWASP define el consumo ilimitado como el proceso en el que una aplicación de modelo de lenguaje permite a los usuarios realizar inferencias excesivas y descontroladas, con riesgos de denegación de servicio, pérdidas económicas, robo de modelo y degradación del servicio. La entrada agrupa siete ejemplos de vulnerabilidad y quince estrategias de mitigación en el documento oficial; la tabla conecta cada ejemplo con la mitigación que más directamente lo corta.

Ejemplo de OWASP En qué consiste Mitigación principal citada
Inundación de entrada de longitud variable Numerosas entradas de tamaño variable que explotan ineficiencias de procesamiento hasta dejar el sistema sin responder Validación de entradas: límites de tamaño razonables aplicados de forma estricta
Denegación de cartera (DoW, Denial of Wallet) Un alto volumen de operaciones que explota el modelo de coste por uso de un servicio en la nube hasta hacerlo financieramente insostenible Limitación de velocidad y cuotas de usuario, más gestión activa de la asignación de recursos
Desbordamiento continuo de entradas Envío continuado de entradas que exceden la ventana de contexto, degradando el servicio Tiempos de espera y limitación de procesamiento en operaciones de alto consumo
Consultas de consumo intensivo de recursos Consultas inusualmente exigentes, con secuencias complejas o patrones de lenguaje muy elaborados, que agotan recursos del sistema Gestión de la asignación de recursos y técnicas de aislamiento frente a recursos de red y APIs internas
Extracción de modelo a través de API Consultas diseñadas con técnicas de inyección de prompts para recopilar salidas suficientes para replicar un modelo parcial o crear un modelo en la sombra Limitar la exposición de logits y logprobs, más controles de acceso
Replicación funcional de modelo Usar el modelo objetivo para generar datos de entrenamiento sintéticos y aplicar fine-tuning sobre ellos, creando un equivalente funcional sin necesidad de extraer pesos Marca de agua sobre las salidas, más inventario centralizado de modelos en producción
Ataques de canal lateral Explotación de las técnicas de filtrado de entrada del sistema para obtener pesos del modelo e información de arquitectura Técnicas de aislamiento, restringiendo el acceso del modelo a recursos de red y servicios internos

El propio documento remite, en marcos relacionados, a CWE-400 y a un grupo de técnicas de MITRE ATLAS bajo la táctica «AI Model Access» (AML.TA0000): AML.T0024 (exfiltración vía API de inferencia), AML.T0029 (denegación de servicio de IA) y AML.T0034 (recolección de coste). Como ya viste en el módulo 2 sobre nombres que cambian con el tiempo en ATLAS, OWASP cita AML.T0029 y AML.T0024 con sus nombres antiguos («Denial of ML Service», «Exfiltration via ML Inference API»); en el YAML vigente ambas llevan «AI» donde antes decía «ML», el mismo patrón de renombrado ya documentado en ese módulo.

Un incidente real ilustra la denegación de cartera sin que hiciera falta ningún modelo de lenguaje. En agosto de 2023, un atacante usó un token de administrador filtrado por un ingeniero de Sourcegraph para entrar en el panel de administración, y desde ahí montó una aplicación proxy que dejaba a cualquiera crear una cuenta gratuita y pedirle que le inflara su límite de peticiones. El equipo de seguridad detectó el 30 de agosto, a las 13:25 UTC, un aumento de uso que calificó de aislado e inorgánico: la aplicación proxy ya había recibido cerca de dos millones de visitas de usuarios usando el límite ajeno. La respuesta fue revocar la cuenta maliciosa, rotar las claves de licencia de los clientes y reducir de forma temporal los límites de la comunidad. El caso empezó con una credencial filtrada, no con un ataque contra el modelo, pero el efecto final, cuotas manipuladas hasta un coste insostenible, es justo lo que OWASP describe como denegación de cartera.

Controles concretos y en qué capa de la aplicación viven

Los controles de este módulo se reparten en tres capas, y conviene distinguirlas porque cada una ve cosas distintas. La pasarela (un proxy inverso, un API gateway, o el punto de entrada de tu propio backend) ve la petición HTTP cruda antes de que nadie la interprete como un prompt: es la capa más barata para cortar algo, porque rechaza sin gastar ni un token de inferencia. El orquestador es la lógica de tu aplicación que arma el contexto, decide qué herramientas invocar y mantiene el estado de la sesión: ve la semántica de lo que está pasando, así que puede aplicar límites que dependen de la historia acumulada. La cuenta del proveedor del modelo es el último cinturón, el que existe aunque tu propio código tenga un fallo, porque lo aplica un sistema que no controlas.

Cuotas por usuario y por clave de API

Este control vive en la pasarela. OWASP recomienda aplicar limitación de velocidad y cuotas de usuario para restringir cuántas peticiones puede hacer una única entidad de origen en un periodo dado, y el OWASP API Security Top 10, en su entrada API4:2023, lo formula igual: un límite de cuántas veces un cliente puede interactuar con la API en un plazo dado, ajustado a la necesidad real del negocio, no a un número redondo elegido sin pensar. MITRE ATLAS recoge la misma idea como mitigación AML.M0004, «Restrict Number of AI Model Queries»: limitar el número total y el ritmo de consultas que puede hacer un usuario.

La clave de API es la unidad natural de este control: sobrevive a cambios de sesión y atribuye consumo a quien lo generó, algo que una cuota solo por dirección IP no consigue en cuanto hay un proxy o una red corporativa de por medio.

Límite de longitud de entrada y de contexto

Este control se reparte entre las dos primeras capas. La pasarela aplica un límite barato basado en caracteres o bytes antes de tocar el modelo, sin necesidad de conocer el tokenizador exacto que usa detrás. El orquestador aplica el límite fino, contando tokens de verdad o recortando el historial de la conversación, porque solo él conoce cuánto contexto acumulado lleva ya esa sesión.

Hay una asimetría que conviene tener presente: con un modelo servido por una API como la de Ollama, el recuento exacto de tokens de una petición (prompt_eval_count) y de la respuesta (eval_count) solo llega después de que la llamada ya se pagó, dentro del propio objeto de respuesta. Un límite previo por caracteres se aplica antes de gastar nada, pero es solo una aproximación; un presupuesto de tokens de verdad solo puede hacerse cumplir revisando el acumulado tras cada llamada y decidiendo si la siguiente se permite. El ejercicio de este módulo construye las dos capas por separado.

Presupuesto máximo por sesión y por día

Este control vive, sobre todo, en el orquestador: un objeto de sesión que acumula tokens o coste según avanza la conversación, y corta antes de la siguiente llamada si el acumulado ya supera el tope fijado. El tope diario suele vivir un nivel más arriba, agregando sesiones por usuario o por clave, y a menudo se apoya en la propia cuenta del proveedor: un límite de gasto configurado ahí actúa como red de seguridad final si el presupuesto por sesión falla o alguien encuentra una vía para esquivarlo. La propia guía de prevención de API4:2023 recomienda justo eso, configurar límites de gasto por proveedor con alertas de facturación cuando el límite no se puede fijar de forma dura.

Límite de iteraciones del agente

El módulo 6 de este curso ya cubrió los parámetros de límite de bucle de AgentExecutor de LangChain y de recursion_limit de LangGraph, con el enfoque puesto en qué daño evita cortar un bucle que repite una acción. Aquí el mismo límite se lee con otra lente: cada iteración es, como mínimo, una llamada de pago al modelo, así que un límite de iteraciones es también un límite de gasto, incluso si ninguna acción individual del bucle es peligrosa por sí sola. Vive en el orquestador, igual que el presupuesto de sesión, y conviene que ambos trabajen juntos: el que salta primero es el que corta.

Tiempos de espera, degradación controlada y alertas de gasto

Un tiempo de espera corta una petición que se alarga más de lo razonable, y conviene fijarlo en más de una capa: la pasarela con un timeout de conexión, el orquestador con un timeout por paso del bucle. La degradación controlada, la novena estrategia de OWASP, consiste en diseñar el sistema para que se degrade de forma progresiva bajo carga pesada en vez de fallar por completo: un modelo más pequeño, una respuesta cacheada, o un mensaje de «vuelve a intentarlo» en vez de dejar que la cola crezca sin control. Las alertas de gasto son transversales a las tres capas, y se apoyan en el mismo registro exhaustivo que pide la séptima estrategia de OWASP y que la mitigación AML.M0024 de ATLAS, «AI Telemetry Logging», describe como registro de entradas y salidas de los modelos desplegados, junto con los pasos intermedios y el uso de herramientas cuando hay un agente de por medio.

Control Capa principal Qué corta
Cuota por usuario y por clave de API Pasarela Volumen de peticiones de una misma entidad en un periodo dado
Límite de longitud de entrada (caracteres o bytes) Pasarela Entradas anormalmente largas, antes de gastar cómputo de inferencia
Límite de contexto acumulado Orquestador Conversaciones que crecen hasta agotar la ventana del modelo
Presupuesto por sesión Orquestador Una sesión concreta que se dispara en coste, aunque cada llamada sea normal
Presupuesto o límite de gasto diario Orquestador y cuenta del proveedor Acumulado agregado de muchas sesiones o usuarios en el mismo periodo
Límite de iteraciones del agente Orquestador Bucles que no avanzan, o que un tercero fuerza a repetirse
Tiempos de espera Pasarela y orquestador Peticiones o pasos individuales que se alargan más de lo razonable
Degradación controlada Orquestador Que la aplicación entera caiga en vez de responder peor bajo carga
Alertas de gasto y registro exhaustivo Transversal Que el abuso pase inadvertido hasta ver la factura

Amplificación: cuando una petición barata desencadena muchas caras

El vector más difícil de ver a simple vista no es el de una entrada enorme, es el de una entrada pequeña que la propia aplicación convierte en muchas llamadas caras. Un patrón habitual en aplicaciones con recuperación de contexto es descomponer una pregunta compleja en varias subpreguntas, buscar cada una por separado y resumir cada resultado con una llamada al modelo. Si nada acota cuántas subpreguntas puede generar ese primer paso, una sola pregunta se convierte en N búsquedas y N llamadas de resumen, y esa N la decide, en la práctica, el propio modelo a partir de un texto que el atacante puede influir.

def responder_pregunta_compleja_sin_limite(pregunta, buscador, modelo):
    subpreguntas = modelo.descomponer(pregunta)
    resultados = []
    for sub in subpreguntas:
        documentos = buscador.buscar(sub)
        resumen = modelo.resumir(documentos)
        resultados.append(resumen)
    return modelo.combinar(pregunta, resultados)

Nada en esta función limita cuántas subpreguntas puede devolver modelo.descomponer. MITRE ATLAS documenta justo este patrón bajo la técnica AML.T0034.002, «Agentic Resource Consumption»: un atacante puede usar inyección de prompt o el envenenamiento de los datos que consulta una herramienta para forzar llamadas computacionalmente caras que desperdician recursos y consumen el presupuesto de la API, con directivas tan simples como pedir que se resuma un texto mil veces o que se traduzca a los principales idiomas del mundo. La misma técnica describe un segundo patrón, el bucle de autodelegación: un agente al que se le da una definición recursiva, o instrucciones repetidas presentadas como peticiones separadas, delega tareas a sí mismo hasta agotar la pila o el presupuesto de la sesión.

La corrección no es sofisticada, es un tope que ninguna instrucción inyectada puede saltarse porque vive fuera del alcance del modelo:

MAX_BUSQUEDAS_POR_CONSULTA = 3

def responder_pregunta_compleja(pregunta, buscador, modelo):
    subpreguntas = modelo.descomponer(pregunta)[:MAX_BUSQUEDAS_POR_CONSULTA]
    resultados = []
    for sub in subpreguntas:
        documentos = buscador.buscar(sub)
        resumen = modelo.resumir(documentos)
        resultados.append(resumen)
    return modelo.combinar(pregunta, resultados)

El recorte con [:MAX_BUSQUEDAS_POR_CONSULTA] es el mismo principio que el límite de iteraciones del módulo 6: un techo que no depende de que el modelo «decida» comportarse, porque el código lo aplica igual sin importar qué devolvió la llamada anterior.

Extracción de modelo e inferencia de pertenencia según NIST

NIST publicó AI 100-2 E2025, su taxonomía de aprendizaje automático adversario, aprobada el 20 de marzo de 2025 y ya presentada en el módulo 2 de este curso. Dentro de los ataques a la privacidad de su taxonomía predictiva, el documento distingue cuatro categorías (reconstrucción de datos, inferencia de pertenencia, inferencia de propiedad y extracción de modelo), y las dos que interesan aquí no son intercambiables aunque el lenguaje coloquial las mezcle a menudo.

La inferencia de pertenencia, identificada como NISTAML.033, tiene un objetivo preciso: determinar si un registro o muestra de datos concreto formaba parte del conjunto de entrenamiento del algoritmo, sea estadístico o de aprendizaje automático. El propio documento señala por qué esto ya es un problema de privacidad aunque el atacante nunca vea el dato en sí: en un estudio médico sobre una enfermedad rara, saber que una persona forma parte de la muestra ya revela algo sobre su salud, sin leer ni una fila de la base de datos.

La extracción de modelo, NISTAML.031, persigue algo distinto: extraer información sobre la arquitectura y los parámetros del propio modelo, enviando consultas al servicio que lo expone. NIST cita un matiz poco intuitivo: la extracción exacta de un modelo se ha demostrado imposible en la práctica; lo que sí es alcanzable, y mucho más barato, es reconstruir un modelo funcionalmente equivalente, distinto del original pero con un desempeño similar en la tarea que resuelve. Esa distinción explica por qué OWASP separa, en su lista de ejemplos de LLM10:2025, la «extracción de modelo a través de API» (recuperar algo parecido a los pesos) de la «replicación funcional de modelo» (entrenar un sustituto con datos sintéticos generados por el original): la misma familia de riesgo con dos caminos técnicos distintos.

Aspecto Inferencia de pertenencia Extracción de modelo
Identificador NIST AI 100-2 E2025 NISTAML.033 NISTAML.031
Qué persigue el atacante Saber si un registro concreto estaba en el conjunto de entrenamiento Recuperar la arquitectura y los parámetros del modelo, o un equivalente funcional
Qué compromete si tiene éxito La privacidad de individuos cuyos datos se usaron para entrenar La propiedad intelectual y la confidencialidad del modelo servido como servicio
Acceso que suele requerir Consultas al modelo, a menudo comparando la confianza o la pérdida de la predicción Consultas repetidas al modelo, cuantas más y más ricas en información, mejor
Protección de la privacidad diferencial Ofrece una garantía formal frente a este ataque No ofrece ninguna garantía: protege los datos de entrenamiento, no los parámetros del modelo

Para aplicaciones construidas sobre un modelo ya entrenado por otro, NIST dedica una categoría propia a la privacidad en su taxonomía generativa, el compromiso de privacidad (NISTAML.03), que incluye el acceso no autorizado a datos de entrenamiento, pesos o arquitectura de un modelo, y también a la información sensible a la que accede en tiempo de inferencia, como la base de conocimiento de una aplicación con recuperación de contexto. El documento señala dos caminos hacia esa exposición: la inyección indirecta de prompt, que exfiltra información del contexto de la conversación (ya cubierta en los módulos 3 y 5), y la extracción de modelo propiamente dicha, dirigida al modelo y no al contexto.

Qué expone realmente una API abierta depende de dos cosas que tú controlas al diseñarla, según el propio NIST: el grado de control de generación que ofreces (ajustar la temperatura, añadir un sesgo a los logits) y la riqueza de lo que devuelves (con o sin probabilidades logarítmicas, con o sin varias opciones alternativas por consulta). Cuanta más de esa información entregues junto con el texto, más munición le das a quien intenta reconstruir el modelo o inferir pertenencia, y esa es la razón detrás de la segunda estrategia de mitigación de OWASP para LLM10:2025: limitar u ofuscar la exposición de logit_bias y logprobs, entregando solo lo que la aplicación necesita para funcionar.

MITRE ATLAS organiza el mismo territorio con su propio vocabulario, bajo la técnica AML.T0024, «Exfiltration via AI Inference API», con tres subtécnicas: «Infer Training Data Membership» (la inferencia de pertenencia de NIST), «Invert AI Model» (reconstrucción de datos a partir de las puntuaciones de confianza) y «Extract AI Model» (la extracción de modelo de NIST, con la nota de que el motivo suele ser evitar pagar por consulta). Tres taxonomías, con vocabulario distinto, apuntando a la misma familia de riesgo: qué se puede aprender consultando un modelo sin acceso a sus pesos.

Robo de prompts y de la lógica de negocio a través del uso masivo

La filtración del prompt de sistema es LLM07:2025 de OWASP, y su desarrollo completo corresponde al módulo 8 de este curso. Lo que interesa aquí es un ataque contiguo que no necesita ver ni una palabra del prompt de sistema para tener éxito: reconstruir, a partir de miles de pares de entrada y salida observados, la función de decisión que implementa tu aplicación, sea un clasificador de tickets de soporte, un motor de descuentos o un filtro de contenido.

El precedente es anterior a los modelos generativos y está bien documentado: en CVE-2019-20634, conocido como «Proof Pudding», unos investigadores construyeron un clasificador propio a partir de las puntuaciones que el motor de filtrado de correo de Proofpoint devolvía por cada mensaje, y lo usaron para averiguar, sin ninguna credencial robada, qué características hacían que un correo puntuara bien, y así diseñar mensajes maliciosos que esquivaban el filtro real. Nunca hizo falta acceder al modelo original: bastó con observar a gran escala cómo respondía y entrenar algo que imitara ese comportamiento. Es el mismo mecanismo que OWASP describe en su escenario de «replicación funcional de modelo» para LLM10:2025, aplicado un año antes de que existiera esa entrada del Top 10.

Trasladado a una aplicación con modelo de lenguaje, el mismo patrón funciona sobre la capa de tu propio código, no solo sobre el modelo base: si tu aplicación decide, con reglas propias más el modelo, qué tickets escalar o qué descuento ofrecer, un atacante que manda suficientes consultas y observa suficientes respuestas puede reconstruir esa lógica sin ninguna inyección de prompt ni fuga del prompt de sistema. Es un ataque de volumen puro, y por eso pertenece a LLM10:2025 más que a LLM01 o LLM07: no rompe nada, solo mira lo bastante para aprender el patrón. Los mismos controles de este módulo (cuotas, límites de velocidad, detección de patrones sistemáticos) son la defensa, porque ninguna validación de entrada distingue una consulta legítima de la consulta número cuatro mil de un barrido sistemático.

Qué se puede evitar y qué no: el límite es económico y de detección

Conviene ser sincero sobre el alcance real de todo lo anterior. NIST lo dice de forma explícita al hablar de mitigaciones contra la extracción de modelo: existen técnicas como limitar las consultas al modelo, detectar consultas sospechosas o construir arquitecturas más robustas frente a canal lateral, pero pueden ser evitadas por atacantes motivados y con recursos suficientes, y deben usarse con esa cautela en mente. No es una admisión de derrota, es una descripción precisa de qué tipo de control es este: operativo y económico, no una prueba matemática de que el ataque sea imposible.

Hay una única excepción parcial, y marca justo dónde termina lo que sí se puede demostrar. La privacidad diferencial es la herramienta más rigurosa disponible en este terreno: ofrece una garantía formal, con un parámetro de presupuesto de privacidad, sobre cuánto puede aprender un atacante con acceso a la salida de un algoritmo acerca de cualquier registro individual del conjunto de datos. Esa garantía cubre, por definición, la reconstrucción de datos y la inferencia de pertenencia. Pero el propio NIST es igual de explícito en la limitación: no ofrece ninguna garantía frente a la extracción de modelo, porque su diseño protege los datos de entrenamiento, no los parámetros del modelo entrenado. Incluso la herramienta más sólida de la caja tiene una frontera clara de lo que cubre.

Para la denegación de servicio pura o la denegación de cartera no existe siquiera ese equivalente parcial. Un límite de velocidad, una cuota o un sistema de detección de anomalías son controles que un atacante paciente, repartido en muchas cuentas de bajo perfil o con presupuesto para rotar credenciales, puede en principio sortear. Publicar una API abierta hacia un modelo se parece, en este sentido, a publicar cualquier aplicación web pública: aceptas un nivel de exposición de partida que ninguna arquitectura hace desaparecer del todo. La tarea realista no es conseguir que la extracción o el abuso de cuota sean imposibles, es conseguir que salgan caros y lentos, y notar cuanto antes que están ocurriendo.

Cómo se detecta el abuso: patrones, entropía y ritmo

Detectar esto conecta directamente con el módulo 13 de este curso, sobre detección, respuesta e IA en la sombra, y con el trabajo que hace un centro de operaciones de seguridad sobre cualquier otro tráfico anómalo. Tres señales, combinadas, dan una base razonable para distinguir uso legítimo intensivo de abuso sistemático.

Patrones de uso

Un usuario humano genera una secuencia de peticiones con variación: temas distintos, longitudes distintas, pausas irregulares. Un barrido automatizado, sea para agotar cuota o para reconstruir un modelo consulta a consulta, tiende a mostrar una estructura mucho más regular: parámetros que varían de forma sistemática, plantillas casi idénticas repetidas con pequeños cambios, o una cobertura casi exhaustiva de un espacio de entradas que ningún usuario real recorrería por curiosidad.

Entropía de las consultas

Medir cuánta diversidad real hay en el contenido de las consultas de una cuenta a lo largo del tiempo operacionaliza la idea anterior. Una cuenta con diversidad temática y léxica estable, similar a la del resto de la base de usuarios, pesa distinto en cualquier sistema de puntuación de riesgo que una cuenta cuya entropía cae de golpe porque empezó a mandar variaciones mínimas de la misma plantilla miles de veces seguidas.

Ritmo

El intervalo entre peticiones sucesivas de una misma cuenta es una señal barata de calcular y difícil de disimular del todo: un ritmo perfectamente regular, o un volumen sostenido sin las pausas que introduce la lectura humana de cada respuesta, apunta a automatización aunque el contenido de cada petición parezca inocuo.

Ninguna de las tres señales por separado prueba abuso, y las tres juntas tampoco lo prueban con certeza matemática; son la misma clase de indicio probabilístico que alimenta cualquier regla de detección. Registrar lo necesario para calcularlas es la mitad barata del trabajo; construir las reglas que convierten ese registro en una alerta accionable, sin ahogar al analista en falsos positivos, es el trabajo que desarrolla en profundidad el curso de ingeniería de detección para SOC. Cuando la alerta llega tarde y hay que reconstruir después qué extrajo exactamente un atacante a partir de ese mismo registro, ese es el trabajo del curso de DFIR.

Tabla de límites recomendados por tipo de aplicación

No hay una cifra universal de peticiones por minuto ni de tokens por sesión que sirva para toda aplicación: el límite correcto depende de qué construiste y de qué coste real tiene cada llamada. La tabla da un criterio de diseño por tipo de aplicación, no un número que copiar.

Tipo de aplicación Qué limitar primero Criterio para fijar el límite
Prototipo interno de bajo tráfico, sin cuenta por usuario Longitud de entrada y presupuesto diario agregado El mínimo que no interrumpa a quien lo está probando; aquí el riesgo es bajo, pero un límite ausente por completo es el error más frecuente en esta fase
Chat público sin cuenta (widget de atención al cliente) Cuota por sesión anónima o por huella de dispositivo, y límite de contexto acumulado Suficiente para una conversación de soporte real completa, poco margen para un barrido automatizado que abre sesiones nuevas sin parar
Agente con herramientas que tienen coste propio Límite de iteraciones y presupuesto por sesión, no solo por llamada al modelo El coste de la herramienta más cara marca el techo: una sola herramienta de pago mal acotada puede superar el coste de cien llamadas al modelo
API para partners o terceros, con clave propia Cuota por clave y alertas de gasto por cliente Pactado por contrato con cada partner, no un valor técnico elegido sin conversación de negocio de por medio
Pipeline batch interno de alto volumen Presupuesto diario y concurrencia máxima, no límite por petición individual El volumen es esperado y legítimo; lo que hay que acotar es el gasto total del proceso completo, con alertas si se sale del rango habitual

Ejercicio de laboratorio: instrumenta el presupuesto de una sesión y provoca el corte

Vas a construir las dos capas de control de longitud y de gasto que describió este módulo, y a comprobar cómo cortan una sesión que se dispara. Sigue el mismo laboratorio de los módulos anteriores: Ollama en local, con el modelo llama3.2:3b, sin coste de API de por medio.

Paso 1: validación barata de longitud de entrada, antes de gastar nada

Esta es la capa de pasarela: un chequeo por caracteres, sin tocar el modelo, que rechaza lo obviamente desproporcionado antes de que cueste un solo token.

class EntradaDemasiadoLarga(Exception):
    pass


def validar_longitud_entrada(texto, max_caracteres=4000):
    if len(texto) > max_caracteres:
        raise EntradaDemasiadoLarga(
            "la entrada tiene %d caracteres, el limite es %d" % (len(texto), max_caracteres)
        )
    return len(texto)


entradas_de_prueba = [
    "Hola, necesito ayuda con el pedido 4471.",
    "Explica en detalle la politica de devoluciones." * 3,
    "A" * 5000,
]

for texto in entradas_de_prueba:
    try:
        longitud = validar_longitud_entrada(texto, max_caracteres=4000)
        print("aceptada: %d caracteres" % longitud)
    except EntradaDemasiadoLarga as e:
        print("rechazada: %s" % e)

Este bloque no depende de ningún modelo, así que la salida de ejecutarlo tal cual es la que sigue, pegada de esta sesión sin modificar:

aceptada: 40 caracteres
aceptada: 141 caracteres
rechazada: la entrada tiene 5000 caracteres, el limite es 4000

Paso 2: presupuesto de tokens por sesión, la capa de orquestador

El chequeo de caracteres del paso 1 es una aproximación. El presupuesto de verdad se mide en tokens, y solo se conoce después de que cada llamada ya se ejecutó. Esta clase acumula el gasto real de una sesión y corta antes de aceptar la siguiente llamada si ya no queda margen.

class PresupuestoAgotado(Exception):
    pass


class PresupuestoSesion:
    def __init__(self, max_tokens_sesion):
        self.max_tokens_sesion = max_tokens_sesion
        self.tokens_consumidos = 0
        self.historial = []

    def registrar_llamada(self, prompt_eval_count, eval_count, herramienta="chat"):
        tokens_llamada = prompt_eval_count + eval_count
        tokens_tras_llamada = self.tokens_consumidos + tokens_llamada
        if tokens_tras_llamada > self.max_tokens_sesion:
            self.historial.append({
                "herramienta": herramienta,
                "tokens_llamada": tokens_llamada,
                "resultado": "cortada",
            })
            raise PresupuestoAgotado(
                "la llamada a '%s' necesita %d tokens y solo quedan %d del presupuesto de %d"
                % (herramienta, tokens_llamada, self.max_tokens_sesion - self.tokens_consumidos,
                   self.max_tokens_sesion)
            )
        self.tokens_consumidos = tokens_tras_llamada
        self.historial.append({
            "herramienta": herramienta,
            "tokens_llamada": tokens_llamada,
            "resultado": "aceptada",
        })
        return self.tokens_consumidos

Para comprobar el corte sin depender de la variabilidad de un modelo real, simula cuatro llamadas con un tamaño de entrada y salida creciente, como si fueran los prompt_eval_count y eval_count reales que devolvería Ollama tras cada una:

presupuesto = PresupuestoSesion(max_tokens_sesion=2000)

llamadas_simuladas = [
    ("consultar_estado_pedido", 320, 180),
    ("buscar_en_base_conocimiento", 410, 260),
    ("redactar_respuesta", 500, 300),
    ("redactar_respuesta", 600, 350),
]

for herramienta, entrada, salida in llamadas_simuladas:
    try:
        total = presupuesto.registrar_llamada(entrada, salida, herramienta)
        print("aceptada  %-28s tokens_llamada=%-4d acumulado=%d/%d"
              % (herramienta, entrada + salida, total, presupuesto.max_tokens_sesion))
    except PresupuestoAgotado as e:
        print("cortada   %-28s %s" % (herramienta, e))

La salida real de ejecutar este bucle en esta sesión, sin modificar, es esta:

aceptada  consultar_estado_pedido     tokens_llamada=500  acumulado=500/2000
aceptada  buscar_en_base_conocimiento tokens_llamada=670  acumulado=1170/2000
aceptada  redactar_respuesta          tokens_llamada=800  acumulado=1970/2000
cortada   redactar_respuesta          la llamada a 'redactar_respuesta' necesita 950 tokens y solo quedan 30 del presupuesto de 2000

Las tres primeras llamadas caben en el presupuesto de 2000 tokens. La cuarta, aunque cada llamada individual es razonable, necesita 950 tokens más cuando solo quedan 30, y el presupuesto la corta antes de que la aplicación llegue a pedirle nada al modelo.

Paso 3: integra el presupuesto con Ollama de verdad

El paso anterior simuló los recuentos para que el corte fuera reproducible sin depender de un modelo cargado. En una aplicación real, esos números vienen de los campos prompt_eval_count y eval_count del objeto de respuesta de la librería ollama, documentados como el número de tokens evaluados en el prompt y el número de tokens generados en la inferencia. Conecta el presupuesto a una llamada de verdad así:

from ollama import chat

def chat_con_presupuesto(mensajes, presupuesto, herramienta="chat"):
    respuesta = chat(model="llama3.2:3b", messages=mensajes)
    presupuesto.registrar_llamada(
        respuesta.prompt_eval_count, respuesta.eval_count, herramienta
    )
    return respuesta


presupuesto = PresupuestoSesion(max_tokens_sesion=1500)
mensajes = [{"role": "user", "content": "Resume en tres frases que es una inyeccion de prompt."}]

try:
    respuesta = chat_con_presupuesto(mensajes, presupuesto, herramienta="resumen")
    print(respuesta.message.content)
    print("tokens acumulados en la sesion:", presupuesto.tokens_consumidos)
except PresupuestoAgotado as e:
    print("corte:", e)

Verifica en tu entorno cuántos tokens consume tu propia pregunta, y baja después max_tokens_sesion hasta un valor que fuerce el corte con una segunda llamada dentro de la misma sesión: verás cómo PresupuestoAgotado se dispara antes de que la petición llegue siquiera a chat(), exactamente el punto donde debe cortar un presupuesto de verdad, antes de gastar, no después.

Capa del ejercicio Qué corta Qué no puede cortar
Validación de longitud por caracteres Entradas desproporcionadas antes de gastar un solo token Una conversación larga construida a base de mensajes cortos y aceptables uno a uno
Presupuesto de tokens por sesión El acumulado real de una sesión, incluidos los bucles de amplificación del vector visto antes El coste de una sola llamada ya en curso cuando se dispara: corta la siguiente, no interrumpe la que está en marcha

Preguntas frecuentes

¿Por qué no basta con las cuotas que ya ofrece el proveedor del modelo, y hace falta controlar esto también en mi propia aplicación?

Porque el proveedor solo ve tu cuenta como un todo, no distingue entre tus usuarios ni entre tus sesiones. Un límite de gasto a nivel de cuenta corta el sangrado cuando ya es tarde y afecta a todo el mundo por igual, incluidos los usuarios legítimos. Los controles de este módulo, aplicados en tu pasarela y en tu orquestador, cortan por usuario y por sesión antes de que el problema afecte a nadie más.

¿La limitación de velocidad no perjudica a los usuarios legítimos que hacen un uso intensivo pero normal?

Solo si el límite se fija sin pensar en el caso de uso real. La propia guía de API4:2023 lo dice de forma explícita: debe ajustarse a la necesidad del negocio, no copiarse de un valor por defecto. Un límite bien calibrado deja pasar sin fricción el percentil alto de tráfico legítimo y solo corta el patrón que se sale de ese rango, algo que solo se consigue mirando datos reales de uso antes de fijar el número.

¿Un atacante que quiere robar la lógica de negocio de mi aplicación necesita acceso a los pesos del modelo?

No. El caso de Proof Pudding lo demuestra sin que hubiera ningún modelo generativo de por medio: bastó con observar las puntuaciones que el sistema devolvía por cada entrada y entrenar un sustituto que aprendiera el mismo patrón. Contra una aplicación con modelo de lenguaje, el mismo ataque funciona sobre la capa de reglas y prompts que tu código añade encima del modelo, no solo sobre el modelo en sí.

¿La inferencia de pertenencia importa si mi aplicación no entrena ningún modelo propio?

Depende de qué uses como conjunto de entrenamiento en sentido amplio. Si tu aplicación hace fine-tuning sobre datos propios, o usa recuperación de contexto sobre una base documental privada, la pregunta deja de ser si un registro estaba en el entrenamiento del modelo base y pasa a ser si alguien puede inferir, a partir de las respuestas, qué documentos concretos hay en tu base de conocimiento, una variante de la misma familia de riesgo que cubrió el módulo 5 sobre control de acceso al conocimiento recuperado.

¿Basta con un límite de tokens por sesión para estar protegido frente al consumo ilimitado?

No por sí solo. Un límite de sesión no evita que un atacante abra miles de sesiones nuevas, cada una dentro del límite individual. Necesita completarse con una cuota agregada por usuario o por clave de API que mire el total a lo largo del tiempo, y con la capa de detección de patrones de este módulo para cuando alguien reparte el abuso entre muchas identidades.

¿Cómo distingo un pico de tráfico legítimo (una promoción, una mención en prensa) de un intento de denegación de cartera?

La diferencia suele estar más en la diversidad del contenido que en el volumen puro. Un pico legítimo trae preguntas distintas, de usuarios distintos, con la variación natural de personas reales escribiendo con sus propias palabras: la entropía de las consultas se mantiene. Un intento de denegación de cartera, aunque también sea un pico de volumen, suele mostrar patrones mucho más repetitivos o sistemáticos, y ese es justo el tipo de señal que este módulo conecta con la detección del módulo 13.