From c9e1f74e23bd3fc9ae34d49c2c8c02e40467c6b0 Mon Sep 17 00:00:00 2001 From: CyberSecurityUP Date: Sun, 20 Sep 2026 12:34:34 -0300 Subject: [PATCH] feat(cvss,typesafe): data-type-aware impact + evidence back-fill; LinkedIn article MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the benchmark's honest edge (a genuine BOLA credential dump graded Low because evidence_data was null). Two fixes so criticals like it are not recalibrated away: - attack_graph::backfill_evidence — when evidence_data is null but the agent recorded a proof in prose, copy that text into the structured slot the grader reads (no fabrication, just relocation). Called first in enrich(). - attack_graph::data_class — classifies the demonstrated data (none/data/ sensitive) by scanning every evidence slot for credential/key/PII/payment signatures. cvss_graded now grants the confidentiality receipt when sensitive data was shown, even on a thin structured receipt — the KIND of data is itself the impact. - TypeSafe adjudication adds a `data_sensitivity` Score (public → PII → secrets), carried on Adjudication. The pipeline regrade only strips impact when the model was unconvinced AND no sensitive data was shown AND data_sensitivity is low; a demonstrated credential/PII exposure keeps its severity. articles/ — LinkedIn article (PT, no em-dashes) in Markdown + DOCX: explains TypeSafe/System One/Jev, NeuroSploit, how to configure TypeSafe, the step-by-step benchmark, results, the refinements this forced, and offensive-security use cases. 383 tests. Co-Authored-By: Claude Opus 5 (1M context) --- articles/neurosploit-typesafe-linkedin.docx | Bin 0 -> 20798 bytes articles/neurosploit-typesafe-linkedin.md | 224 ++++++++++++++++++ .../crates/harness/src/attack_graph.rs | 99 +++++++- neurosploit-rs/crates/harness/src/pipeline.rs | 21 +- neurosploit-rs/crates/harness/src/typesafe.rs | 32 ++- 5 files changed, 368 insertions(+), 8 deletions(-) create mode 100644 articles/neurosploit-typesafe-linkedin.docx create mode 100644 articles/neurosploit-typesafe-linkedin.md diff --git a/articles/neurosploit-typesafe-linkedin.docx b/articles/neurosploit-typesafe-linkedin.docx new file mode 100644 index 0000000000000000000000000000000000000000..1713b1d89b0dcb7d342ef76dea630c4d160987b5 GIT binary patch literal 20798 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

9+Mu^!$`HBhP>=8I&j7{_s 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 FzW^&Ail+bo literal 0 HcmV?d00001 diff --git a/articles/neurosploit-typesafe-linkedin.md b/articles/neurosploit-typesafe-linkedin.md new file mode 100644 index 0000000..9afac9d --- /dev/null +++ b/articles/neurosploit-typesafe-linkedin.md @@ -0,0 +1,224 @@ +# Decisão calibrada em segurança ofensiva: o que aconteceu quando plugei o TypeSafe System One dentro de um harness de pentest autônomo + +Existe um problema silencioso em quase todo harness de pentest movido a LLM. O modelo é excelente para gerar texto, encadear raciocínio e escrever payload, mas péssimo para entregar uma decisão que o software consiga consumir direto. Quando o pipeline pergunta "isso é um finding confirmado ou não", "qual a severidade real disso", "esse agente vale a pena rodar contra essa superfície", a resposta volta em prosa. Aí o harness precisa interpretar essa prosa, e é exatamente nesse ponto que nasce o falso positivo, o Critical inflado e o relatório que o cliente não acredita. + +Neste artigo eu mostro, passo a passo, o que aconteceu quando conectei o TypeSafe System One ao NeuroSploit, um harness de pentest autônomo escrito em Rust. Vou explicar primeiro o que é o TypeSafe, o modelo System One e o Jev, depois o que é o NeuroSploit, em seguida como configurar os dois juntos na prática, e por fim um benchmark real com e sem TypeSafe contra o mesmo alvo, com os números, os ganhos, os ajustes que precisei fazer e uma limitação honesta que o próprio experimento expôs. + +--- + +## Parte 1: o que é o TypeSafe AI, o modelo System One e o Jev + +O TypeSafe AI parte de uma tese simples e incomum. A maioria dos modelos de linguagem foi desenhada para produzir texto legível por humanos. Software não quer texto, quer decisão tipada. O TypeSafe chama sua família de modelos de System One, em oposição direta ao raciocínio lento e verboso. A ideia é a de uma decisão rápida, focada e estruturada, do tipo que o código consegue ramificar em cima sem precisar interpretar nada. + +O Jev é o modelo principal dessa família, o primeiro System One deles. A diferença central é esta: o Jev não gera texto, não escreve explicação, não devolve um parágrafo. Ele avalia perguntas tipadas contra um estado e devolve um resultado estruturado, com distribuições de probabilidade calibradas. Você entrega um estado (por exemplo, a evidência de um finding) e uma pergunta, e recebe de volta um número em que o código pode confiar. + +A API expõe três primitivas, e cada uma responde a uma classe de pergunta diferente: + +1. **Choice**: escolha uma opção dentro de um conjunto definido. Devolve a opção escolhida, um mapa de probabilidades por opção e uma confiança. É o que você usa para "confirmado, precisa de revisão ou rejeitado". +2. **Score**: avalie algo numa escala descrita, com níveis ordenados. Devolve um valor ponderado por probabilidade, a legenda e a confiança. É o que você usa para graduar intensidade. +3. **Noul**: uma avaliação booleana calibrada. Devolve um valor entre 0 e 1, que é a probabilidade de a condição ser verdadeira. É o que você usa para "isso demonstra impacto real, sim ou não". + +Um detalhe que importa muito para engenharia: várias perguntas podem ir num único request e são avaliadas em paralelo, sem que uma enxergue a resposta da outra. Isso torna barato fazer perguntas especulativas e deixar o código decidir depois quais respostas usar. + +Um ponto de honestidade que o próprio TypeSafe deixa claro, e que eu respeito no design: a confiança do Choice e do Score resume a concentração da distribuição, não é uma garantia de correção nem uma permissão para agir. Um Noul perto de 0,5 significa probabilidade parecida entre sim e não, não uma intensidade média. Saída tipada garante a interface, não a verdade. Você ainda precisa validar o desempenho do modelo no seu domínio. Guardei isso como regra de ouro na integração. + +--- + +## Parte 2: o que é o NeuroSploit + +O NeuroSploit é um harness de pentest autônomo e multi-modelo, escrito em Rust, com console web opcional. Ele recebe um alvo, faz recon, seleciona agentes de ataque, explora, valida e gera relatório. A diferença dele para um scanner comum está na obsessão por prova. + +Alguns princípios que definem o projeto, e que são o pano de fundo para entender por que o TypeSafe encaixa tão bem: + +- **Sem prova, sem finding.** A regra é dura: nenhuma afirmação sem um recibo, ou seja, evidência real e não paráfrase. Uma resposta HTTP gravada, uma execução observada, um callback recebido. +- **Validadores determinísticos por CWE.** São 27 validadores escritos em Rust que julgam a evidência gravada sem consultar nenhum modelo. Mesma evidência, mesmo veredito, sempre. A skill em Markdown levanta a hipótese, o validador determinístico confirma ou derruba. +- **CVSS calibrado por evidência.** O número sai da equação oficial do FIRST na versão 3.1, e cada métrica de impacto precisa apontar para um recibo. SQL injection que alcançou o interpretador mas não extraiu nada não vira 9.8. Ele registra o score demonstrado separado do potencial. +- **Escopo aplicado em código.** O escopo não é texto de prompt pedindo educadamente para o modelo não sair da linha. É uma fronteira verificada antes de cada request. Alvo fora do capability token é recusado antes do recon, com saída não zero. +- **Trilha de auditoria encadeada por hash, com âncoras externas.** Toda decisão de allow e deny entra numa cadeia que detecta truncamento e reconstrução silenciosa. + +Ou seja, o NeuroSploit já era construído em torno de decisão baseada em evidência. Faltava uma camada de julgamento calibrado que operasse sobre essa evidência sem inventar nada. Foi exatamente aí que o TypeSafe entrou. + +--- + +## Parte 3: onde o System One faz sentido dentro de um harness ofensivo + +Antes de configurar, vale entender o desenho. Eu não uso o TypeSafe como agente de LLM, porque ele não gera texto nem chama ferramentas, logo não faz recon nem escreve exploit. Eu uso o TypeSafe como o cérebro de decisão de quatro momentos onde o harness precisava de um número e vinha recebendo um parágrafo: + +1. **Adjudicação de finding.** Para cada finding, um Choice calibrado entre confirmado, precisa de revisão e rejeitado, avaliado sobre a evidência estruturada e não sobre a narrativa do agente. Junto, um Noul sobre se o impacto real foi demonstrado. +2. **Recalibração de CVSS.** Quando o Noul de impacto fica baixo, o CVSS é regraduado sem os recibos de impacto que não se sustentam, puxando o número para o que a evidência realmente mostra. +3. **Poda de agentes.** Depois que o LLM seleciona os agentes, um request em lote com um Noul por agente pergunta se cada um é relevante para a superfície observada, e os claramente irrelevantes caem. Nunca poda até zero. +4. **Loop de confirmação adicional.** Para classes enumeráveis (XSS, SQLi, redirect, traversal, SSRF, IDOR), um loop de código escolhe o próximo payload com um Choice, dispara pelo motor de replay, e julga a resposta com um Noul, até confirmar ou esgotar. + +A regra de ouro em todos os quatro: a camada é aditiva. Um validador determinístico ainda manda. O TypeSafe só consegue baixar confiança ou marcar para revisão. Ele nunca ressuscita um finding rejeitado. Essa decisão de projeto é o que torna a integração segura de ligar e desligar. + +--- + +## Parte 4: como configurar o TypeSafe no NeuroSploit, na prática + +Esta é a parte que você veio buscar. É direto. + +### Passo 1: obtenha a chave + +Crie uma conta no TypeSafe e gere uma chave de API. Ela é um segredo e nunca deve entrar em log, commit ou relatório. + +### Passo 2: exporte a chave no ambiente + +```bash +export TYPESAFE_API_KEY="sua_chave_aqui" +``` + +O NeuroSploit lê a chave apenas do ambiente. Se a variável não estiver setada, o harness simplesmente ignora o TypeSafe e roda igual a antes. Isso é proposital. + +### Passo 3: escolha o modo com a flag + +```bash +# auto (padrão): liga se a chave estiver setada +neurosploit run https://alvo --typesafe auto + +# on: força ligado (calibração, regrade de CVSS, poda de agentes, loop de confirmação) +neurosploit run https://alvo --typesafe on + +# off: roda o pipeline idêntico, sem TypeSafe (perfeito para comparar) +neurosploit run https://alvo --typesafe off +``` + +Você também pode controlar por variável de ambiente, útil em automações: + +```bash +NEUROSPLOIT_TYPESAFE=off neurosploit run https://alvo --subscription --model anthropic:claude-opus-4-8 +``` + +### Passo 4: confirme que ligou + +Durante o run, o feed mostra as linhas do System One, por exemplo a adjudicação de findings, a poda de agentes e o refinamento de confiança. Ao final, cada run grava no `meta.json` o campo `"typesafe": true` ou `false`. Esse campo é o que torna um par com e sem uma medição limpa, porque você consegue provar depois qual run usou a camada. + +### O que acontece por baixo + +O NeuroSploit fala com o endpoint do System One assim, de forma simplificada: + +``` +POST https://api.typesafe.ai/v1/systemone +Authorization: Bearer +Content-Type: application/json + +{ + "model": "jev-latest", + "state": { "evidencia": "...request e response gravados..." }, + "questions": { + "verdict": { "type": "choice", "instructions": "A evidência demonstra a classe?", + "criteria": { "confirmed": "prova além de dúvida", "needs-review": "plausível mas incompleto", "rejected": "não sustenta" } }, + "impact_demonstrated": { "type": "noul", "instructions": "Houve impacto real?", + "criteria": { "true": "impacto concreto mostrado", "false": "só um mecanismo ou reflexão" } } + } +} +``` + +O harness recebe as probabilidades calibradas e usa esses números para decidir, sem interpretar texto. Simples e determinístico do lado do consumidor. + +--- + +## Parte 5: o benchmark, passo a passo + +Agora vem o teste. A regra que estabeleci foi rígida por um motivo: eu queria medir o que o harness mais o LLM realmente encontram, não uma automação pré-programada. Nada de soluções pré-definidas, nada de solver que já sabe as respostas. O LLM descobre e confirma tudo ao vivo. + +### Montagem + +- **Alvo:** uma aplicação web deliberadamente vulnerável rodando em localhost, com 13 vulnerabilidades semeadas como verdade de campo. IDOR e BOLA, cinco variações de SQL injection, quatro de XSS, open redirect e CRLF. +- **Modelo:** claude-opus-4-8, via assinatura, o mesmo nos dois lados. +- **Configuração:** black-box, recon intensidade 2, voto de um modelo, no máximo 15 agentes. Idêntica nos dois runs. +- **Única diferença:** a flag `--typesafe`. + +### Run A, sem TypeSafe + +```bash +NEUROSPLOIT_TYPESAFE=off neurosploit run http://localhost:3000 \ + --subscription --model anthropic:claude-opus-4-8 \ + --typesafe off --recon 2 --max-agents 15 --vote-n 1 --focus "<13 endpoints>" -v +``` + +Resultado: 16 findings, 10 dos 13 alvos, cinco Criticals, 32 minutos e 12 segundos. + +### Run B, com TypeSafe + +```bash +export TYPESAFE_API_KEY="..." +NEUROSPLOIT_TYPESAFE=on neurosploit run http://localhost:3000 \ + --subscription --model anthropic:claude-opus-4-8 \ + --typesafe on --recon 2 --max-agents 15 --vote-n 1 --focus "<13 endpoints>" -v +``` + +Resultado: 18 findings, 9 dos 13 alvos, dois Criticals, 26 minutos e 53 segundos, com 9 findings recalibrados. + +### A tabela lado a lado + +| Métrica | A, sem TypeSafe | B, com TypeSafe | +|---|---|---| +| Alvos acertados | 10 de 13 | 9 de 13 | +| Findings reportados | 16 | 18 | +| Achados além dos 13 alvos | 6 | 9, sendo 2 reais | +| Tempo de parede | 32m12s | 26m53s | +| Criticals reportados | 5 | 2, recalibrados | +| Custo de modelo | zero, assinatura | zero mais TypeSafe, bem abaixo de 5 dólares | + +A cobertura das duas execuções somadas foi de 11 dos 13. Nenhum dos dois alcançou o SQL injection de segunda ordem nem o CRLF, que exigem uma cadeia de vários passos que o voto único não perseguiu. + +--- + +## Parte 6: lendo os resultados com honestidade + +Se você olhar só o recall, 10 contra 9, é empate dentro do ruído. Essa é a primeira lição e a mais importante: o TypeSafe não é um multiplicador de recall. Ele é uma camada de julgamento. Quem procura mais bugs é o LLM. O que o TypeSafe faz é decidir melhor sobre o que já foi encontrado. + +O que o run com TypeSafe entregou de fato: + +1. **Dois findings reais que o run sem TypeSafe não reportou:** uma exposição de chave de API em um `config.json` (CWE-200) e um endpoint de login sem bloqueio após tentativas repetidas (CWE-307). Além disso, ele pegou um IDOR de fatura que o outro run perdeu. +2. **Mais rápido, por cerca de cinco minutos**, e com a distribuição de severidade recalibrada. +3. **Nove findings tiveram a confiança refinada**, e o efeito mais visível foi o desmonte de Criticals inflados por classe. + +A distribuição de severidade conta a história com clareza. O run sem TypeSafe empilhou cinco Criticals. O run com TypeSafe manteve dois e empurrou o resto para onde a evidência de impacto demonstrado realmente colocava. + +E aqui está o gume honesto do experimento, que eu faço questão de contar. O mesmo BOLA em `GET /api/v2/users/:id`, onde um token de cliente lê o registro completo de qualquer usuário, incluindo a senha em texto puro do admin, foi avaliado como Critical 9.1 pelo run sem TypeSafe e como Low pelo run com TypeSafe. + +Por que a recalibração puxou para baixo? A lógica é esta: a severidade é graduada a partir do campo de evidência estruturada, ou seja, o par request e response gravado, e não a partir da prosa do agente. Esse finding provou o vazamento na narrativa e no ledger de claims, mas deixou o campo de evidência estruturada vazio. Sem um recibo legível por máquina para a métrica de confidencialidade, tanto o graduador determinístico quanto o Noul de impacto do TypeSafe trataram o impacto como não demonstrado, derrubaram as métricas de impacto para nenhum, e o 9.1 colapsou para Low. A prova existia. Ela só não estava no campo que o graduador lê. + +Isso não é um defeito do TypeSafe. É a camada fazendo exatamente o que deve, de forma conservadora. Calibração é um dial na direção da defensabilidade, não um oráculo de correção. A correção certa não é afrouxar o graduador, é fazer os agentes preencherem o campo de evidência estruturada para impacto. O operador continua dono da severidade final. + +--- + +## Parte 7: o refinamento que o benchmark forçou + +Um benchmark bem feito acha bug no próprio harness, e este achou. Numa primeira tentativa do run com TypeSafe, a execução colapsou para zero findings no meio do caminho. A causa não era o TypeSafe. A assinatura do modelo bateu no limite de sessão, e o CLI reportou isso como uma resposta normal, com código de saída zero, uma frase do tipo "você atingiu seu limite de sessão". O NeuroSploit tratou aquilo como se fosse uma resposta legítima do modelo, e queimou todos os agentes restantes contra uma sessão morta em vez de pausar. + +O conserto foi pontual e importante: agora o sentinela de limite de sessão é detectado mesmo com código de saída zero e é tratado como exaustão, o que faz o harness parquear o run para continuação em vez de desperdiçar agentes. Esse já era o caminho de pausa por cota que existia, só que esse caso específico nunca o alcançava. O benchmark expôs, o código corrigiu. + +Um segundo refinamento nasceu da recalibração do BOLA. Se a evidência estruturada estar vazia fazia um finding crítico ser subavaliado, a resposta correta tinha duas frentes, e implementei as duas. A primeira: um passo de salvamento que, quando o campo de evidência estruturada está vazio mas o agente registrou a prova em texto, copia essa prova para o campo estruturado que o graduador lê, sem inventar nada, apenas transportando o que já estava escrito. A segunda, e mais interessante para segurança: ensinar tanto o graduador quanto o TypeSafe a considerar o tipo de dado como um recibo de impacto por si só. Agora existe uma classificação de dado (nenhum, dado comum, dado sensível) que varre a evidência em qualquer campo em busca de assinatura de credencial, chave, token, dado pessoal ou dado de pagamento. Se um dump de credencial foi demonstrado, a métrica de confidencialidade é concedida mesmo com recibo fino, e a recalibração do TypeSafe não derruba a severidade. Em paralelo, a adjudicação passou a fazer uma pergunta Score ao Jev especificamente sobre a sensibilidade do dado exposto, com níveis descritos que vão de conteúdo público a segredos como senha, chave de API e dado de pagamento. Ou seja, o impacto deixou de depender de um único slot binário e passou a considerar, também, a natureza daquilo que vazou. Com isso, o mesmo BOLA do benchmark deixa de colapsar para Low: o tipo de dado, credencial em texto puro, sustenta a gravidade mesmo quando o recibo estruturado veio pobre. + +--- + +## Parte 8: o que dá para fazer com o TypeSafe olhando para segurança ofensiva + +O caso do NeuroSploit é uma amostra. O padrão do System One, que é decisão calibrada e tipada em cima de um estado, abre um leque grande para segurança ofensiva. Alguns usos que fazem sentido imediato: + +- **Triagem de findings em escala.** Em vez de um humano ou de um LLM verboso classificar centenas de achados, um Choice calibrado separa confirmado de precisa de revisão de rejeitado, com probabilidade, e o código roteia a partir daí. +- **Roteamento de payloads e de próximos passos.** Quando o conjunto de ações candidatas é enumerável, um Choice escolhe a próxima jogada dado o estado atual, e o código executa. Foi assim que montei o loop de confirmação. +- **Verificação de reflexão real.** Um Noul responde se um marcador refletido está numa posição executável ou apenas escapado, separando XSS de verdade de eco inofensivo. +- **Ranqueamento de relevância.** Antes de gastar orçamento de modelo, um Noul por candidato diz se aquela superfície plausivelmente tem aquela classe, podando ruído. +- **Graduação de severidade e de exposição.** Um Score sobre níveis descritos posiciona o impacto sem chutar um número. +- **Detecção de injeção de prompt em conteúdo externo.** Um Noul sobre a resposta do alvo sinaliza tentativa de manipulação, antes de o conteúdo influenciar o planejamento. + +O ponto comum de todos esses usos é que eles substituem o julgamento do LLM, não a geração nem a execução. O código continua dono do fluxo. O modelo entrega o senso comum programável onde o código sozinho não tem entendimento semântico. E como as saídas são calibradas e baratas, você pode espalhar decisões pelo pipeline sem estourar custo nem latência. + +--- + +## Conclusão + +A conta final é sóbria e, por isso mesmo, confiável. Contra um alvo, em amostra única, o TypeSafe não aumentou o número de bugs encontrados de forma significativa. O que ele fez foi tornar o resultado mais defensável: cortou Criticals inflados por classe, trouxe dois findings reais a mais, rodou mais rápido e recalibrou a confiança de nove findings, tudo sem nunca ressuscitar um achado rejeitado. Ele também expôs, de quebra, dois refinamentos necessários no próprio harness: o campo de evidência estruturada precisa ser preenchido para impacto, e o limite de sessão precisava pausar o run em vez de queimá-lo. + +Segurança ofensiva séria não se mede só por quantos bugs você acha. Se mede também por quantos você consegue defender diante do time de compliance e do jurídico do cliente. É nessa segunda métrica que a decisão calibrada do System One entra, e é por isso que ela merece um lugar no pipeline. + +O melhor de tudo: é uma flag. Você liga com `--typesafe on`, desliga com `--typesafe off`, e o `meta.json` guarda qual modo rodou. Ou seja, você não precisa acreditar em mim. Rode o par no seu alvo e meça. + +Agora a gente manda bala. + +--- + +*NeuroSploit é um harness de pentest autônomo em Rust. O benchmark completo, com os artefatos dos dois runs, o scorer e o relatório visual, está publicado no repositório em `benchmarks/typesafe-2026-09-20`. TypeSafe, System One e Jev são do TypeSafe AI. Teste sempre apenas alvos que você tem autorização para testar.* diff --git a/neurosploit-rs/crates/harness/src/attack_graph.rs b/neurosploit-rs/crates/harness/src/attack_graph.rs index ef16181..5870c29 100644 --- a/neurosploit-rs/crates/harness/src/attack_graph.rs +++ b/neurosploit-rs/crates/harness/src/attack_graph.rs @@ -201,6 +201,39 @@ const SENSITIVE: &[&str] = &[ /// Only observations count. A finding that says "could lead to RCE" without an /// observation of code running stays where its evidence put it — which is the /// entire point of grading this way. +/// The kind of data a finding demonstrably exposed, read from any slot the +/// agent used (structured evidence body, or the prose evidence/impact). This is +/// the "data type" axis: a credential or key dump is a confidentiality breach +/// regardless of whether the receipt landed in the structured slot, and it must +/// not be recalibrated away just because `evidence_data` was left null. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DataClass { None, Data, Sensitive } + +const CREDENTIALS: &[&str] = &[ + "password", "passwd", "senha", "bcrypt", "$2y$", "$2a$", "api_key", "apikey", + "api key", "secret", "private key", "begin rsa", "bearer ", "authorization:", + "aws_secret", "aws_access_key", "credit card", "card_number", + "cvv", "ssn", "cpf", +]; + +pub fn data_class(f: &Finding) -> DataClass { + let mut hay = format!("{} {} {}", f.evidence, f.impact, f.payload).to_lowercase(); + if let Some(e) = f.evidence_data.as_ref() { + for ex in [e.attack.as_ref(), e.baseline.as_ref(), e.identity_a.as_ref(), e.identity_b.as_ref()].into_iter().flatten() { + hay.push(' '); + hay.push_str(&ex.body.to_lowercase()); + } + } + if CREDENTIALS.iter().any(|s| hay.contains(s)) { + return DataClass::Sensitive; + } + // A record dump without a credential signature is still data read. + if SENSITIVE.iter().any(|s| hay.contains(s)) { + return DataClass::Data; + } + DataClass::None +} + pub fn demonstrated_rung(f: &Finding) -> Rung { let ev = f.evidence_data.as_ref(); let text = format!("{} {}", f.evidence, f.impact).to_lowercase(); @@ -311,7 +344,12 @@ pub fn cvss_graded(f: &Finding) -> Option { }; // The demonstrated rung decides which impact metrics carry a receipt. let rung = demonstrated_rung(f); - let has_c = matches!(rung, Rung::ReadData | Rung::ReadSensitive | Rung::Wrote | Rung::Executed | Rung::CrossedSystem); + // Data type is a first-class impact receipt: a demonstrated credential/PII + // exposure grants the confidentiality metric even if the rung slot was + // empty (e.g. the agent recorded the dump in prose, not evidence_data). + let dc = data_class(f); + let has_c = matches!(rung, Rung::ReadData | Rung::ReadSensitive | Rung::Wrote | Rung::Executed | Rung::CrossedSystem) + || dc != DataClass::None; let has_i = matches!(rung, Rung::Wrote | Rung::Executed | Rung::CrossedSystem); let has_a = matches!(rung, Rung::Executed | Rung::CrossedSystem); Some(crate::cvss::grade(proposed, move |m| match m { @@ -439,8 +477,39 @@ fn min_impact(a: &'static str, b: &'static str) -> &'static str { } /// Fill in any empty mapping fields on each finding (does not overwrite model-set values). +/// Back-fill a minimal structured `evidence_data` from a finding's prose when +/// the agent left it null but clearly recorded a proof in text. It does NOT +/// invent evidence: it copies what the finding already states (the endpoint as +/// the attack URL, the evidence text as the response body) into the structured +/// slot the deterministic grader and TypeSafe read, so a proof written as +/// narrative is no longer treated as "no receipt". A credential/PII dump that +/// lived only in prose then keeps its severity. +pub fn backfill_evidence(f: &mut Finding) { + if f.evidence_data.is_some() { + return; + } + // Only salvage when there is a substantive textual proof to carry over. + let body = if !f.evidence.trim().is_empty() { f.evidence.clone() } else { return }; + if body.len() < 12 { + return; + } + let url = f.endpoint.split_whitespace().last().unwrap_or(&f.endpoint).to_string(); + let ex = crate::validation::Exchange { + method: f.endpoint.split_whitespace().next().filter(|m| m.chars().all(|c| c.is_ascii_uppercase())).unwrap_or("GET").to_string(), + url, + status: 200, + body, + content_type: String::new(), + ..Default::default() + }; + f.evidence_data = Some(crate::validation::Evidence { attack: Some(ex), ..Default::default() }); +} + pub fn enrich(findings: &mut [Finding]) { for f in findings.iter_mut() { + // Salvage a structured receipt from prose BEFORE grading, so a proof the + // agent wrote as narrative is graded, not discarded. + backfill_evidence(f); let (owasp, mitre, stage) = map_cwe(&f.cwe); if f.owasp.is_empty() { f.owasp = owasp.into(); } if f.mitre.is_empty() { f.mitre = mitre.into(); } @@ -821,4 +890,32 @@ mod ladder_tests { assert!(g2.demonstrated_score >= g.demonstrated_score, "reading data cannot lower the score"); assert!(g2.demonstrated.vector_string().contains("CVSS:3.1/")); } + #[test] + fn data_class_reads_credentials_from_prose_and_backfills() { + // The exact benchmark case: a BOLA whose proof (admin password dump) is + // in prose, evidence_data null. data_class must see the credential, and + // backfill must give the grader a structured receipt. + let mut f = Finding { + cwe: "CWE-639".into(), + title: "BOLA on /api/v2/users/:id".into(), + endpoint: "GET https://t.test/api/v2/users/1".into(), + evidence: "GET /api/v2/users/1 with a customer token returned admin record incl. password=SuperSecret and apiKey=nk_live_x".into(), + ..Default::default() + }; + assert_eq!(data_class(&f), DataClass::Sensitive, "a credential dump is sensitive data"); + assert!(f.evidence_data.is_none()); + backfill_evidence(&mut f); + assert!(f.evidence_data.is_some(), "prose proof is salvaged into the structured slot"); + // Now the graded CVSS keeps a confidentiality receipt (data type), not 0. + let g = cvss_graded(&f).expect("graded"); + assert!(g.demonstrated_score > 0.0, "a demonstrated credential exposure is not zero"); + } + + #[test] + fn backfill_does_not_invent_evidence_when_there_is_none() { + let mut f = Finding { cwe: "CWE-79".into(), endpoint: "https://t.test/x".into(), evidence: "".into(), ..Default::default() }; + backfill_evidence(&mut f); + assert!(f.evidence_data.is_none(), "no prose proof, nothing to salvage"); + } + } diff --git a/neurosploit-rs/crates/harness/src/pipeline.rs b/neurosploit-rs/crates/harness/src/pipeline.rs index af83cee..e4b27c6 100644 --- a/neurosploit-rs/crates/harness/src/pipeline.rs +++ b/neurosploit-rs/crates/harness/src/pipeline.rs @@ -2373,15 +2373,30 @@ async fn finish(cfg: RunConfig, _lib: &Library, pool: &ModelPool, recon: String, // says shows no real impact loses its C/I/A the same // way an absent receipt would — the demonstrated // score follows the evidence, calibrated. - if adj.impact_demonstrated < 0.5 { + // Data type is a guardrail against over-recalibration. + // The impact is only stripped when BOTH the model was + // unconvinced AND nothing sensitive was actually shown + // (no credential/PII signature, and the calibrated + // data-sensitivity is low). A demonstrated credential + // or PII exposure keeps its severity even on a thin + // receipt — the KIND of data is itself the impact. + let dc = crate::attack_graph::data_class(f); + let sensitive_shown = dc != crate::attack_graph::DataClass::None || adj.data_sensitivity >= 0.5; + if adj.impact_demonstrated < 0.5 && !sensitive_shown { if let Some(g) = crate::attack_graph::cvss_graded(f) { - // Strip demonstrated impact the model is not - // convinced of; keep potential as context. let dropped = crate::cvss::grade(g.potential, |_| false); if dropped.demonstrated_score < g.demonstrated_score { f.cvss = format!("{:.1} ({})", dropped.demonstrated_score, dropped.demonstrated.vector_string()); } } + } else if sensitive_shown && f.cvss.is_empty() { + // Sensitive data shown but no score yet: grade it + // WITH the data-type receipt rather than leaving it blank. + if let Some(g) = crate::attack_graph::cvss_graded(f) { + if g.demonstrated_score > 0.0 { + f.cvss = format!("{:.1} ({})", g.demonstrated_score, g.demonstrated.vector_string()); + } + } } refined += 1; } diff --git a/neurosploit-rs/crates/harness/src/typesafe.rs b/neurosploit-rs/crates/harness/src/typesafe.rs index fdcb078..001c3d6 100644 --- a/neurosploit-rs/crates/harness/src/typesafe.rs +++ b/neurosploit-rs/crates/harness/src/typesafe.rs @@ -194,12 +194,30 @@ impl TypeSafe { Question::noul( "Does the evidence show REAL impact (data read/written, code executed, a boundary crossed), as opposed to only that a payload was reflected or an error appeared?", "concrete impact is shown in the evidence", - "no impact is shown — only a mechanic or a reflection", + "no impact is shown - only a mechanic or a reflection", + ), + ); + // Data type is a separate axis from "was impact demonstrated": a flaw + // that exposes credentials or PII is severe by the KIND of data it + // touched, even when the receipt is thin. Scored so the calibration can + // consider it instead of collapsing purely on the impact Noul. + qs.insert( + "data_sensitivity".to_string(), + Question::score( + "Judging only by what the evidence shows was exposed or affected, how sensitive is that data?", + &[ + "nothing sensitive: only reflection, an error, or public content", + "internal or low-sensitivity data (ids, non-secret fields)", + "personal data (PII): emails, names, addresses, phone numbers", + "secrets: passwords, API keys, tokens, private keys, payment data", + ], ), ); let answers = self.evaluate(state, qs).await?; let verdict = answers.get("verdict").cloned().unwrap_or_default(); let impact = answers.get("impact_demonstrated").and_then(|a| a.noul).unwrap_or(0.0); + // Score returns a weighted position on the 0..3 ladder; normalise to 0..1. + let data_sensitivity = answers.get("data_sensitivity").and_then(|a| a.score).map(|s| (s / 3.0).clamp(0.0, 1.0)).unwrap_or(0.0); Ok(Adjudication { verdict: verdict.choice.clone().unwrap_or_else(|| "needs-review".into()), p_confirmed: verdict.p("confirmed"), @@ -207,6 +225,7 @@ impl TypeSafe { p_rejected: verdict.p("rejected"), confidence: verdict.confidence.unwrap_or(0.0), impact_demonstrated: impact, + data_sensitivity, }) } } @@ -222,6 +241,11 @@ pub struct Adjudication { pub confidence: f64, /// Probability real impact was shown (0..1). pub impact_demonstrated: f64, + /// Calibrated data-sensitivity (0..1): 1.0 = secrets/credentials exposed. + /// A high value means the finding must NOT be recalibrated down just because + /// the impact receipt was thin - the KIND of data is itself the impact. + #[serde(default)] + pub data_sensitivity: f64, } impl Adjudication { @@ -280,7 +304,7 @@ mod tests { #[test] fn calibrated_confidence_folds_in_demonstrated_impact() { // High p_confirmed but NO demonstrated impact → confidence is held back. - let a = Adjudication { verdict: "confirmed".into(), p_confirmed: 0.9, p_needs_review: 0.05, p_rejected: 0.05, confidence: 0.8, impact_demonstrated: 0.0 }; + let a = Adjudication { verdict: "confirmed".into(), p_confirmed: 0.9, p_needs_review: 0.05, p_rejected: 0.05, confidence: 0.8, impact_demonstrated: 0.0, data_sensitivity: 0.0 }; assert!((a.calibrated_confidence() - 0.45).abs() < 1e-9, "no impact halves the weight"); // Same, with full impact → near p_confirmed. @@ -290,9 +314,9 @@ mod tests { #[test] fn review_is_wanted_on_a_split_distribution() { - let split = Adjudication { verdict: "confirmed".into(), p_confirmed: 0.45, p_needs_review: 0.3, p_rejected: 0.25, confidence: 0.4, impact_demonstrated: 0.5 }; + let split = Adjudication { verdict: "confirmed".into(), p_confirmed: 0.45, p_needs_review: 0.3, p_rejected: 0.25, confidence: 0.4, impact_demonstrated: 0.5, data_sensitivity: 0.0 }; assert!(split.wants_review(), "no option clears 0.6 — a human should look"); - let clear = Adjudication { verdict: "confirmed".into(), p_confirmed: 0.88, p_needs_review: 0.08, p_rejected: 0.04, confidence: 0.8, impact_demonstrated: 0.9 }; + let clear = Adjudication { verdict: "confirmed".into(), p_confirmed: 0.88, p_needs_review: 0.08, p_rejected: 0.04, confidence: 0.8, impact_demonstrated: 0.9, data_sensitivity: 1.0 }; assert!(!clear.wants_review()); }