Superando Limitações SAST: Validação Real com Codex Security
Introdução: Além da Análise Estática Tradicional
Por décadas, a análise estática de segurança de aplicações (SAST) tem sido um dos métodos mais eficazes para equipes de segurança escalarem a revisão de código. No entanto, ao desenvolvermos o Codex Security na AITY, fizemos uma escolha de design deliberada: não começamos importando um relatório de análise estática e pedindo ao agente para triá-lo. Projetamos o sistema para iniciar com o próprio repositório — sua arquitetura, limites de confiança e comportamento pretendido — e para validar o que encontra antes de pedir que um humano dedique tempo a isso.
A razão é simples: as vulnerabilidades mais difíceis geralmente não são problemas de fluxo de dados. Elas ocorrem quando o código parece impor uma verificação de segurança, mas essa verificação não garante a propriedade da qual o sistema depende. Em outras palavras, o desafio não é apenas rastrear como os dados se movem através de um programa — é determinar se as defesas no código realmente funcionam.
O Desafio Além do Fluxo de Dados
O SAST é frequentemente enquadrado como um pipeline limpo: identificar uma fonte de entrada não confiável, rastrear os dados através do programa e sinalizar os casos em que esses dados atingem um sink sensível sem sanitização. É um modelo elegante e cobre muitos bugs reais.
Na prática, o SAST precisa fazer aproximações para se manter viável em escala, especialmente em bases de código reais com indireção, despacho dinâmico, callbacks, reflexão e fluxo de controle pesado em frameworks. Essas aproximações não são uma falha do SAST; são a realidade de tentar raciocinar sobre o código sem executá-lo.
O problema mais profundo surge depois de rastrear com sucesso uma fonte até um sink. Mesmo quando a análise estática rastreia corretamente a entrada através de múltiplas funções e camadas, ela ainda precisa responder à pergunta que realmente determina se uma vulnerabilidade existe: "Essa verificação realmente garante a propriedade que o sistema espera?"
Considere um padrão comum: o código chama algo como sanitize_html() antes de renderizar conteúdo não confiável. Um analisador estático pode ver que o sanitizador foi executado. O que ele geralmente não consegue determinar é se esse sanitizador é realmente suficiente para o contexto de renderização específico, motor de templates, comportamento de codificação e transformações posteriores envolvidas. O problema não é apenas se os dados atingem um sink, mas se as verificações no código realmente restringem o valor da maneira que o sistema assume. Há uma grande diferença entre "o código chama um sanitizador" e "o sistema está seguro".
Muitas das vulnerabilidades que importam na prática se parecem com isso: erros de ordem de operações, normalização parcial, ambiguidades de parsing e incompatibilidades entre validação e interpretação. O fluxo de dados é visível. A fraqueza está em como as restrições se propagam — ou falham em se propagar — através da cadeia de transformação.
Um exemplo prático é o CVE-2024-29041, onde o Express foi afetado por um problema de redirecionamento aberto. URLs malformadas podiam ignorar implementações comuns de allowlist devido à forma como os alvos de redirecionamento eram codificados e interpretados. O fluxo de dados era direto. A questão mais difícil — e a que determinou a existência do bug — era se a validação ainda era válida após a cadeia de transformação.
A Abordagem Única do Codex Security
O Codex Security é construído em torno de um objetivo simples: reduzir a triagem, expondo problemas com evidências mais fortes. No produto, isso significa usar o contexto específico do repositório (incluindo um modelo de ameaças) e validar problemas de alto sinal em um ambiente isolado antes de expô-los.
Quando o Codex Security encontra um limite que se parece com "validação" ou "sanitização", ele não o trata como um checkbox. Ele tenta entender o que o código está tentando garantir — e então tenta falsificar essa garantia. Na prática, isso tende a se parecer com uma mistura de:
- Leitura de Caminho de Código com Contexto Completo: Lê o caminho de código relevante com o contexto completo do repositório, como um pesquisador de segurança faria, procurando incompatibilidades entre intenção e implementação.
- Redução do Problema: Reduz o problema à menor fatia testável (por exemplo, o pipeline de transformação em torno de uma única entrada), escrevendo micro-fuzzers para eles.
- Raciocínio sobre Restrições: Raciocina sobre as restrições em todas as transformações, em vez de tratar cada verificação independentemente. Isso pode incluir a formalização como uma questão de satisfatibilidade, utilizando ferramentas como o
z3-solverem um ambiente Python para problemas complexos de restrição de entrada. - Execução de Hipóteses em Ambiente Isolado: Executa hipóteses em um ambiente de validação sandboxed sempre que possível, para distinguir "isso pode ser um problema" de "isso é um problema". A melhor prova é um PoC (Proof of Concept) completo de ponta a ponta com o código compilado em modo de depuração.
Essa é a mudança fundamental: em vez de parar em "uma verificação existe", o sistema avança para "o invariante se mantém (ou não), e aqui está a evidência". E o modelo escolhe a melhor ferramenta para essa tarefa.
Por Que Não Começar com Relatórios SAST?
Uma reação razoável é: por que não fazer as duas coisas? Começar com um relatório SAST e, em seguida, usar o agente para raciocinar mais profundamente. Há casos em que as descobertas pré-computadas são úteis, especialmente para classes de bugs estreitas e conhecidas. No entanto, para um agente projetado para descobrir e validar vulnerabilidades em contexto, começar com um relatório SAST cria três modos de falha previsíveis:
- Estreitamento Prematuro: Uma lista de descobertas é um mapa de onde uma ferramenta já procurou. Se você a trata como ponto de partida, pode enviesar o sistema a gastar esforço desproporcional nas mesmas regiões, usando as mesmas abstrações, e perdendo classes de problemas que não se encaixam na visão de mundo da ferramenta.
- Julgamentos Implícitos: Muitas descobertas de SAST codificam suposições sobre sanitização, validação ou limites de confiança. Se essas suposições estiverem erradas — ou apenas incompletas — alimentá-las no loop de raciocínio pode mudar o agente de "investigar" para "confirmar ou descartar", o que não é o que queremos que o agente faça.
- Dificuldade de Avaliação: Se o pipeline começa com a saída do SAST, torna-se difícil separar o que o agente descobriu através de sua própria análise do que herdou de outra ferramenta. Essa separação é importante se você deseja medir as capacidades do sistema com precisão, o que é necessário para que o sistema melhore ao longo do tempo.
Construímos o Codex Security para começar onde a pesquisa de segurança começa: a partir do código e da intenção do sistema, com validação usada para aumentar a barra de confiança antes de interromper um humano.
Conclusão: O Impacto Prático na Segurança de Software
As ferramentas SAST podem ser excelentes no que são projetadas para fazer: aplicar padrões de codificação segura, detectar problemas diretos de fonte para sink e identificar padrões conhecidos em escala com tradeoffs previsíveis. Elas podem ser uma parte forte da defesa em profundidade.
O ecossistema de ferramentas de segurança continuará a melhorar: análise estática, fuzzing, guardas em tempo de execução e fluxos de trabalho agentic terão todos os seus papéis. O que queremos que o Codex Security faça bem é a parte que mais custa às equipes de segurança: transformar "isso parece suspeito" em "isso é real, é assim que falha e aqui está uma correção que corresponde à intenção do sistema". Essa abordagem prática e baseada em evidências fortalece a segurança, reduz o esforço de triagem manual e acelera a resolução de vulnerabilidades críticas.
Aguardando Login...