Pitch: Reduzindo falsos positivos em triagem de AppSec com lógica paraconsistente (LPA2v) — projeto de TCC, open source
Fiz esse projeto pro meu TCC (Análise e Desenvolvimento de Sistemas) e queria compartilhar aqui porque acho que a discussão técnica interessa mais a esse público do que a outros lugares onde publiquei.
O problema
Pipeline de AppSec com SAST, SCA e DAST rodando em cada commit gera uma quantidade de achado que nenhum time consegue triar de verdade. A maioria não é vulnerabilidade real — código de teste, dependência já mitigada, scanner instável entre execuções. Só que cada ferramenta decide isoladamente, por severidade, o que vira alerta. O resultado é alert fatigue: quando o ruído passa de um certo ponto, o time para de confiar no scanner, e um alerta real se perde no meio de mil falsos.
Não quis construir mais um scanner. Quis testar se dava pra correlacionar o que os scanners já produzem antes de decidir o que vira alerta.
Por que lógica paraconsistente
A maioria dos mecanismos de triagem opera em lógica clássica: um achado é vulnerabilidade ou não é. O problema é que isso não reflete a realidade de um pipeline — é comum SAST apontar ausência de autenticação num endpoint enquanto DAST tenta explorar esse mesmo endpoint e é bloqueado por um WAF. As duas evidências são reais e contraditórias. Um mecanismo binário é obrigado a descartar uma.
A Lógica Paraconsistente Anotada de dois valores (LPA2v — da Costa, 1974; Abe, 2015) representa cada evidência com dois graus, favorável (μ) e desfavorável (λ), que podem ser altos ao mesmo tempo:
GC = μ - λ grau de certeza
GCT = μ + λ - 1 grau de contradição
Quando GCT é alto, não é empate pra desempatar — é contradição genuína, e deveria virar um estado "inconsistente" sinalizado pra revisão humana, não um "confirmado" ou "descartado" forçado.
Arquitetura
Cinco neurônios de domínio (SAST, SCA, DAST, contexto de código, contexto operacional) estimam (μ, λ) a partir do sinal bruto de cada fonte. Um neurônio mestre agrega isso de duas formas diferentes — e essa separação foi a parte que mais me custou acertar:
- Consenso: média ponderada de μ e λ entre todos os domínios → usada pra ranquear severidade.
- Contradição: maior μ e maior λ entre os detectores primários (SAST/SCA/DAST) — não a média.
Minha primeira versão usava só a média ponderada pra tudo, e não funcionava: uma contradição real entre dois detectores específicos ficava diluída pela média dos domínios neutros e nunca gerava um GCT alto o suficiente pra disparar o estado inconsistente. É o oposto do que a lógica paraconsistente deveria fazer. Separar as duas agregações resolveu.não disparar escalada sozinho.
Simulação sintética
Simulador em TypeScript, 206 ativos sintéticos em 12 cenários (de migração de dependência benigna a RCE confirmado, passando pelo caso do WAF acima e um compromisso de supply-chain silencioso que só SCA enxerga). Comparei threshold puro, rule-based (regras IF-THEN previstas de antemão) e o cluster LPA2v, em 3.005 eventos:
| Mecanismo | Precisão | Recall | Falsos positivos |
|---|---|---|---|
| Threshold | 8,01% | 86,8% | 2.493 |
| Rule-Based | 10,41% | 86,8% | 1.868 |
| LPA2v-Cluster | 100% | 65,6% | 0 |
O ponto que importa aqui não é a precisão de 100% isolada — é que o cluster zerou os falsos positivos sem simplesmente parar de alertar: ainda captura 164 dos 250 positivos reais simulados. A queda de recall é real, concentrada em três cenários difíceis de propósito (evidência de um único domínio, sinal fraco no início de um vazamento progressivo, aquecimento da janela de persistência). Reportei o número como saiu, sem recalibrar até ficar bonito.
Contra dados reais
Simulação prova arquitetura, não desempenho em produção. Rodei Semgrep, Snyk e OWASP ZAP de verdade contra 5 aplicações (duas SaaS, freeCodeCamp, Plane e meu portfólio pessoal), coletando 559 achados rotulados por revisão humana assistida por IA — não pela severidade autorreportada da própria ferramenta, isso tornaria o estudo circular.
Com o cluster sem nenhum reajuste de peso ou limiar em relação à simulação:
| Mecanismo | Precisão | Recall |
|---|---|---|
| Threshold | 31,66% | 100% |
| Rule-Based | 34,17% | 100% |
| LPA2v-Cluster | 86,84% | 74,58% |
O ponto mais fraco: recall de 33,33% em um dos repositórios, onde 8 de 12 vulnerabilidades reais eram do mesmo padrão (evidência moderada de um único domínio) e ficaram abaixo do limiar de atenção — mesmo efeito de diluição da média, só que sem contradição pra mitigar, porque não havia contradição ali, só evidência fraca isolada. Não recalibrei pra esconder isso.
O que não resolvi
Pesos e limiares foram calibrados manualmente, não aprendidos a partir de dado rotulado. Com os 559 achados já rotulados, calibração adaptativa é o próximo passo óbvio, mas ficou fora do escopo do TCC.
Código todo aberto, simulação determinística por seed pra quem quiser reproduzir os números:Link do repositório
Se alguém aqui já mexeu com correlação de alerta em pipeline de segurança, queria muito ouvir crítica sobre a decisão de separar consenso de contradição — foi a escolha de projeto que mais me custou, e ainda não tenho certeza se é a forma mais elegante de resolver isso.