Durante décadas, os testes estáticos de segurança de aplicações (SAST) têm sido uma das formas mais eficazes de as equipas de segurança escalarem a revisão de código.
Mas, quando criámos o Codex Security, tomámos uma decisão de design deliberada: não começámos por importar um relatório de análise estática e pedir ao agente que fizesse a triagem. Concebemos o sistema para começar pelo próprio repositório — a sua arquitetura, limites de confiança e comportamento previsto — e para validar o que encontra antes de pedir a uma pessoa que dedique tempo a isso.
A razão é simples: as vulnerabilidades mais difíceis normalmente não são problemas de fluxo de dados. Acontecem quando o código parece impor uma verificação de segurança, mas essa verificação não garante, na prática, a propriedade em que o sistema se baseia. Por outras palavras, o desafio não é apenas rastrear como os dados se movem num programa — é determinar se as defesas no código funcionam mesmo.
Muitas vezes, o SAST é descrito como um pipeline limpo: identificar uma fonte de entrada não fiável, rastrear os dados ao longo do programa e sinalizar casos em que esses dados chegam a um destino sensível («sink») sem sanitização. É um modelo elegante e cobre muitos bugs reais.
Na prática, o SAST tem de fazer aproximações para se manter tratável à escala — sobretudo em bases de código reais com indireção, despacho dinâmico, callbacks, reflexão e um fluxo de controlo fortemente dependente de frameworks. Essas aproximações não são uma crítica ao SAST; são a realidade de tentar raciocinar sobre código sem o executar.
Isso, por si só, não explica por que o Codex Security não começa com um relatório SAST.
O problema mais profundo é o que acontece depois de rastrear com sucesso uma fonte até um «sink».
Mesmo quando a análise estática rastreia corretamente a entrada através de várias funções e camadas, ainda tem de responder à pergunta que determina, de facto, se existe uma vulnerabilidade:
Considere um padrão comum: o código chama algo como sanitize_html() antes de renderizar conteúdo não fiável. Um analisador estático consegue ver que o sanitizador foi executado. O que, geralmente, não consegue determinar é se esse sanitizador é realmente suficiente para o contexto de renderização específico, o motor de templates, o comportamento de codificação e as transformações a jusante envolvidas.
É aqui que as coisas se complicam. O problema não é apenas se os dados chegam a um «sink». É se as verificações no código, na prática, restringem o valor da forma que o sistema assume.
Dito de outro modo: há uma grande diferença entre «o código chama um sanitizador» e «o sistema é seguro».
Eis um padrão que aparece constantemente em sistemas reais.
Uma aplicação Web recebe um payload JSON, extrai um redirect_url, valida-o face a uma regex de lista de permissões (allowlist), descodifica o URL e passa o resultado para um handler de redirecionamento.
Um relatório clássico de source-to-sink pode descrever o fluxo:
entrada não fiável → verificação regex → descodificação do URL → redirecionamento
Mas a verdadeira questão não é se a verificação existe. É se essa verificação ainda restringe o valor depois das transformações que se seguem.
Se a regex for aplicada antes da descodificação, ela restringe mesmo o URL descodificado da forma como o handler de redirecionamento o interpreta?
Responder a isto implica raciocinar sobre toda a cadeia de transformação: o que a regex permite, como a descodificação e a normalização se comportam, como a análise de URLs trata casos-limite e como a lógica de redirecionamento resolve esquemas e autoridades.
Muitas das vulnerabilidades que importam na prática são assim: erros de ordem de operações, normalização parcial, ambiguidades de parsing e discrepâncias entre validação e interpretação. O fluxo de dados é visível. A fragilidade está na forma como as restrições se propagam — ou deixam de se propagar — ao longo da cadeia de transformação.
Isto não é apenas um padrão teórico. No CVE-2024-29041(abre numa nova janela), o Express foi afetado por um problema de redirecionamento aberto (open redirect), em que URLs malformados podiam contornar implementações comuns de listas de permissões (allowlist), devido à forma como os destinos de redirecionamento eram codificados e depois interpretados. O fluxo de dados era direto. A questão mais difícil — e a que determinou se o bug existia — era se a validação ainda se mantinha depois da cadeia de transformação.
O Codex Security foi concebido com um objetivo simples: reduzir a triagem ao destacar problemas com evidência mais robusta. No produto, isso significa usar contexto específico do repositório (incluindo um modelo de ameaça) e validar problemas com fortes indícios num ambiente isolado antes de os apresentar.
Quando o Codex Security encontra um ponto do código que parece «validação» ou «sanitização», não o trata como uma simples caixa a assinalar. Tenta compreender o que o código está a tentar garantir — e, depois, tenta invalidar essa garantia.
Na prática, isto costuma traduzir-se numa combinação de:
- Ler o percurso de código relevante com o contexto completo do repositório, como faria um investigador de segurança, e procurar discrepâncias entre a intenção e a implementação. Isto inclui comentários, mas o modelo não confia necessariamente neles; por isso, acrescentar //Halvar says: this is not a bug acima do código não o confunde se, de facto, houver um bug.
- Reduzir o problema à menor fatia testável (por exemplo, o pipeline de transformação em torno de uma única entrada), para poder raciocinar sobre ele sem que o resto do sistema se intrometa. Nesse sentido, o Codex Security extrai pequenos excertos de código e escreve micro-fuzzers para os testar.
- Raciocinar sobre restrições ao longo das transformações, em vez de tratar cada verificação de forma independente. Quando apropriado, isto pode incluir uma formalização como um problema de satisfatibilidade. Por outras palavras, damos ao modelo acesso a um ambiente Python com o z3-solver, e ele usa-o bem quando é necessário — tal como uma pessoa teria de fazer ao resolver um problema particularmente complexo de restrições de entrada. Isto é especialmente útil para analisar overflows de inteiros ou bugs semelhantes em arquiteturas não padrão.
- Executar hipóteses, quando possível, num ambiente de validação em sandbox, para distinguir «isto pode ser um problema» de «isto é um problema». Não há prova melhor do que uma PoC completa, de ponta a ponta, com o código compilado em modo de depuração.
Esta é a mudança-chave: em vez de parar em «existe uma verificação», o sistema avança para «o invariante mantém-se (ou não), e aqui está a evidência». E o modelo escolhe a melhor ferramenta para esse trabalho.
Uma reação razoável é: porque não fazer ambos? Começar com um relatório SAST e, depois, usar o agente para aprofundar o raciocínio.
Há casos em que achados pré-computados são úteis — sobretudo para classes de bugs específicas e conhecidas. Mas, para um agente concebido para descobrir e validar vulnerabilidades em contexto, começar por um relatório SAST cria três modos de falha previsíveis.
Em primeiro lugar, pode incentivar um estreitamento prematuro. Uma lista de achados é um mapa de onde uma ferramenta já analisou. Se a tratar como ponto de partida, pode enviesar o sistema para investir um esforço desproporcionado nas mesmas áreas, usar as mesmas abstrações e deixar escapar classes de problemas que não se encaixam na visão do mundo da ferramenta.
Em segundo lugar, pode introduzir juízos implícitos difíceis de desfazer. Muitos achados de SAST incorporam pressupostos sobre sanitização, validação ou fronteiras de confiança. Se esses pressupostos estiverem errados — ou forem apenas incompletos — introduzi-los no ciclo de raciocínio pode fazer o agente passar de «investigar» para «confirmar ou descartar», o que não é o que queremos que o agente faça.
Em terceiro lugar, pode tornar mais difícil avaliar o sistema de raciocínio. Se o pipeline começa pela saída do SAST, torna-se difícil separar o que o agente descobriu pela sua própria análise do que herdou de outra ferramenta. Essa separação é importante se quisermos medir com precisão as capacidades do sistema, o que é necessário para o sistema melhorar ao longo do tempo.
Por isso, concebemos o Codex Security para começar onde começa a investigação em segurança: no código e na intenção do sistema, usando validação para elevar a fasquia de confiança antes de interrompermos uma pessoa.
As ferramentas SAST podem ser excelentes no que foram concebidas para fazer: impor normas de codificação segura, detetar problemas diretos de source-to-sink e detetar padrões conhecidos à escala com trade-offs previsíveis. Podem ser uma componente forte de uma estratégia de defesa em profundidade.
Este artigo é mais específico: explica por que motivo um agente concebido para raciocinar sobre comportamentos e validar achados não deve iniciar o seu trabalho ancorado numa lista estática de achados.
Vale também a pena salientar uma limitação relacionada do pensamento puramente source-to-sink: nem toda a vulnerabilidade é um problema de fluxo de dados. Muitas falhas reais são problemas de estado e de invariantes — bypasses de fluxo de trabalho, lacunas de autorização e bugs em que «o sistema está no estado errado». Para estes tipos de bugs, um valor contaminado não chega a um único «sink perigoso». O risco está no que o programa assume que será sempre verdade.
Esperamos que o ecossistema de ferramentas de segurança continue a evoluir: análise estática, fuzzing, proteções em runtime e fluxos de trabalho com agentes terão todos o seu papel.
O que queremos que o Codex Security faça bem é a parte que custa mais às equipas de segurança: transformar «isto parece suspeito» em «isto é real, eis como falha e eis uma correção que corresponde à intenção do sistema».
Se quiser saber mais sobre como o Codex Security analisa repositórios, valida achados e propõe correções, consulte a nossa documentação(abre numa nova janela).


