Aikido

Por que você deve evitar recursão sem proteção de profundidade

Risco de Bug

Regra
Evite recursão sem proteção proteção.
Recursão sem proteção limitação limitação riscos pilha
transbordamento e cria DoS vulnerabilidades a partir de entradas
entradas. Recursão com profundidade limites e e limites
limites verificação é aceitável.

Suporte a idiomas: 45+

Introdução

Funções recursivas sem limites de profundidade podem esgotar a pilha de chamadas (call stack), causando falhas. Entradas maliciosas, como objetos JSON profundamente aninhados ou estruturas de dados cíclicas, podem desencadear recursão ilimitada intencionalmente. Uma única requisição maliciosa pode derrubar seu serviço ao exceder os limites da pilha, criando uma vulnerabilidade de negação de serviço (denial-of-service) trivial de explorar.

Por que isso importa

Implicações de segurança (ataques DoS): Atacantes podem criar entradas que desencadeiam recursão profunda, travando sua aplicação. JSON, XML ou estruturas de dados aninhadas profundamente são vetores de ataque comuns. Uma única requisição maliciosa esgota a pilha, derrubando todo o serviço para todos os usuários.

Estabilidade do sistema: Erros de stack overflow travam o processo imediatamente sem degradação elegante. Em produção, isso significa requisições perdidas, transações interrompidas e indisponibilidade do serviço. A recuperação exige reiniciar a aplicação inteira.

Esgotamento de recursos: A recursão ilimitada consome memória da pilha exponencialmente. Cada chamada recursiva adiciona um frame à pilha, e cadeias de recursão profundas podem consumir megabytes de memória. Isso afeta outros processos no mesmo servidor e pode desencadear condições de falta de memória.

Exemplos de código

❌ Não-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 que está errado: Nenhum limite de profundidade permite que invasores enviem objetos profundamente aninhados que excedem os limites da pilha. Entrada como {a: {a: {a: {...}}}} aninhado a 10.000 níveis de profundidade trava a aplicação com estouro de pilha (stack overflow). A função recursa cegamente sem verificar a profundidade.

✅ Compatível:

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 que isso importa: O maxDepth o parâmetro limita a recursão a 100 níveis, prevenindo estouro de pilha (stack overflow). Este limite é alto o suficiente para estruturas de dados aninhadas legítimas (a maioria dos dados do mundo real raramente excede 10-20 níveis), ao mesmo tempo em que é baixo o suficiente para interromper ataques antes de consumir uma quantidade significativa de memória da pilha. Entradas maliciosas profundamente aninhadas geram um erro em vez de travar a aplicação. A verificação de profundidade ocorre antes do processamento, falhando rapidamente quando os limites são excedidos.

Conclusão

Adicione parâmetros de profundidade a todas as funções recursivas que processam dados externos. Defina profundidades máximas razoáveis com base na complexidade esperada da estrutura de dados. Lance erros ou retorne valores padrão quando os limites de profundidade forem excedidos em vez de falhar.

FAQs

Dúvidas?

Qual é uma profundidade máxima de recursão razoável?

Depende das suas estruturas de dados. Para parsing de JSON ou travessia de árvore, 100-1000 níveis é razoável. A maioria das estruturas de dados legítimas não excede 10-20 níveis. Defina limites com base no seu domínio, mas sempre tenha limites. Monitore a produção para ver as profundidades reais e ajuste conforme necessário.

Como converter recursão em iteração?

Utilize pilhas ou filas explícitas. Substitua chamadas recursivas por um loop que empurra itens para uma pilha, depois os retira e processa. Isso lhe dá controle total sobre o uso de memória e a profundidade. Para travessia de árvores, a iteração em largura (breadth-first) ou em profundidade (depth-first) com estruturas de dados explícitas previne o estouro de pilha (stack overflow).

Devo verificar a profundidade no início ou no final de chamadas recursivas?

No início, antes de qualquer processamento. Isso falha rapidamente quando os limites são excedidos, evitando o desperdício de computação em dados que serão rejeitados. Cláusulas de guarda na entrada da função tornam as verificações de profundidade explícitas e fáceis de auditar.

Como lidar com referências cíclicas em funções recursivas?

Rastreie objetos visitados em um Set ou WeakSet. Antes de recursar, verifique se o objeto já foi visitado. Se sim, ignore-o, lance um erro ou retorne um placeholder. Isso evita recursão infinita de estruturas de dados circulares: obj.child.parent === obj.

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.