
Um mecanismo de reconstrução forense para resposta a incidentes em nuvem e identidade.
Um mecanismo de reconstrução forense para resposta a incidentes em nuvem e identidade.
Com base em telemetria fragmentada de nuvem, SaaS e identidade — logs do plano de controle, eventos de login, atividade de tokens e consentimento — a NV reconstrói como uma intrusão provavelmente se moveu entre contas e serviços, enumera os outros caminhos mais prováveis que poderia ter seguido e relata cada etapa com confiança calibrada e explicável. Ela se recusa a afirmar o que as evidências não podem sustentar.
A detecção diz que algo aconteceu. O Nimbus Vestige diz como — e o que mais.
Intrusões modernas não "invadem". Elas fazem login. A identidade é agora o principal vetor de ataque, implicada na grande maioria das investigações de IR em nuvem, e a maioria das intrusões abrange múltiplas superfícies — identidade mais nuvem mais SaaS mais endpoint. A telemetria que as registra é fragmentada e inconsistente, o que já força os respondedores a reconstruir a história manualmente a partir de dados incompletos.
Essa reconstrução manual é lenta e falha de uma forma previsível: o respondedor se ancora na primeira narrativa plausível e perde a real. A NV automatiza a reconstrução e ataca diretamente esse modo de falha, sempre apresentando o espaço classificado de caminhos plausíveis, não uma única história.
Crucialmente, a NV faz isso com honestidade como produto. O inimigo declarado do mercado é a confiança de caixa-preta — uma ferramenta que afirma uma conclusão sem mostrar seu trabalho. Cada número produzido pela NV é rastreável até a evidência específica que o gerou, e qualquer coisa que ela não possa sustentar é omitida em vez de adivinhada.
A NV não é um detector, um scanner, um auditor ou um executor de ataques. Essas ferramentas dizem que algo aconteceu e o medem. A NV trabalha para trás — reconstrução abdutiva do mecanismo a partir de padrões, em um ambiente de identidade fragmentado.
A equipe azul se regenera; a equipe vermelha se commoditiza. Ferramentas ofensivas encontram um conjunto finito e corrigível de falhas e estão sendo incorporadas a pipelines automatizados de segurança em CI/CD. Reconstruir como uma intrusão aconteceu nunca se resolve — os atacantes continuam inventando, então a necessidade é permanente e autorrenovável.
O mecanismo é independente do substrato. A lógica central — reconstruir o mecanismo a partir de padrões, com confiança calibrada e um limite de parada — está comprometida com nuvem/identidade primeiro, mas é portável para rede, endpoint e OT depois. O alvo pode mudar sem reescrever a tese.
A reconstrução viabiliza o endurecimento. Depois que você sabe como eles entraram — e quais outras portas estavam abertas — você constrói as defesas. Os caminhos não percorridos, mas plausíveis, são um backlog de endurecimento, muitas vezes mais valioso do que a própria reconstrução, porque a maioria das violações explora exposição evitável, não táticas inéditas.
Degrada-se graciosamente na intrusão inédita — exatamente o incidente que mais importa. Um mecanismo de assinaturas/regras fica cego diante de um zero-day; o núcleo abdutivo da NV ainda produz um caminho mais provável, honestamente sinalizado como de menor confiança.
A honestidade é um fosso. Confiança calibrada e baseada em casos, com alternativas classificadas, é exatamente o que o mercado diz querer e o que concorrentes de caixa-preta estruturalmente não podem oferecer sem redesenho.
Logs brutos do provedor → grafo normalizado de eventos/entidades → mecanismo de reconstrução → JSON → GUI.``` O365 / Entra audit logs nv_extract_identity_events.py AWS CloudTrail (IAM/STS/S3) nv_extract_cloudtrail_events.py (ingest + normalize) ▼ normalized identity events (JSONL) ← one event model, any substrate │ nv/graph.py (typed per-actor timelines) ▼ reconstruction engine ├─ nv/patterns.py known layer: ATT&CK identity pattern library ├─ nv/providers.py provider packs: per-substrate op→ATT&CK vocab (Entra + AWS) ├─ nv/validation.py Phase 3: provenance + integrity gate on every pattern ├─ nv/feeds.py live ATT&CK STIX / TAXII / Sigma clients + scheduler ├─ nv/confidence.py evidence-corroboration scorer (calibratable weights) ├─ nv/calibration.py fit + measure confidence against labeled ground truth ├─ nv/reconstruct.py most-likely chain + ranked competing paths + trust floor └─ nv/scope.py authorized-scope gate (refuses unauthorized tenants/accounts) │ run_nv.py ▼ reconstruction.json ──► nv_gui.html (embedding canvas + two-column view + trust slider)
A GUI é uma **camada de visualização pura** — ela renderiza a saída do mecanismo e permite que o analista mova
o piso de confiança. Ela não contém lógica de reconstrução própria.
---
## O modelo de confiança (a credibilidade de todo o produto)
Cada etapa carrega uma confiança em [0, 1], construída por **corroboração de evidências**:```
confidence = per-technique base rate
+ bonus for each independent corroborating signal
(source IP, device, successful outcome, temporal adjacency, broad-consent flags)
− penalty for missing signals (e.g. no source IP to corroborate origin)
As pontuações de caminho compõem seus elos por média geométrica, então um elo fraco honestamente rebaixa um caminho em vez de ser diluído pela média.
Cada peso acima — as taxas base por técnica, os bônus por sinal, as penalidades por ausência —
vive em uma única tabela PARAMS em nv/confidence.py. Esses são os priors v1 da NV, e eles são
calibráveis: nv/calibration.py pode reajustá-los contra verdade de campo rotulada
e o motor carregará o resultado, recorrendo aos priors v1 quando nenhuma
calibração for fornecida (veja abaixo).
A regra de retenção, embutida no contrato de saída do motor — não apenas na interface. Abaixo do piso, uma etapa é retida. Arrastar o piso é a troca honestidade-versus-cobertura tornada física: para cima para alta confiança/estrito, para baixo para permissivo/alta cobertura. O piso padrão é uma decisão editorial sobre onde a NV se posiciona antes de o analista tocá-la.
Os pesos v1 são defensáveis, mas são priors. nv/calibration.py os ajusta
contra verdade de campo rotulada — eventos em que sabemos quais faziam parte da
intrusão e quais eram benignos — e, crucialmente, mede se o ajuste realmente
ajudou, de modo que uma calibração só é adotada se reduzir o erro de calibração em vez de apenas
mover números.
Confiança calibrada significa uma coisa: a confiança da NV para uma etapa deve ser igual à probabilidade de que a etapa realmente estava no caminho do ataque. Assim, a confiança de cada evento rotulado é tratada como uma probabilidade prevista, e o harness ajusta:
Os limites verified/withheld são política, não calibração, e são deixados intactos.
Ele reporta escore de Brier, log-loss, ECE, AUC, uma tabela de confiabilidade e uma varredura do piso de confiança (recall de etapas verdadeiras vs taxa de falsos positivos benignos em cada piso), antes e depois — para que a troca honestidade-versus-cobertura seja legível em vez de um único número.```bash
python3 calibrate.py --labeled run_labeled.jsonl --origin live
--run-label "badzure-2026-07 tenant-x" --out calibration.json
python3 calibrate.py --out calibration.json
python3 run_nv.py events.jsonl out.json --authorize t.onmicrosoft.com
--calibration calibration.json
**Fonte de verdade (ground truth).** A entrada honesta é a telemetria de uma execução do BadZure / MAAD-AF em um
tenant real, rotulada ao unir o log de auditoria unificado ao registro de atividade da própria ferramenta de ataque
(todo evento causado pela ferramenta está on-chain; todo o resto é benigno — veja
`LABELED_SCHEMA` em `nv/calibration.py`). Até que você a aponte para uma execução real,
`eval/calibration_cases.py` fornece um corpus **proxy** de forma documentada, e todo
artefato ajustado a partir dele é carimbado com `source="proxy"` em sua proveniência, para que um ajuste proxy nunca
possa ser confundido com um ao vivo. `calibration.proxy.json` é esse artefato proxy incluído.
No corpus proxy incluído, o ajuste é uma melhoria clara — Brier 0,398 → 0,045, ECE
0,576 → 0,081, AUC 0,81 → 0,91, e a taxa de falsos positivos benignos no piso de 0,60
colapsa de 0,69 para 0,00 (os priors v1 eram excessivamente confiantes em atividade benigna).
O proxy é deliberadamente conservador; uma execução em tenant real é o que produz pesos que você
deve realmente implantar.
## Integridade de confiança (Fase 3)
Dois controles de primeira classe protegem a reconstrução de entradas ruins:
- **Portão de escopo autorizado** (`nv/scope.py`) — a NV se recusa a executar em qualquer estate que não esteja na
lista autorizada. Execute com `--authorize <tenant-domain>` por estate que você tem permissão
para investigar.
- **Validação de fonte de padrões** (`nv/validation.py`) — nenhum padrão entra na biblioteca
sem passar por: (1) allowlist de fontes confiáveis, (2) checksum de conteúdo / verificação de adulteração,
(3) boa formação de esquema, (4) ID de técnica válido no estilo ATT&CK. Cada padrão
admitido mantém um registro de auditoria; rejeições são registradas com um motivo. Um feed
envenenado ou malformado é interrompido upstream antes que possa fabricar uma reconstrução falsa.
Este é o mesmo ceticismo que o mecanismo aplica às evidências, movido para os próprios padrões.
---
## Mantendo o conhecimento atualizado (ficando à frente da evolução dos atacantes)
Um mecanismo de reconstrução é tão atual quanto sua biblioteca de padrões mais sua capacidade de
lidar com o que a biblioteca nunca viu. A NV aborda a defasagem em **duas frentes
independentes**, por design:
### 1. A camada conhecida permanece atualizada por meio de um pipeline de atualização validado
A biblioteca não é codificada — `nv/patterns.py` carrega todo padrão (incluindo o
conjunto embutido) por meio de `nv/validation.py`, então feeds externos entram pelo mesmo caminho
confiável. Os clientes de rede ao vivo que puxam esses feeds em um cronograma são implementados em
`nv/feeds.py` e conduzidos por `update_feeds.py`. Fontes, em níveis de confiança:
- **MITRE ATT&CK (STIX)** — a taxonomia autoritativa de técnicas. O conector lê
o índice STIX do ATT&CK, puxa o release enterprise mais recente e atualiza o
nome autoritativo da técnica e a tática para toda operação sobre a qual a NV raciocina —
descartando qualquer uma cuja técnica o ATT&CK tenha desde então revogado ou descontinuado. A NV mantém
a propriedade da ligação operação→técnica e do prior de taxa base; o ATT&CK possui
a taxonomia.
- **CTI verificado via TAXII 2.1** — um cliente completo de descoberta → api-root → coleção → objetos.
Puxa objetos STIX attack-pattern de uma coleção CTI verificada e atualiza
as técnicas que a NV rastreia. Ponderado abaixo do ATT&CK.
- **Ruleset comunitário Sigma** — puxado como um arquivo git; cada regra de nuvem/identidade que
nomeia uma operação O365/Entra e carrega uma tag `attack.tXXXX` torna-se um
candidato operação→técnica, com uma taxa base derivada do próprio `level` de severidade
da regra e então reduzida pelo peso da fonte comunitária. Regras que não mapeiam são
ignoradas com um motivo, nunca adivinhadas.
Em uma colisão de operações, a união é ordenada por confiança ascendente antes do commit, então uma
ligação ATT&CK autoritativa sempre vence sobre uma comunitária; conteúdo de nível inferior ainda é
admitido e registrado na auditoria como corroboração. `update_library(candidates)`
re-ingere todo o conjunto por meio da validação e reconstrói a biblioteca atomicamente, então um
feed envenenado nunca pode deixar a biblioteca parcialmente atualizada.
**Duas allowlists, não uma.** A validação já faz o gate na tag de fonte; os conectores
adicionam uma allowlist de *host* na camada de rede, então um conector só pode buscar de um host
confiável via TLS — o equivalente de transporte da allowlist de fonte, e uma proteção contra uma
URL de feed sequestrada ou digitada incorretamente. Cada busca registra o sha256 do payload bruto, a URL e o tempo
como proveniência, então uma re-busca posterior pode detectar mutação upstream.
**Por que a camada de validação importa mais à medida que os feeds crescem:** quanto mais você automatiza
atualizações, mais um pipeline de auto-atualização se torna um risco de confiança disfarçado — um
feed errado ou envenenado injeta padrões falsos e fabrica reconstruções falsas. A NV trata
a ingestão como adversarial: allowlist, checksum, esquema e trilha de auditoria em todo padrão,
toda atualização.
### 2. A camada abdutiva cobre o que nenhum feed ainda nomeou
Feeds sempre ficam atrás do tradecraft mais recente. O núcleo abdutivo é a proteção: quando uma
operação observada não corresponde a **nenhum** padrão da biblioteca, a NV não a descarta — ela reconstrói
o caminho mais provável a partir de primeiros princípios e o sinaliza como `no known match` com confiança
reduzida. Isso é validado no conjunto de avaliação (`eval/ground_truth_cases.py`), onde um
passo deliberadamente desconhecido de roubo de token de identidade gerenciada é capturado, sequenciado corretamente,
e honestamente rebaixado abaixo do piso de confiança.
Juntas, essas medidas significam que a NV nunca fica totalmente defasada: a camada conhecida rastreia a fronteira
publicada por meio de um pipeline resistente a envenenamento, e a camada abdutiva impede que a ferramenta
fique cega para a intrusão que a fronteira ainda não alcançou.
### Cadência de atualização sugerida
`nv/feeds.py` carrega uma cadência por feed e `update_feeds.py` só puxa o que é devido,
então pode ser colocado diretamente no cron ou em um timer systemd:
- ATT&CK STIX — 90 dias (alguns releases oficiais por ano).
- CTI/TAXII — 7 dias (conforme o feed publica), sempre por meio da validação.
- Sigma — 30 dias.```bash
# run whatever is due, keeping state under ./.nv_feeds (cron-friendly)
python3 update_feeds.py
# force all feeds now but only report what would change
python3 update_feeds.py --force --dry-run
# run fully offline against captured fixtures (no network)
python3 update_feeds.py --offline eval/fixtures --force
Após qualquer alteração na biblioteca, execute novamente eval/run_eval.py para confirmar que as formas de ataque conhecidas ainda são reconstruídas, eval/validation_cases.py para confirmar que o gate ainda rejeita entradas inválidas e eval/feeds_offline_test.py para confirmar que o caminho do feed está intacto de ponta a ponta.
A reconstrução exibida em nv_gui.html é construída a partir de um conjunto de dados de pesquisa público
— o corpus de log de auditoria unificada do Office 365 da invictus-ir — e não de qualquer tenant
ativo de uma empresa. Ele existe para que o mecanismo possa ser demonstrado e testado internamente de ponta a ponta sem
a cooperação de ninguém. Para usar o NV de verdade, uma organização conecta seus próprios dados de auditoria do Entra/M365,
conforme descrito abaixo. Nenhum dado proprietário ou de cliente está incluído em nenhum lugar deste
projeto.
O NV roda onde quer que você o execute; seus logs nunca precisam sair do seu ambiente. Quatro etapas:
1 — Exporte seus logs de auditoria. O NV lê o log de auditoria unificada do Microsoft 365. Obtenha-o
do Microsoft Purview (Pesquisa de auditoria → exportar CSV), do cmdlet Search-UnifiedAuditLog
do Exchange Online PowerShell, dos endpoints auditLogs / signIns
do Microsoft Graph, ou de uma exportação do Sentinel/SIEM de OfficeActivity e SigninLogs.
2 — Normalize-os no modelo de eventos sobre o qual o NV raciocina:```bash python3 nv_extract_identity_events.py your_audit_export.csv your_events.jsonl
Isto mantém os inícios de sessão, concessões de consentimento, alterações de principais de serviço e de funções, e o acesso à caixa de correio — incluindo operações de identidade que a NV nunca nomeou, para que ainda cheguem à camada abdutiva — e descarta o resto. Lê um CSV com a coluna padrão `AuditData`, ou JSON/JSONL onde cada registo é um objeto `AuditData` (a forma como o corpus de investigação invictus-ir é distribuído).
**3 — Reconstrução, limitada por âmbito.** O motor recusa-se a executar num inquilino que não autorizaste explicitamente:```bash
python3 run_nv.py your_events.jsonl reconstruction.json \
--authorize yourtenant.onmicrosoft.com
4 — Visualizar. Abra nv_gui.html e clique em ⤒ carregar reconstruction.json para apontá-lo para
o arquivo que o mecanismo acabou de produzir — a mesma tela então mostra seu incidente, suas
entidades e suas faixas de confiança. Cada ponto na tela de incorporação reexecuta a
reconstrução para aquela entidade ao clicar.
O mecanismo é independente de substrato — apenas o vocabulário de operações é específico do provedor
(nv/providers.py). O caminho AWS é o mesmo três etapas contra o CloudTrail:```bash
python3 nv_extract_cloudtrail_events.py cloudtrail.json aws_events.jsonl
python3 run_nv.py aws_events.jsonl reconstruction.json --authorize aws:123456789012
O ingest mantém eventos de IAM/STS/sign-in (incluindo operações IAM novas, para que cheguem à
camada abdutiva) além de leituras de objetos S3, e faz o escopo-gate na **conta** da AWS em vez de um
domínio de tenant. O mesmo núcleo abdutivo, modelo de confiança e piso de confiança se aplicam sem alterações.
A GUI também traz um **banner de dados de demonstração** e um guia no aplicativo "Conectando seus próprios dados",
para que qualquer pessoa que a abra entenda que a reconstrução é dados de amostra públicos até
que conecte os seus próprios.
## Estrutura do projeto```
run_nv.py reconstruction engine entrypoint (scope-gated)
nv_extract_identity_events.py ingest: M365/Entra unified audit log -> event model
nv_extract_cloudtrail_events.py ingest: AWS CloudTrail -> event model [iteration 3]
update_feeds.py pull + validate threat-intel feeds on a cadence [iteration 1]
calibrate.py fit confidence weights from labeled ground truth [iteration 2]
nv/ the engine package
graph.py patterns.py validation.py confidence.py reconstruct.py scope.py
providers.py per-substrate op->ATT&CK vocabulary packs (Entra + AWS) [iteration 3]
feeds.py ATT&CK STIX / TAXII 2.1 / Sigma clients + scheduler [iteration 1]
calibration.py metrics + parameter fit [iteration 2]
eval/ evals + offline fixtures (no network, no tenant)
nv_gui.html pure view layer (loads engine reconstruction.json)
calibration.proxy.json bundled proxy calibration artifact [iteration 2]
Execute tudo a partir da raiz do projeto para que o pacote nv seja importável.
python3 nv_extract_identity_events.py auditrecords.csv nv_identity_events.jsonl
python3 run_nv.py nv_identity_events.jsonl reconstruction.json
--trust-floor 0.6 --authorize your-tenant.onmicrosoft.com
Mantenha a biblioteca de padrões atualizada e ajuste a confiança:```bash
python3 update_feeds.py # pull + validate feeds that are due
python3 calibrate.py --labeled run.jsonl --origin live --out calibration.json
python3 eval/run_eval.py # attack shapes reconstruct correctly python3 eval/validation_cases.py # poisoned patterns are rejected python3 eval/feeds_offline_test.py # feed connectors + scheduler (offline) python3 eval/providers_aws_test.py # AWS chain reconstructs through the same engine python3 calibrate.py # calibration harness on the bundled proxy corpus
---
## Estado
**Funciona de ponta a ponta com dados reais** (conjunto de dados O365 do invictus-ir), em todo o plano:
| Fase | Item | Estado |
|---|---|---|
| 1 | Modelo normalizado de eventos/entidades | concluído |
| 1 | Ingestão + normalização (ponto de entrada Entra/M365) | concluído |
| 2 | Camada conhecida (biblioteca de padrões ATT&CK) | concluído |
| 2 | Modelo de confiança de corroboração de evidências | concluído |
| 2 | Piso de confiança no contrato de saída | concluído |
| 2 | Núcleo abdutivo (camada desconhecida) | concluído, comprovado por avaliação |
| 2 | Hipóteses concorrentes classificadas | concluído |
| 3 | Camada de validação de fontes de padrões | concluído |
| 3 | Controlo de âmbito autorizado | concluído |
| 3 | Conectores de feeds ao vivo (ATT&CK STIX / TAXII / Sigma) + agendador | concluído |
| 4 | Ecrã de duas colunas na saída real do motor | concluído |
| 4 | Tela de embeddings / similaridade | concluído |
| 4 | Reconstrução por entidade orientada pela tela (clique reexecuta a cadeia) | concluído |
| 4 | GUI carrega o `reconstruction.json` do motor (aponte para o seu ficheiro) | concluído |
| 4 | Substrato multi-fornecedor (AWS CloudTrail, comprovado por avaliação) | concluído |
| 5 | Estética / temas | concluído |
| 5 | Ferramenta de calibração de confiança + integração no motor | concluído |
### Lacunas conhecidas e honestas (próximas iterações)
- **Números de calibração em tenant real.** A ferramenta de calibração está *completa* e a
interface está comprovada, mas o `calibration.proxy.json` incluído é ajustado a dados
de proxy com formato documentado. Pesos implementáveis exigem executar a ferramenta
contra uma execução real de BadZure / MAAD-AF — um passo operacional para quem adotar.
- **Crescimento do vocabulário orientado por feeds.** Os conectores atualizam a NV de operações
sobre a qual o motor já raciocina; permitir que um feed *introduza* uma nova operação (e a
integre na classificação inicial/pivô/coleção) é trabalho futuro.
- **Profundidade do segundo fornecedor.** A AWS está incluída como prova de independência do
substrato (pacote + ingestão + avaliação), mas apenas IAM/STS/S3. Alargar o pacote AWS,
adicionar conectores de feeds específicos do fornecedor e um terceiro substrato (GCP, Okta)
são os próximos passos.
---
## Licença
O Nimbus Vestige é **de código-fonte disponível** sob a **Licença Não Comercial PolyForm
1.0.0** (ver [LICENSE](https://gitlab.com/dobybaxter127/nimbus-vestige/-/blob/main/LICENSE)). Toda a ferramenta — motor de reconstrução, ambas as
ingestões de fornecedores, GUI e ferramenta de avaliação — é livre de inspecionar, executar
e usar para qualquer fim **não comercial**: projetos pessoais, investigação, educação,
organizações sem fins lucrativos e avaliação. Leia cada linha antes de decidir.
**O uso comercial requer uma licença paga** — usá-la num produto ou serviço que venda
ou aloje, dentro dos sistemas de produção ou internos de uma empresa com fins lucrativos,
ou em serviços pagos de resposta a incidentes ou envolvimentos com clientes. Ver
[COMMERCIAL-LICENSE.md](https://gitlab.com/dobybaxter127/nimbus-vestige/-/blob/main/COMMERCIAL-LICENSE.md).
---
### Autor
Doby Baxter 2026