Há décadas, o teste estático de segurança de aplicações (SAST) é uma das formas mais eficazes de equipes de segurança escalarem a revisão de código.
Mas, ao construir o Codex Security, fizemos uma escolha deliberada de design: não começamos importando um relatório de análise estática e pedindo ao agente que fizesse a triagem. Projetamos o sistema para começar pelo próprio repositório — sua arquitetura, fronteiras de confiança e comportamento esperado — e validar o que encontra antes de pedir que uma pessoa invista tempo nisso.
O motivo é simples: as vulnerabilidades mais difíceis geralmente não são problemas de fluxo de dados. Elas acontecem quando o código parece impor uma verificação de segurança, mas essa verificação não garante de fato a propriedade da qual o sistema depende. Em outras palavras, o desafio não é apenas rastrear como os dados percorrem um programa — é determinar se as defesas no código realmente funcionam.
O SAST costuma ser apresentado como um pipeline limpo: identificar uma fonte de entrada não confiável, rastrear os dados pelo programa e sinalizar casos em que esses dados chegam a 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 permanecer viável em escala — especialmente em bases de código reais com indireção, despacho dinâmico, callbacks, reflexão e fluxo de controle 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 executá-lo.
Isso, por si só, não é o motivo de o Codex Security não começar com um relatório de SAST.
O problema mais profundo é o que acontece depois que você consegue rastrear uma fonte até um sink.
Mesmo quando a análise estática rastreia corretamente a entrada por várias funções e camadas, ela ainda precisa responder à pergunta que de fato determina se existe uma vulnerabilidade:
Pense em 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 consegue ver que o sanitizador foi executado. O que ele normalmente não consegue determinar é se esse sanitizador é de fato suficiente para o contexto específico de renderização, o mecanismo de templates, o comportamento de codificação e as transformações a jusante envolvidas.
É aí que as coisas ficam complicadas. O problema não é apenas se os dados chegam a um sink. É se as verificações no código realmente restringem o valor da forma que o sistema assume.
Em outras palavras: há uma grande diferença entre "o código chama um sanitizador" e "o sistema está seguro".
Este é um padrão que aparece o tempo todo em sistemas reais.
Uma aplicação web recebe um payload JSON, extrai um redirect_url, valida-o com uma regex de allowlist, faz a decodificação de 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 confiável → verificação de regex → decodificação de URL → redirecionamento
Mas a pergunta real não é se a verificação existe. É se essa verificação ainda restringe o valor depois das transformações que vêm em seguida.
Se a regex roda antes da decodificação, ela realmente restringe a URL decodificada da forma como o handler de redirecionamento a interpreta?
Responder a isso significa raciocinar sobre toda a cadeia de transformações: o que a regex permite, como a decodificação e a normalização se comportam, como o parsing de URL trata casos-limite e como a lógica de redirecionamento resolve esquemas e autoridades.
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 desalinhamentos entre validação e interpretação. O fluxo de dados é visível. A fraqueza está em como as restrições se propagam — ou deixam de se propagar — pela cadeia de transformações.
Isso não é apenas um padrão teórico. No CVE-2024-29041(abre em uma nova janela), o Express foi afetado por uma falha de open redirect em que URLs malformadas podiam contornar implementações comuns de allowlist por causa de como os destinos de redirecionamento eram codificados e depois interpretados. O fluxo de dados era direto. A pergunta mais difícil — e a que determinava se o bug existia — era se a validação ainda se mantinha após a cadeia de transformações.
O Codex Security foi construído em torno de um objetivo simples: reduzir a triagem ao trazer à tona achados com evidências mais fortes. No produto, isso significa usar contexto específico do repositório (incluindo um modelo de ameaças) e validar achados de alto sinal em um ambiente isolado antes de apresentá-los.
Quando o Codex Security encontra uma fronteira que parece "validação" ou "sanitização", ele não trata isso como uma caixa de seleção. Ele tenta entender o que o código está tentando garantir — e, depois, tenta falsificar essa garantia.
Na prática, isso tende a ser uma combinação de:
- Ler o caminho de código relevante com todo o contexto do repositório, como faria um pesquisador de segurança, e buscar desalinhamentos entre intenção e implementação. Isso inclui comentários, mas o modelo não necessariamente acredita em comentários; então adicionar //Halvar says: this is not a bug acima do seu código não o confunde, se de fato houver um bug.
- Reduzir o problema ao menor recorte testável (por exemplo, o pipeline de transformação em torno de uma única entrada), para que você consiga raciocinar sobre ele sem o resto do sistema atrapalhando. Nesse sentido, o Codex Security separa pequenos recortes de código e depois escreve micro-fuzzers para eles.
- Raciocinar sobre restrições ao longo das transformações, em vez de tratar cada checagem de forma independente. Quando apropriado, isso pode incluir uma formalização como um problema de satisfatibilidade. Em outras palavras, damos ao modelo acesso a um ambiente Python com z3-solver, e ele é bom em usá-lo quando necessário, assim como uma pessoa teria que fazer ao responder a um problema de restrições de entrada particularmente complexo. Isso é especialmente útil para analisar overflows de inteiro ou bugs similares em arquiteturas não padrão.
- Executar hipóteses em um ambiente de validação isolado (sandbox) quando possível, para distinguir "isso poderia ser um problema" de "isso é 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 checagem", o sistema avança para "o invariante se mantém (ou não), e aqui estão as evidências". E o modelo escolhe a melhor ferramenta para esse trabalho.
Uma reação razoável é: por que não fazer os dois? Começar com um relatório de SAST e, depois, usar o agente para ir mais fundo no raciocínio.
Há casos em que achados pré-computados são úteis — especialmente para classes de bugs específicas e conhecidas. Mas, para um agente projetado para descobrir e validar vulnerabilidades em contexto, começar por um relatório de SAST cria três modos de falha previsíveis.
Primeiro, isso pode incentivar um afunilamento prematuro. Uma lista de achados é um mapa de onde uma ferramenta já olhou. Ao tratá-la como ponto de partida, você pode enviesar o sistema para gastar um esforço desproporcional nas mesmas áreas, usando as mesmas abstrações, e deixar passar classes de vulnerabilidades que não se encaixam na visão de mundo da ferramenta.
Segundo, isso pode introduzir julgamentos implícitos difíceis de desfazer. Muitos achados de SAST embutem suposições sobre sanitização, validação ou fronteiras de confiança. Se essas suposições estiverem erradas — ou apenas incompletas — alimentá-las no loop de raciocínio pode deslocar o agente de "investigar" para "confirmar ou descartar", o que não é o que queremos que o agente faça.
Terceiro, isso pode dificultar avaliar o sistema de raciocínio. Se o pipeline começa pela saída do SAST, fica difícil separar o que o agente descobriu pela própria análise do que ele herdou de outra ferramenta. Essa separação é importante se você quiser medir com precisão as capacidades do sistema, o que é necessário para que ele melhore ao longo do tempo.
Por isso, construímos o Codex Security para começar onde a pesquisa de segurança começa: no código e na intenção do sistema, usando validação para elevar o nível de confiança antes de interromper uma pessoa.
Ferramentas de SAST podem ser excelentes no que foram projetadas para fazer: impor padrões de codificação segura, capturar problemas diretos de source-to-sink e detectar padrões conhecidos em escala, com trade-offs previsíveis. Elas podem ser uma parte forte de uma defesa em camadas.
Este post é mais específico: trata de por que um agente projetado para raciocinar sobre comportamento e validar achados não deveria começar seu trabalho ancorado em uma lista estática de achados.
Também vale destacar uma limitação relacionada do pensamento puramente source-to-sink: nem toda vulnerabilidade é um problema de fluxo de dados. Muitas falhas reais são problemas de estado e invariantes — bypass de fluxo de trabalho, lacunas de autorização e bugs de "o sistema está no estado errado". Para esses tipos de bug, um valor contaminado não chega a um único "sink perigoso". O risco está no que o programa supõe que sempre será verdadeiro.
Esperamos que o ecossistema de ferramentas de segurança continue melhorando: análise estática, fuzzing, proteções em runtime e fluxos de trabalho com agentes terão todos seu papel.
O que queremos que o Codex Security faça bem é a parte que custa mais para equipes de segurança: transformar "isso parece suspeito" em "isso é real, aqui está como falha e aqui está uma correção que corresponde à intenção do sistema".
Se você quiser saber mais sobre como o Codex Security analisa repositórios, valida achados e propõe correções, veja nossa documentação(abre em uma nova janela).


