Cómo probar tu API contra entradas no confiables (antes de que un atacante lo haga)

La entrada es una superficie de ataque. Construye pruebas negativas para inyecciones, cargas útiles sobredimensionadas y malformadas, convierte el esquema en un control de seguridad y ejecútalas en CI.

Ashley Innocent

Ashley Innocent

23 July 2026

Cómo probar tu API contra entradas no confiables (antes de que un atacante lo haga)

Apidog para empresas

Despliegue local

SSO & RBAC

Conforme con SOC 2

Explorar Apidog Enterprise
TL;DR: La entrada de tu API es una superficie de ataque, así que pruébala como tal. Escribe casos negativos que envíen campos sobredimensionados, tipos incorrectos, cuerpos mal formados y cadenas de inyección, y luego verifica que el endpoint responda con un 4xx y nunca con un 5xx. Convierte la validación de esquema en un control de seguridad con `additionalProperties: false`, enums y límites de longitud. Ejecuta toda la suite en CI con cada cambio. Los agentes de IA hacen esto urgente: generan y reenvían cargas útiles a la velocidad de la máquina, por lo que "cargar estos datos" que se convierte silenciosamente en "ejecutar este código" ahora escala.

<

La mayoría de las suites de pruebas demuestran que tu API funciona cuando el llamador es educado. Envías un cuerpo válido, obtienes un 200, la aserción pasa. Ese resultado no te dice casi nada sobre lo que sucede cuando el cuerpo es hostil. Una entrada no confiable es cualquier dato que tu endpoint no generó por sí mismo: cuerpos de solicitud, cadenas de consulta, encabezados, cargas de archivos, cargas útiles de webhooks y el JSON que un agente de IA ensambla sobre la marcha. Todo ello merece la misma suposición, que es que alguien eventualmente enviará la peor versión posible.

En julio de 2026, Hugging Face describió un incidente de seguridad cuyo vector de entrada eran datos, no una contraseña robada. Cubrimos las lecciones de esa brecha por separado; esta guía es la parte práctica. Construirás pruebas que envían el tipo de entrada que envía un atacante, y luego las ejecutarás automáticamente en cada cambio. Las categorías se alinean con el Top 10 de Seguridad de API de OWASP, que vale la pena tener abierto en una pestaña. Apidog es una forma de diseñar el contrato y ejecutar estas pruebas, pero las ideas se mantienen en cualquier framework que ya utilices.

La entrada es una superficie de ataque, no un campo de formulario

La validación a menudo se trata como una cortesía de la experiencia de usuario: detecta el correo electrónico vacío, muestra un borde rojo, y sigue adelante. Ese enfoque es el problema. Cada campo que tu API acepta es una promesa que el llamador puede romper, y cada promesa rota es un camino hacia tu lógica. Un parámetro limit que esperabas que fuera un entero pequeño se convierte en 999999999. Un filename que esperabas que fuera una sola palabra se convierte en ../../etc/passwd. Un objeto config que esperabas que contuviera configuraciones se convierte en un conjunto de instrucciones.

Las pruebas de seguridad no son una disciplina separada que se añade al final. Es el mismo tipo de pruebas negativas que ya conoces, dirigidas a los campos con más probabilidades de causarte daño. Si adquieres el hábito de preguntar "¿qué es lo peor que cabe en este campo?", ya estás a medio camino de las prácticas de nuestra guía de mejores prácticas de seguridad de API. El resto de este artículo convierte esa única pregunta en pruebas concretas que puedes ejecutar.

Cómo "cargar estos datos" se convirtió en "ejecutar este código"

El incidente de Hugging Face es un claro ejemplo de por qué la entrada merece esta atención. Hugging Face dijo que el vector de entrada fueron conjuntos de datos maliciosos: un conjunto de datos manipulado activó un cargador de conjuntos de datos de código remoto, y una inyección de plantilla residía dentro de una configuración de conjunto de datos. Puedes leer el propio relato de la compañía en su informe de incidentes de seguridad.

Detente a pensar en la forma de ese fallo. Un endpoint aceptó algo descrito como datos. La carga de esos datos ejecutó una ruta de código que podía ejecutar instrucciones controladas por el atacante. "Cargar estos datos" se convirtió en "ejecutar este código". La inyección de plantilla es la misma historia a menor escala: un valor de configuración que se suponía que era texto inerte fue evaluado, por lo que el texto se convirtió en ejecución.

La conclusión no es "Hugging Face cometió un error raro". Es que cualquier endpoint que acepte un nombre de cargador, un formato, una plantilla, un objeto serializado o un blob de configuración está aceptando instrucciones, lo hayas pretendido o no. Si nunca escribiste una prueba que envíe una configuración hostil a ese endpoint, nunca verificaste realmente la suposición de que permanece inerte. Esa suposición no probada es toda la vulnerabilidad.

Validación de esquema como control de seguridad

El control más barato que puedes añadir es un esquema estricto en el borde. Un esquema no es solo documentación. Cuando rechazas cualquier cosa que no coincide, el esquema se convierte en un filtro que se ejecuta antes de que tu lógica de negocio vea la solicitud. JSON Schema te proporciona las primitivas para hacer ese filtro estricto.

Aquí hay un esquema para la configuración del conjunto de datos de la historia, escrito para que la mayoría de las entradas hostiles nunca lleguen al código de la aplicación:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["loader", "name"],
  "properties": {
    "loader": { "enum": ["csv", "json", "parquet"] },
    "name": { "type": "string", "maxLength": 128, "pattern": "^[\\w .-]+$" },
    "rows": { "type": "integer", "minimum": 0, "maximum": 1000000 }
  }
}

Léelo como cuatro defensas separadas. `additionalProperties: false` rechaza un campo `template` contrabandeado directamente, para que un atacante no pueda añadir uno. El enum `loader` significa que `pickle://` o cualquier cargador de código remoto simplemente no es un valor válido. `maxLength` anula la cadena de varios megabytes destinada a agotar la memoria. El `pattern` en `name` rechaza los caracteres `{{` y `'; DROP TABLE` antes de que viajen más lejos. Ninguna de estas líneas sabe sobre atacantes. Solo aceptan el conjunto reducido de entradas que realmente admites, y esa estrechez es la propiedad de seguridad.

La validación de contratos como esta no detecta todos los exploits, y ningún esquema lo hará. Lo que sí cierra es una categoría específica y común: el error de "nunca comprobamos lo que este endpoint acepta". Esa categoría es donde comienza un número sorprendente de brechas.

Pruebas negativas: demuestra que el endpoint dice que no

Las pruebas de "ruta feliz" afirman que una entrada buena produce una salida buena. Las pruebas negativas afirman que una entrada mala produce un rechazo controlado. La distinción importa porque un rechazo es una característica: un 400 con un error claro es tu API defendiendo su límite. Un 500 es tu API perdiendo el control.

Construye casos negativos de la misma manera cada vez. Para cada campo, escribe lo que debe rechazar: tipo incorrecto, ausente cuando es requerido, presente cuando está prohibido, demasiado largo, fuera de rango, y las cadenas de inyección que se ajustan a su formato. Luego, afirma dos cosas sobre la respuesta. Primero, el estado es un 4xx, usualmente 400 o 422. Segundo, el estado nunca es un 5xx. Un 500 significa que tu entrada hostil llegó a un código que no estaba preparado para ella, que es exactamente el alcance que un atacante desea. Nuestra lista de verificación de pruebas de seguridad de API tiene una lista inicial campo por campo que puedes adaptar.

Una regla mantiene esto honesto: afirma el comportamiento, no el texto del error. Si afirmas que el mensaje dice "cargador inválido", una refactorización inofensiva romperá tu prueba y enseñará al equipo a relajarla. Afirma el código de estado, y donde puedas, afirma que no hubo ningún efecto secundario en absoluto.

Las clases de inyección que merecen una prueba dedicada

Algunas familias de inyección aparecen con la suficiente frecuencia como para que cada una merezca casos de prueba permanentes, no una verificación manual única. No es necesario ser exhaustivo aquí. Necesitas un caso de prueba por clase para que una regresión falle ruidosamente. Las herramientas que ejecutan la detección automatizada de vulnerabilidades de API pueden ampliar la cobertura más tarde, pero un puñado de casos escritos a mano detecta primero los agujeros obvios.

Tamaño excesivo, malformación y confusión de tipo de contenido

No toda entrada hostil es una cadena inteligente. Algunas son simplemente demasiado grandes o tienen una forma incorrecta, y a menudo rompen los analizadores antes de que tu lógica de validación se ejecute.

Envía una carga útil sobredimensionada: un solo campo que contenga cinco megabytes de un solo carácter, o un array JSON con un millón de elementos. Una API saludable impone un límite de tamaño de cuerpo y devuelve 413 en lugar de asignar memoria hasta que colapsa. Envía también cuerpos mal formados: JSON truncado, una coma final, o JSON anidado mil niveles de profundidad para probar el agotamiento de la pila. La respuesta correcta es un 400 rápido, no un trabajador bloqueado.

La confusión de tipo de contenido es la silenciosa. Declara Content-Type: application/json pero envía XML, o declara application/xml y envía una carga útil con una entidad externa para sondear XXE. Dale la vuelta y envía JSON como text/plain para ver si un analizador laxo lo acepta de todos modos. Cada discrepancia prueba si tu servidor confía en el encabezado, confía en el cuerpo, o verifica que los dos estén de acuerdo. Debe requerir acuerdo antes de analizar cualquier cosa.

Por qué los agentes de IA aumentan los riesgos

Todo lo anterior era cierto antes de que existieran los agentes. Los agentes cambian el volumen y la velocidad. Un atacante humano teclea una solicitud hostil a la vez. Un agente de IA genera y reenvía cargas útiles a la velocidad de la máquina, y felizmente construirá entradas que una persona nunca se molestaría en intentar.

Tres propiedades empeoran esto. Los agentes sintetizan entradas, por lo que producen valores de campo que ningún humano escribió y ninguna prueba anticipó. Los agentes reintentan y encadenan llamadas, por lo que un único documento de origen envenenado puede convertirse en miles de solicitudes hostiles contra tu endpoint en segundos. Y los agentes reenvían datos en los que se les dijo que confiaran, que es como una carga útil oculta en un conjunto de datos o un webhook se convierte en una solicitud real a tu API. El patrón de Hugging Face, donde "cargar estos datos" se convierte en "ejecutar este código", es precisamente el tipo de instrucción que un agente llevará a través de un límite de confianza sin darse cuenta. Nuestra nota sobre la inyección de prompts para equipos de API profundiza en ese traspaso. La defensa no cambia; simplemente tiene que ser automática, porque no puedes revisar el tráfico de los agentes manualmente.

Construye la suite negativa y ejecútala en CI con cada cambio

Convierte los casos anteriores en una suite que se ejecute en cada solicitud de extracción. Aquí tienes una versión parametrizada compacta en pytest que accede a un endpoint de staging y afirma un rechazo controlado:

import httpx
import pytest

BASE = "https://staging.internal/v1"

HOSTILE_CONFIGS = [
    {"loader": "pickle://s3/models/payload.pkl", "format": "auto"},  # remote-code loader
    {"loader": "csv", "name": "{{ 7*7 }}"},                          # template injection
    {"loader": "csv", "name": "{{ config.__class__ }}"},             # object traversal
    {"loader": "csv", "filter": "1); DROP TABLE datasets;--"},       # SQL injection
    {"loader": "csv", "name": "A" * 5_000_000},                      # oversized field
]

@pytest.mark.parametrize("config", HOSTILE_CONFIGS)
def test_dataset_config_is_refused(config):
    r = httpx.post(f"{BASE}/datasets", json={"config": config}, timeout=10)
    assert r.status_code in (400, 413, 422), r.text  # a boundary that says no
    assert r.status_code < 500, "5xx means the payload reached logic it should not"
    assert "49" not in r.text, "template rendered: server-side template injection"

Conéctalo a tu CI para que bloquee las fusiones. Un trabajo mínimo de GitHub Actions lo hará:

name: api-abuse-tests
on: [push, pull_request]
jobs:
  negative-input:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pytest tests/negative_input.py -q

Aquí es donde una herramienta que prioriza el esquema se gana su lugar. En Apidog, diseñas el endpoint según un contrato OpenAPI, de modo que cada solicitud y respuesta se verifica contra ese contrato mientras realizas las pruebas. Puedes guardar escenarios negativos junto a los de "ruta feliz": campos sobredimensionados, tipos incorrectos y las cadenas de inyección mencionadas anteriormente, cada uno con una afirmación de que el estado es un 4xx. Luego ejecutas los mismos escenarios en CI a través de la CLI de Apidog, de modo que un cambio que relaja silenciosamente la validación falla la compilación en lugar de desplegarse. Si quieres probarlo, Descarga Apidog y añade un escenario negativo a un endpoint que ya tengas.

Sé claro sobre el límite. Apidog es una herramienta de diseño, prueba, simulación y documentación. No ejecuta un firewall de aplicaciones web, no filtra el tráfico en vivo ni reemplaza un SIEM, y la validación de contratos durante las pruebas no detectará todos los exploits. Lo que sí hace bien es hacer explícito el contrato y mantener la honestidad sobre lo que un endpoint acepta, para que la categoría de "nunca verificamos" deje de ser lo que te sorprende en producción.

Preguntas frecuentes

¿Cuál es la diferencia entre las pruebas negativas y el fuzzing? Las pruebas negativas envían un conjunto curado de entradas incorrectas que elegiste a propósito, una por cada fallo que te importa. El fuzzing envía grandes volúmenes de entradas aleatorias o mutadas para encontrar casos en los que no pensaste. Comienza con las pruebas negativas porque son rápidas, deterministas y fáciles de ejecutar en CI. Añade fuzzing cuando quieras una amplitud más allá de tu propia imaginación.

¿Deberían ejecutarse estas pruebas en producción? No. Ejecútalas en staging o en un entorno aislado. Algunos casos, como la carga útil sobredimensionada o una sonda de inyección de comandos, están diseñados para estresar el sistema, y algunos podrían mutar datos si existe un error. Un entorno de prueba dedicado permite que las pruebas sean agresivas sin ningún riesgo para los usuarios reales.

¿Un firewall o WAF no detectaría esto de todos modos? Un WAF es una defensa en profundidad útil, pero no es un sustituto de la aplicación que rechaza entradas incorrectas. Las reglas pueden ser eludidas y un WAF no puede conocer tu lógica de negocio. El objetivo de estas pruebas es demostrar que el propio endpoint dice que no, para que no dependas de un filtro que no controlas completamente.

¿Cuántos casos negativos son suficientes por endpoint? Apunta a un caso por campo por cada clase de fallo que pueda sufrir: tipo incorrecto, fuera de rango, demasiado largo, campo prohibido y cualquier cadena de inyección que se ajuste a su formato. Esto suele ser un puñado de casos por endpoint, no cientos. La cobertura de las clases importa más que el recuento bruto.

¿La validación de esquema detiene la inyección por completo? No, y no debería ser tu única capa. Un esquema estricto elimina una gran parte de las entradas mal formadas y sobredimensionadas, y bloquea campos inesperados, pero un valor puede ser válido según el esquema y seguir siendo una inyección SQL o de plantilla. Mantén las consultas parametrizadas, la deserialización segura y la codificación de salida en su lugar, y utiliza el esquema para reducir la superficie que esas capas tienen que defender.

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs