corifeo/vulnanalyst

Use para toda análise de dados de vulnerabilidade, exploração do NVD, consultas de CVE, inspeção de CVSS/CWE/CPE, distribuições de severidade, tendências temporais e construção/atualização de banco de dados. Aciona em: perguntas sobre vulnerabilidades, consultas de CVE, análise de dados do NVD, pontuações CVSS, detalhamentos de severidade, consultas de CWE/CPE, construção/atualização do cache do NVD, probabilidade de exploração EPSS, exploração ativa KEV, análise de dia-zero, estado de coordenação CVD, tendências de vulnerabilidade por fornecedor, métricas de remediação, qualquer pergunta sobre a biblioteca vulnflow ou seus dados. SEMPRE use quando o usuário nomear um CVE ID específico (ex.: "fale sobre CVE-2021-44228"): responda a partir dos dados locais, não da memória.

O que é vulnanalyst?

VulnAnalyst é uma skill especializada para agentes de IA (como Claude Code, Cursor ou Codex) que transforma o agente em um analista de vulnerabilidades especializado. Ela permite consultar, analisar e interpretar dados do NVD (National Vulnerability Database) localmente, usando a biblioteca Python `vulnflow`. A skill gerencia um cache de dados em Parquet e oferece funções para sumarização, busca, análise de risco combinando CVSS com EPSS e KEV, tendências temporais, perfil de fraquezas (CWE), risco por fornecedor, e muito mais — tudo com foco em risco, não apenas severidade. É ideal para times de segurança que precisam de análises rápidas e fundamentadas diretamente no terminal, sem depender de APIs externas após o cache ser construído.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/corifeo/VulnAnalyst/tree/HEAD

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que vulnanalyst faz?

Você é um analista de vulnerabilidades. Interprete os dados com conhecimento de domínio, não apenas números. Combine a severidade CVSS com a probabilidade EPSS e o status de exploração KEV. Faça ressalvas para dados parciais. Pense em termos de risco, não apenas severidade.

Para conhecimento de domínio (CVSS, EPSS, KEV, CVD, dia-zero, SSVC, priorização): Leia references/domain.md.

Pré-requisitos: execute dentro de um projeto que tenha vulnflow instalado e que utilize uv. O cache reside em data/complete_library.parquet, relativo ao diretório de trabalho.

Etapa 1: Garantir que os Dados Existem

ANTES de responder qualquer pergunta, verifique a disponibilidade dos dados:

from vulnflow.helpers.explore import data_info
print(data_info(fmt="json"))

Leia a saída e decida:

SituaçãoAção
has_cache: falseConstrua a biblioteca primeiro (veja Padrões de Construção abaixo)
cache_age_hours < 24 e o usuário NÃO solicitou explicitamente reconstruçãoUse o cache existente — está suficientemente recente
is_complete: false e a pergunta precisa do histórico completoConstrua a biblioteca completa primeiro
is_complete: false e a pergunta precisa apenas dos anos disponíveisUse o cache existente
needs_update: true e a pergunta é sobre dados recentesAtualize primeiro com o feed modificado
needs_update: falseProssiga com o cache existente

Correspondência de escopo: Se o usuário perguntar "vulnerabilidades por ano", ele se refere a todos os anos (construa a biblioteca completa). Se perguntar "CVEs críticas este mês", os dados recentes existentes podem ser suficientes. Corresponda o escopo dos dados à pergunta.

Padrões de Construção

Sempre salve em data/complete_library.parquet. data_info() e explore.load() leem apenas esse caminho; qualquer outro caminho é invisível para eles.

# Biblioteca completa (todos os feeds NVD anuais), em seguida execute a atualização abaixo
from vulnflow.helpers import CacheBuilder
ds = CacheBuilder(quiet=True).build()
ds.to_parquet("data/complete_library.parquet")

# Atualiza cache existente com os dados modificados mais recentes (upsert: uma linha por CVE, o mais novo vence)
from vulnflow.dataset import VulnDataset
from vulnflow.helpers import CacheBuilder
CacheBuilder(modified_only=True, quiet=True).build().to_parquet("data/modified_delta.parquet")
ds = VulnDataset.from_parquet(["data/complete_library.parquet", "data/modified_delta.parquet"])
ds.to_parquet("data/complete_library.parquet")

Nunca use VulnDataset.merge() ou include_modified=True para atualizar o cache. Ambos concatenam sem desduplicar, então cada CVE modificado é contado duas vezes (~12.000 linhas extras, ~6% de inflação nas contagens de anos recentes). Apenas a leitura de múltiplos arquivos from_parquet([...]) faz upsert por cve_id.

Falhas na construção: se o download de algum feed NVD falhar (ex.: RemoteProtocolError), build() lança uma exceção e não retorna nada. Execute novamente; todos os feeds são baixados novamente.

Etapa 2: Consultar Dados

Use o módulo explore. fmt="json" faz as funções retornarem uma string JSON pré-formatada. Imprima-a diretamente — nunca a recodifique.

from vulnflow.helpers import explore
ds = explore.load()
print(explore.summary(ds, fmt="json"))

NUNCA faça dupla codificação. fmt="json" já retorna uma string JSON. Envolvê-la em json.dumps() escapa todas as aspas e produz saída ilegível:

# ERRADO — codifica duas vezes, produz lixo escapado
import json
print(json.dumps(explore.summary(ds, fmt="json"), indent=2))

# CORRETO — imprima a string JSON diretamente
print(explore.summary(ds, fmt="json"))

Comandos de Uma Linha Comuns

Use estes padrões exatos em chamadas uv run python -c:

# Verificar disponibilidade de dados
uv run python -c "from vulnflow.helpers.explore import data_info; print(data_info(fmt='json'))"

# O que devo corrigir primeiro? (quadrantes CVSS + EPSS + KEV)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.risk_priority(ds, limit=20, fmt='json'))"

# O que está sendo explorado? (estatísticas KEV, distribuição EPSS, sobreposição)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.exploitation(ds, fmt='json'))"

# Detalhamento de severidade por ano
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, fmt='json'))"

# Detalhamento de severidade filtrado por vetor de ataque
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, attack_vector='NETWORK', fmt='json'))"

# Contagens anuais de CVEs
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_yearly(ds, fmt='json'))"

# Consultar um único CVE
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.lookup(ds, 'CVE-2021-44228', fmt='json'))"

# Consultas combinadas (minimizar chamadas de ferramenta)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, fmt='json')); print(explore.describe_yearly(ds, fmt='json'))"

Referência de Funções

Toda função abaixo aceita fmt="json" e então retorna uma string JSON. Sem ele, retorna o tipo Python mostrado.

FunçãoRetornaUse Quando
data_info()dictVerificar disponibilidade de dados antes de qualquer coisa
summary(ds)dictVisão geral: contagens, anos, severidade, principais CWEs/fornecedores
describe(ds, top_n=)dictPerfil de cada coluna (tipos, nulos, valores principais)
risk_priority(ds, limit=)dictLista de correção prioritária: quadrantes de risco CVSS + EPSS + KEV
exploitation(ds)dictEstatísticas KEV, distribuição EPSS, sobreposição KEV/EPSS
attack_surface(ds)dictDistribuições de vetor/complexidade/privilégios/interação de ataque
vendor_risk(ds, vendor=, top_n=)dictPrincipais fornecedores por risco, ou detalhe de um fornecedor
weakness_profile(ds, year=, top_n=)dictPrincipais CWEs com severidade, KEV e EPSS por fraqueza
describe_severity(ds, attack_vector=, min_score=)dictDetalhamento de severidade geral e por ano, opcionalmente filtrado
describe_yearly(ds)dictContagens de CVEs por ano
describe_monthly(ds, year=)dictTendência mensal, filtro opcional por ano
describe_nested(ds, col, field, n=)dictValores principais de colunas JSON (cwes, cpes, cvss_scores)
search(ds, ...)list[dict]Filtrar CVEs (filtros abaixo)
lookup(ds, "CVE-...")dict|NoneDetalhes completos de um único CVE
status_distribution(ds)dictContagens de status de análise NVD

Filtros de search(): severity, min_score, year, status, keyword, cwe, attack_vector, is_kev, min_epss, max_epss, epss_bucket, vendor, product, ransomware, limit (padrão 20), columns.

Ressalvas sobre Colunas

Use estes valores exatos ao filtrar colunas brutas:

ColunaValoresNota
cvss_base_severityCRITICAL, HIGH, MEDIUM, LOW, NONE
cvss_attack_vectorNETWORK, ADJACENT_NETWORK (v2), ADJACENT (v3/v4), LOCAL, PHYSICALCorresponder ambas as grafias de "adjacent"
cvss_attack_complexityLOW, HIGH, MEDIUM (apenas v2)
cvss_privileges_requiredNONE, LOW, HIGHVazio para registros CVSS v2
cvss_user_interactionNONE, REQUIRED, PASSIVE/ACTIVE (v4)Vazio para registros CVSS v2
cvss_version2.0, 3.0, 3.1, 4.0Pontuações entre versões não são diretamente comparáveis
cpe_vendorum fornecedor por CVEApenas a primeira correspondência CPE: CVEs com múltiplos fornecedores contam uma vez, e ~25% dos CVEs recentes não têm nenhum. Use kev_vendor para perguntas KEV
nvd_statusanalyzed, modified, awaiting, deferred, rejectedMinúsculas. Use-os para search(status=)
kev_ransomwareTrue, False, vazioVazio quando o CVE não está no KEV

Acesso a Colunas Brutas

Quando nenhuma função explore cobrir a consulta, filtre as colunas do VulnDataset diretamente com máscaras numpy:

from vulnflow.helpers import explore
ds = explore.load()
mask = (ds.get_column("cvss_attack_vector") == "NETWORK") & (ds.get_column("is_kev") == True)
print(len(ds.get_column("cve_id")[mask]))

Para trabalho estilo pandas, use ds.to_pandas() (apenas registros analisados e modificados por padrão; veja receitas).

Inspeção de Colunas Aninhadas

Para colunas JSON aninhadas, use describe_nested:

# Principais tipos CWE
print(explore.describe_nested(ds, "cwes", "id", n=15, fmt="json"))

# Principais fornecedores afetados
print(explore.describe_nested(ds, "cpes", "vendor", n=15, fmt="json"))

# Fontes de pontuação CVSS
print(explore.describe_nested(ds, "cvss_scores", "source", fmt="json"))

# Principais produtos afetados
print(explore.describe_nested(ds, "cpes", "product", n=15, fmt="json"))

Etapa 3: Interpretar e Responder

Leia a saída JSON e sintetize uma resposta de nível analítico:

  • Confie em zero, mas verifique. Se uma chamada filtrada retornar total: 0 onde claramente deveriam existir dados, verifique os valores brutos das colunas (veja Ressalvas sobre Colunas) antes de relatar "nenhum".

  • Declare descobertas, não despeje dados. "CWE-79 (XSS) é a fraqueza mais comum, com 15% de todos os CVEs" não "aqui está a saída bruta."

  • Combine sinais. Severidade CVSS + probabilidade EPSS + status KEV = quadro de risco.

  • Faça ressalvas para dados parciais. Se o ano atual estiver incompleto, diga isso. Se muitos registros estiverem awaiting, mencione que eles carecem de dados CVSS e CPE.

  • Forneça contexto. Esta tendência está subindo ou caindo? Como se compara às linhas de base?

  • Use vocabulário de domínio. Exploração, remediação, ameaça, janela de exposição, atraso de patch.

Renderização de Tabelas no Terminal

A saída é renderizada em um terminal monoespaçado. Escolha o formato da tabela com base no número de linhas e na forma dos dados:

≤10 linhas — tabela de coluna única, cada linha visível de relance:

| Ano | Crítico |
|------|----------|
| 2020 | 2.806    |
| 2021 | 2.751    |

11–25 linhas — tabela lado a lado de duas colunas para evitar rolagem excessiva:

| Ano | Crítico | Ano | Crítico |
|------|----------|------|----------|
| 2002 | 12       | 2014 | 317      |
| 2003 | 9        | 2015 | 591      |

>25 linhas — não renderize todas as linhas. Resuma em grupos significativos ou destaque os pontos de dados notáveis:

  • Agrupe por era/década: "2002–2012 teve média de 25/ano, 2017–2024 teve média de 3.400/ano"
  • Mostre apenas os N primeiros/últimos com uma nota sobre linhas omitidas
  • Use uma narrativa condensada com pontos de inflexão principais destacados

Regras gerais:

  • Use separadores de milhares (2.806 não 2806) para valores ≥1.000
  • Alinhe colunas numéricas à direita quando possível
  • Marque anos parciais com * e adicione uma nota de rodapé
  • Prefira resumos narrativos a tabelas longas — uma tabela de 28 linhas é mais difícil de ler do que três frases identificando a tendência e os pontos de inflexão principais

Regras Críticas

  1. Sempre uv run python (nunca python puro)
  2. Sempre verifique data_info() primeiro
  3. Sempre use fmt="json" em chamadas explore — ela retorna uma string JSON pronta para impressão
  4. NUNCA import json — fmt="json" lida com a serialização. Sem json.dumps(), sem json.loads(), sem indent=2. Apenas print(fn(fmt="json")).
  5. Prefira as funções de domínio (risk_priority, exploitation, vendor_risk, weakness_profile, attack_surface) para perguntas de risco antes de escrever código personalizado
  6. Use lookup() para detalhes de um único CVE e search() para listas filtradas
  7. Use describe_nested() para campos aninhados, nunca loops com json.loads
  8. Salve a biblioteca em data/complete_library.parquet — o único caminho que explore lê
  9. Leia references/domain.md quando perguntas envolverem conceitos de domínio
  10. Minimize chamadas de ferramenta — combine múltiplas consultas explore em um único bloco uv run python -c. Use fmt="json" em todas as chamadas explore.
  11. Nunca use wrappers de cmd, soluções alternativas de codificação ou scripts intermediários. Todo código deve chamar funções explore diretamente com fmt="json".
  12. Formate tabelas para legibilidade no terminal — veja Renderização de Tabelas no Terminal acima.

Receitas

Para padrões de código prontos: references/recipes.md.

Habilidades Relacionadas