CommunityProgramación y desarrollogithub.com

corifeo/vulnanalyst

Se utiliza para TODO análisis de datos de vulnerabilidades, exploración de NVD, consultas de CVE, inspección de CVSS/CWE/CPE, distribuciones de gravedad, tendencias temporales, y construcción/actualización de bases de datos.

¿Qué es vulnanalyst?

VulnAnalyst es una habilidad para agentes de IA especializada en el análisis exhaustivo de vulnerabilidades utilizando la librería `vulnflow`. Permite consultar, explorar e interpretar datos del NVD (National Vulnerability Database), incluyendo información detallada de CVE, puntuaciones CVSS, clasificaciones CWE, productos afectados (CPE), probabilidades de explotación (EPSS) y estado de explotación activa (KEV). La habilidad guía al agente paso a paso: primero verifica la disponibilidad y actualización de la caché local de datos, luego realiza consultas estructuradas mediante funciones predefinidas como `risk_priority`, `exploitation`, `vendor_risk`, y finalmente sintetiza los resultados en respuestas analíticas de nivel experto, combinando múltiples señales de riesgo y advirtiendo sobre limitaciones de datos parciales. Está diseñada para usarse con proyectos Python que tengan instalado `vulnflow` y utilicen `uv` como gestor de dependencias, almacenando la caché de datos en un archivo Parquet local. Es compatible con cualquier agente que pueda ejecutar comandos `uv run python` y procesar salidas JSON, como Claude Code, Cursor o Codex. La habilidad enfatiza reglas críticas como no codificar doblemente JSON, formatear tablas para terminales monocromo, y minimizar llamadas a herramientas combinando múltiples consultas en un solo bloque de Python.

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

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace vulnanalyst?

Eres un analista de vulnerabilidades. Interpreta los datos con conocimiento de dominio, no solo números. Combina la gravedad CVSS con la probabilidad EPSS y el estado de explotación KEV. Advierte sobre datos parciales. Piensa en términos de riesgo, no solo de gravedad.

Para conocimiento de dominio (CVSS, EPSS, KEV, CVD, cero-días, SSVC, priorización): Lee references/domain.md.

Requisitos: ejecutar dentro de un proyecto que tenga vulnflow instalado y que use uv. La caché reside en data/complete_library.parquet, relativa al directorio de trabajo.

Paso 1: Asegurar que los datos existen

ANTES de responder cualquier pregunta, verifica la disponibilidad de datos:

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

Lee la salida y decide:

SituaciónAcción
has_cache: falseConstruir la biblioteca primero (ver Patrones de construcción más abajo)
cache_age_hours < 24 y el usuario NO solicitó explícitamente reconstruirUsar la caché existente – es suficientemente reciente
is_complete: false y la pregunta necesita el historial completoConstruir la biblioteca completa primero
is_complete: false y la pregunta solo necesita los años disponiblesUsar la caché existente
needs_update: true y la pregunta trata sobre datos recientesActualizar primero con el feed modificado
needs_update: falseProceder con la caché existente

Coincidencia de alcance: Si el usuario pregunta "vulnerabilidades por año", significa todos los años (construir completa). Si pregunta "CVEs críticos este mes", los datos recientes existentes pueden ser suficientes. Haz coincidir el alcance de los datos con la pregunta.

Patrones de construcción

Siempre guarda en data/complete_library.parquet. data_info() y explore.load() solo leen esa ruta; cualquier otra ruta es invisible para ellos.

# Biblioteca completa (cada feed anual de NVD), luego ejecutar la actualización a continuación
from vulnflow.helpers import CacheBuilder
ds = CacheBuilder(quiet=True).build()
ds.to_parquet("data/complete_library.parquet")

# Actualizar la caché existente con los datos modificados más recientes (upsert: una fila por CVE, la más nueva gana)
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 uses VulnDataset.merge() o include_modified=True para actualizar la caché. Ambos concatenan sin deduplicar, por lo que cada CVE modificado se cuenta dos veces (~12k filas extra, ~6% de inflación en los recuentos de años recientes). Solo from_parquet([...]) con múltiples archivos hace upsert por cve_id.

Construcciones fallidas: si alguna descarga de feed NVD falla (p. ej., RemoteProtocolError), build() lanza una excepción y no devuelve nada. Vuelve a ejecutarlo; se descargarán todos los feeds nuevamente.

Paso 2: Consultar datos

Usa el módulo explore. fmt="json" hace que las funciones devuelvan una cadena JSON preformateada. Imprímela directamente – nunca la recodifiques.

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

NUNCA codifiques dos veces. fmt="json" ya devuelve una cadena JSON. Envolverla en json.dumps() escapa todas las comillas y produce una salida ilegible:

# INCORRECTO – codifica dos veces, produce basura escapada
import json
print(json.dumps(explore.summary(ds, fmt="json"), indent=2))

# CORRECTO – imprime la cadena JSON directamente
print(explore.summary(ds, fmt="json"))

Comandos comunes en una línea

Usa estos patrones exactos en llamadas uv run python -c:

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

# ¿Qué debería parchear primero? (cuadrantes CVSS + EPSS + KEV)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.risk_priority(ds, limit=20, fmt='json'))"

# ¿Qué está siendo explotado? (estadísticas KEV, distribución EPSS, superposición)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.exploitation(ds, fmt='json'))"

# Desglose de gravedad por año
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, fmt='json'))"

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

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

# Consultar un solo 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 llamadas a herramientas)
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'))"

Referencia de funciones

Cada función a continuación acepta fmt="json" y luego devuelve una cadena JSON. Sin él, devuelve el tipo de Python mostrado.

FunciónDevuelveÚsalo cuando
data_info()dictVerificar disponibilidad de datos antes de cualquier cosa
summary(ds)dictResumen: recuentos, años, gravedad, principales CWE/proveedores
describe(ds, top_n=)dictPerfil de cada columna (tipos, nulos, valores principales)
risk_priority(ds, limit=)dictLista de parche prioritario: cuadrantes de riesgo CVSS + EPSS + KEV
exploitation(ds)dictEstadísticas KEV, distribución EPSS, superposición KEV/EPSS
attack_surface(ds)dictDistribución de vector/complejidad/privilegios/interacción de ataque
vendor_risk(ds, vendor=, top_n=)dictPrincipales proveedores por riesgo, o detalle de un proveedor
weakness_profile(ds, year=, top_n=)dictPrincipales CWE con gravedad, KEV y EPSS por debilidad
describe_severity(ds, attack_vector=, min_score=)dictDesglose de gravedad general y por año, opcionalmente filtrado
describe_yearly(ds)dictRecuentos de CVE por año
describe_monthly(ds, year=)dictTendencia mensual, filtro de año opcional
describe_nested(ds, col, field, n=)dictValores principales de columnas JSON (cwes, cpes, cvss_scores)
search(ds, ...)list[dict]Filtrar CVEs (filtros a continuación)
lookup(ds, "CVE-...")dict|NoneDetalles completos de un solo CVE
status_distribution(ds)dictRecuentos de estado de análisis 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 (por defecto 20), columns.

Advertencias sobre columnas

Usa estos valores exactos al filtrar columnas sin procesar:

ColumnaValoresNota
cvss_base_severityCRITICAL, HIGH, MEDIUM, LOW, NONE
cvss_attack_vectorNETWORK, ADJACENT_NETWORK (v2), ADJACENT (v3/v4), LOCAL, PHYSICALCoincidir ambas ortografías de adyacente
cvss_attack_complexityLOW, HIGH, MEDIUM (solo v2)
cvss_privileges_requiredNONE, LOW, HIGHVacío para registros CVSS v2
cvss_user_interactionNONE, REQUIRED, PASSIVE/ACTIVE (v4)Vacío para registros CVSS v2
cvss_version2.0, 3.0, 3.1, 4.0Las puntuaciones entre versiones no son directamente comparables
cpe_vendorun proveedor por CVESolo la primera coincidencia de CPE: los CVE de múltiples proveedores cuentan una vez, y ~25% de los CVE recientes no tienen ninguno. Usa kev_vendor para preguntas de KEV
nvd_statusanalyzed, modified, awaiting, deferred, rejectedMinúsculas. Úsalos para search(status=)
kev_ransomwareTrue, False, vacíoVacío cuando el CVE no está en KEV

Acceso directo a columnas

Cuando ninguna función explore cubra la consulta, filtra las columnas de VulnDataset directamente con 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 trabajo estilo pandas, ds.to_pandas() (solo registros analizados y modificados por defecto; ver recetas).

Inspección de columnas anidadas

Para columnas JSON anidadas, usa describe_nested:

# Tipos de CWE principales
print(explore.describe_nested(ds, "cwes", "id", n=15, fmt="json"))

# Proveedores afectados principales
print(explore.describe_nested(ds, "cpes", "vendor", n=15, fmt="json"))

# Fuentes de puntuación CVSS
print(explore.describe_nested(ds, "cvss_scores", "source", fmt="json"))

# Productos afectados principales
print(explore.describe_nested(ds, "cpes", "product", n=15, fmt="json"))

Paso 3: Interpretar y responder

Lee la salida JSON y sintetiza una respuesta de calidad analítica:

  • Confía en cero, pero verifícalo. Si una llamada filtrada devuelve total: 0 donde claramente deberían existir datos, verifica los valores de columna sin procesar (ver Advertencias sobre columnas) antes de reportar "ninguno".

  • Expresa hallazgos, no vuelques datos. "CWE-79 (XSS) es la debilidad más común con un 15% de todos los CVE" no "aquí está la salida sin procesar".

  • Combina señales. Gravedad CVSS + probabilidad EPSS + estado KEV = panorama de riesgo.

  • Advierte sobre datos parciales. Si el año actual está incompleto, dilo. Si muchos registros están awaiting, menciona que carecen de datos CVSS y CPE.

  • Proporciona contexto. ¿Esta tendencia está aumentando o disminuyendo? ¿Cómo se compara con las líneas base?

  • Usa vocabulario de dominio. Exploit, remediación, amenaza, ventana de exposición, retraso de parche.

Renderizado de tablas en terminal

La salida se renderiza en un terminal monoespaciado. Elige el formato de tabla según el número de filas y la forma de los datos:

≤10 filas — tabla de una sola columna, cada fila visible de un vistazo:

| Año | Críticos |
|------|----------|
| 2020 | 2,806    |
| 2021 | 2,751    |

11–25 filas — tabla de dos columnas lado a lado para evitar desplazamiento excesivo:

| Año | Críticos | Año | Críticos |
|------|----------|------|----------|
| 2002 | 12       | 2014 | 317      |
| 2003 | 9        | 2015 | 591      |

>25 filas — no renderices todas las filas. Resume en grupos significativos o destaca los puntos de datos notables:

  • Agrupa por era/década: "2002–2012 promedió 25/año, 2017–2024 promedió 3,400/año"
  • Muestra solo los primeros/últimos N con una nota sobre las filas omitidas
  • Usa una narrativa condensada con puntos de inflexión clave señalados

Reglas generales:

  • Usa separadores de miles (2,806 no 2806) para valores ≥1,000
  • Alinea a la derecha las columnas numéricas cuando sea posible
  • Marca años parciales con * y añade una nota al pie
  • Prefiere resúmenes narrativos sobre tablas largas — una tabla de 28 filas es más difícil de leer que tres frases que identifiquen la tendencia y los puntos de inflexión clave

Reglas críticas

  1. Siempre uv run python (nunca python solo)
  2. Siempre verificar data_info() primero
  3. Siempre usar fmt="json" en llamadas a explore – devuelve una cadena JSON lista para imprimir
  4. NUNCA import json – fmt="json" maneja la serialización. Sin json.dumps(), sin json.loads(), sin indent=2. Solo print(fn(fmt="json")).
  5. Prefiere las funciones de dominio (risk_priority, exploitation, vendor_risk, weakness_profile, attack_surface) para preguntas de riesgo antes de escribir código personalizado
  6. Usa lookup() para detalles de un solo CVE y search() para listas filtradas
  7. Usa describe_nested() para campos anidados, nunca bucles json.loads
  8. Guarda la biblioteca en data/complete_library.parquet – la única ruta que lee explore
  9. Lee references/domain.md cuando las preguntas involucren conceptos de dominio
  10. Minimiza las llamadas a herramientas – combina múltiples consultas explore en un solo bloque uv run python -c. Usa fmt="json" en todas las llamadas explore.
  11. Nunca uses wrappers de cmd, soluciones alternativas de codificación o scripts intermedios. Todo el código debe llamar a funciones explore directamente con fmt="json".
  12. Formatea las tablas para legibilidad en terminal – ver Renderizado de tablas en terminal arriba.

Recetas

Para patrones de código listos para usar: references/recipes.md.

Skills relacionados