Aikido

¿Por qué se debería evitar la recursión sin protección de profundidad?

Riesgo de bug

Regla
Evitar la recursividad sin profundidad protección.
Recursividad sin adecuada profundidad que limite riesgos apilamiento
desbordamiento y crea DoS vulnerabilidades a entradas
entrada. Recursividad con profundidad profundidad y y límites
los límites comprobación es aceptable.

Compatibilidad con idiomas: 45+

Introducción

Las funciones recursivas sin límites de profundidad pueden agotar la pila de llamadas (call stack), provocando fallos. Entradas maliciosas, como objetos JSON profundamente anidados o estructuras de datos cíclicas, pueden desencadenar intencionadamente una recursión ilimitada. Una única solicitud manipulada puede bloquear su servicio al exceder los límites de la pila, creando una vulnerabilidad de denegación de servicio trivial de explotar.

Por qué es importante

Implicaciones de seguridad (ataques DoS): Los atacantes pueden elaborar entradas que desencadenen una recursión profunda, bloqueando su aplicación. Las estructuras de datos JSON, XML o enlazadas profundamente anidadas son vectores de ataque comunes. Una única solicitud maliciosa agota la pila, interrumpiendo todo el servicio para todos los usuarios.

Estabilidad del sistema: Los errores de desbordamiento de pila (stack overflow) bloquean el proceso inmediatamente sin una degradación elegante. En producción, esto se traduce en solicitudes perdidas, transacciones interrumpidas e indisponibilidad del servicio. La recuperación requiere reiniciar toda la aplicación.

Agotamiento de recursos: La recursión ilimitada consume memoria de pila de forma exponencial. Cada llamada recursiva añade un marco de pila, y las cadenas de recursión profunda pueden consumir megabytes de memoria. Esto afecta a otros procesos en el mismo servidor y puede desencadenar condiciones de falta de memoria.

Ejemplos de código

❌ No conforme:

function processNestedData(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    const result = {};
    for (const key in obj) {
        result[key] = processNestedData(obj[key]);
    }
    return result;
}

Por qué es incorrecto: La ausencia de límite de profundidad permite a los atacantes enviar objetos profundamente anidados que exceden los límites de la pila. Entradas como {a: {a: {a: {...}}}} anidado a 10.000 niveles de profundidad provoca el cierre inesperado de la aplicación por desbordamiento de pila. La función recurre ciegamente sin comprobar la profundidad.

✅ Conforme:

function processNestedData(obj, depth = 0, maxDepth = 100) {
    if (depth > maxDepth) {
        throw new Error('Maximum nesting depth exceeded');
    }

    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    const result = {};
    for (const key in obj) {
        result[key] = processNestedData(obj[key], depth + 1, maxDepth);
    }
    return result;
}

¿Por qué esto importa? El profundidad máxima el parámetro limita la recursión a 100 niveles, evitando el desbordamiento de pila. Este límite es lo suficientemente alto para estructuras de datos anidadas legítimas (la mayoría de los datos del mundo real rara vez superan los 10-20 niveles) mientras que es lo suficientemente bajo para detener los ataques antes de consumir una cantidad significativa de memoria de pila. Una entrada maliciosa profundamente anidada lanza un error en lugar de bloquear la aplicación. La comprobación de profundidad se realiza antes del procesamiento, fallando rápidamente cuando se superan los límites.

Conclusión

Añade parámetros de profundidad a todas las funciones recursivas que procesan datos externos. Establece profundidades máximas razonables basándose en la complejidad esperada de la estructura de datos. Lanza errores o devuelve valores predeterminados cuando se excedan los límites de profundidad en lugar de provocar un fallo.

Preguntas frecuentes

¿Tiene preguntas?

¿Cuál es una profundidad máxima de recursión razonable?

Depende de tus estructuras de datos. Para el análisis de JSON o el recorrido de árboles, 100-1000 niveles es razonable. La mayoría de las estructuras de datos legítimas no superan los 10-20 niveles. Establece límites basados en tu dominio, pero siempre ten límites. Monitoriza la producción para ver las profundidades reales y ajusta en consecuencia.

¿Cómo convierto recursión a iteración?

Utilice pilas o colas explícitas. Reemplace las llamadas recursivas con bucles que empujen elementos a una pila, luego los extraigan y procesen. Esto le da control total sobre el uso de memoria y la profundidad. Para el recorrido de árboles, la iteración en anchura o en profundidad con estructuras de datos explícitas previene el desbordamiento de pila (stack overflow).

¿Debería verificar la profundidad al inicio o al final de las llamadas recursivas?

Al inicio, antes de cualquier procesamiento. Esto falla rápidamente cuando se exceden los límites, evitando el desperdicio de computación en datos que serán rechazados. Las cláusulas de guarda en la entrada de la función hacen que las comprobaciones de profundidad sean explícitas y fáciles de auditar.

¿Cómo gestiono las referencias cíclicas en funciones recursivas?

Rastree los objetos visitados en un Set o WeakSet. Antes de la recursión, compruebe si el objeto ya fue visitado. Si es así, omítalo, lance un error o devuelva un marcador de posición. Esto evita la recursión infinita de estructuras de datos circulares: obj.child.parent === obj.

Asegúrate ahora.

Proteja su código, la nube y el entorno de ejecución en un único sistema central.
Encuentre y corrija vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.