Plataforma de inteligencia de amenazas autoalojada — agregación de feeds, triaje con IA, cobertura de MITRE ATT&CK e ingeniería de detección integrada con Sentinel. Se ejecuta de forma independiente o totalmente integrada en Azure.
Una plataforma de threat intelligence autoalojada que agrega feeds RSS de más de 60 proveedores de seguridad, ejecuta triaje con IA, correlaciona los hallazgos con tu inventario de activos de RunZero y expone alertas accionables a través de un panel web en modo oscuro.
Diseñada para ejecutarse de forma independiente con cero dependencia de la nube, o totalmente integrada en un entorno Azure/Entra/Sentinel — elige el nivel que se ajuste a lo que tengas.
| Nivel | Script | Triaje con IA | Autenticación | Almacenamiento | Lo que obtienes |
|---|---|---|---|---|---|
| Basic | scripts/setup-basic.sh | Desactivado | Clave de API local | Postgres local (Docker) | Agregación de feeds, extracción de IOCs, matriz MITRE, paneles — sin IA, sin nube, nada que registrar |
| Basic + API | scripts/setup-basic-api.sh | Anthropic (directo) | Clave de API local | Postgres local (Docker) | Todo lo anterior, más triaje de severidad/TTP/resumen con IA |
| Azure + API | scripts/setup-azure.ps1 | Azure AI Foundry | SSO con Microsoft Entra ID | Tu propio Postgres (Azure DB for PostgreSQL, etc.) | Despliegue completo en Azure Container Apps, SSO con roles por usuario. (La integración del pipeline de detections.ai llegará en una versión futura — ver más abajo.) |
Los tres ejecutan exactamente el mismo código de aplicación — lo único que cambia es qué variables de entorno están definidas. Consulta Variables de entorno para la referencia completa.```bash
./scripts/setup-basic.sh
./scripts/setup-basic-api.sh
./scripts/setup-azure.ps1
Los dos scripts de bash levantan un contenedor local de Postgres, aplican el esquema y generan `backend/.env` / `frontend/.env.local` por ti — luego imprimen los dos comandos para iniciar realmente la aplicación (`pip install` + ejecutar backend, `npm install` + ejecutar el servidor de desarrollo del frontend). `setup-azure.ps1` es un envoltorio fino alrededor de `infra/provision.ps1`, el runbook real de despliegue de Azure Container Apps.
---
## Características
- **Agregación de feeds** — sondea más de 60 feeds RSS de seguridad de Nivel 1/2/3 según una programación; deduplica y filtra contenido promocional automáticamente
- **Triaje con IA** — clasifica cada entrada con severidad (Crítica/Alta/Media/Baja/Informativa), TTPs de MITRE ATT&CK y un resumen en lenguaje sencillo. Modular por proveedor: API directa de Anthropic o Azure AI Foundry, conmutables mediante una variable de entorno sin perder funcionalidad en ningún caso
- **Extracción de IOC** — extrae automáticamente IPs, dominios, URLs, hashes de archivos y CVEs de cada entrada
- **Integración con RunZero** — sincroniza tu inventario de activos y correlaciona inteligencia de amenazas contra activos en vivo; coincide en CVEs, nombres de software, versiones de SO y direcciones IP. Tres subpestañas bajo `RUNZERO`: **Matches** (entradas correlacionadas contra tu inventario, filtrables por severidad/fecha/confianza/KEV), **Exposure** (postura confirmada/posible a nivel de organización con seguimiento de remediación) y **Metrics** (tendencias de ingesta vs. remediación a lo largo del tiempo)
- **Your Stack** — define el software/SO de tu entorno; vuelve a puntuar todas las entradas por relevancia
- **Libro mayor de IOC** — libro mayor buscable de todos los indicadores extraídos con referencias cruzadas de entradas y exportación STIX/CSV
- **Matriz MITRE ATT&CK** — mapa de calor de la cobertura de TTPs a través de la inteligencia de amenazas ingerida
- **Panel de estado de feeds** — estado de sondeo por feed, seguimiento de fallos consecutivos y volumen de artículos de 7 días
- **Detections** — una superficie de revisión de 9 pestañas (ver abajo) que cubre todo lo registrado como detección, ya sea generado por IA, importado desde tus propios archivos o sincronizado desde un espacio de trabajo de Sentinel en vivo
- **Autenticación modular** — SSO de Microsoft Entra ID con acceso basado en roles, o una única clave de API local compartida con cero dependencia de Azure. Detectada automáticamente por el frontend; ver [Modos de autenticación](#auth-modes)
### Dos características relacionadas con detecciones
Este repositorio en realidad incluye dos cosas relacionadas pero utilizables de forma independiente bajo el paraguas de "detections":
1. **La pestaña `DETECTIONS`** — una superficie de revisión autocontenida, dividida en nueve subpestañas:
- **All Detections** — el catálogo completo de analíticas registradas, filtrable por técnica/disposición/estado de revisión, cada una expandible a su descripción y KQL completo.
- **Defender Custom Detections** — el mismo catálogo, limitado a detecciones destinadas a las reglas de detección personalizadas de Microsoft Defender for Endpoint en lugar de a las reglas analíticas de Sentinel.
- **Alignment Reviews** — cada vez que una analítica de detección se registra contra una técnica de MITRE, una comprobación de IA compara su cobertura real con la propia descripción de MITRE de esa técnica. Cuando diverge o solo cubre parcialmente la técnica, aterriza aquí como un elemento de revisión humana con el razonamiento de la IA, una corrección de KQL sugerida y el propio resultado de validación de esa corrección (puerta estática + backtest) — nunca una sugerencia ciega.
- **Disposition Alerts** — una cola de detección de podredumbre: una analítica aprobada cuya telemetría se degrada o cuya regla subyacente empieza a dar errores se marca aquí para re-revisión, nombrada por su propia detección en lugar de solo por la técnica MITRE compartida.
- **Generated Hunts** — las detecciones se agrupan en hunts (uno por archivo importado hoy; uno por artículo de TI/proyecto de detections.ai de origen una vez que se lance esa integración), coincidiendo con la propia característica Hunts de Microsoft Sentinel. Un hunt puede sincronizarse en un espacio de trabajo real de Sentinel como un objeto `Microsoft.SecurityInsights/hunts` más sus consultas de búsqueda guardadas constituyentes (restringido por `SENTINEL_HUNTING_SYNC_ENABLED` y un `mode` — off/manual/auto — configurable por equipo en Settings > API Settings; nunca un auto-push silencioso a menos que lo habilites).
- **Sentinel Hunts** — el inventario en vivo de lo que realmente está desplegado en la característica Hunting de tu espacio de trabajo de Sentinel, extraído directamente de ARM en lugar del propio historial de sincronización de esta aplicación; incluye sugerencias de prueba/ajuste por consulta que puedes aplicar o descartar en el lugar.
- **Sentinel Analytics Rules** — la misma idea para las Analytics Rules de Microsoft Sentinel (`Microsoft.SecurityInsights/alertRules`) — un tipo de recurso de Sentinel distinto de Hunting, ya que estas son las que realmente disparan incidentes/alertas según una programación — con el mismo flujo de trabajo de aplicar/descartar sugerencias de ajuste.
- **Local Detections** — ver [Ejecución sin Sentinel ni un proveedor de IA](#running-without-sentinel-or-an-ai-provider-local-detections-import) abajo.
- **Audit Log** (solo administradores) — un registro entre pipelines de cada comprobación que esta aplicación ha ejecutado realmente: resultados de puerta estática/sonda de control de detecciones generadas por IA, intentos de sincronización de hunts de Sentinel y ejecuciones de prueba de consultas de hunt/reglas analíticas de Sentinel, combinados en una única lista paginada y filtrable — cubriendo deliberadamente lo que ninguna pestaña de revisión individual hace por sí sola.
Se ejecuta completamente dentro del backend principal, sin necesidad de despliegue adicional para la propia superficie de revisión. Su propio diseño de API sigue deliberadamente las convenciones de detections.ai a continuación, aunque es totalmente autocontenido.
2. **Orquestador de pipeline de detections.ai — próximamente.** detections.ai tiene una API pública en desarrollo para la generación de detecciones asistida por IA, y este repositorio tiene una integración real construida para ella (`backend/detection_pipeline/orchestrator.py`) que toma inteligencia de amenazas triada, la compara con la cobertura de detección existente y genera KQL en borrador para tu espacio de trabajo de Sentinel como un trabajo programado. Esta integración dará soporte a esa API una vez que esté disponible, y aún no forma parte de esta versión pública. Mientras tanto, **no la necesitas en absoluto para usar la pestaña Detections** — [Local Detections Import](#running-without-sentinel-or-an-ai-provider-local-detections-import) abajo cubre el mismo objetivo de "obtener detecciones reales en esta aplicación" para configuraciones sin generación por IA y sin Sentinel hoy.
### Ejecución sin Sentinel ni un proveedor de IA: Local Detections Import
Dado el nombre y el discurso principal de la aplicación, la pregunta más común de un auto-hospedador en el nivel **Basic** probablemente será *"No tengo Sentinel ni un proveedor de IA configurado — ¿aún puedo obtener algo de las pestañas Detections/Hunts?"* La respuesta es sí: apunta la aplicación a una carpeta de tus propios archivos de reglas de detección (escritos a mano, exportados de un tenant real de Sentinel/Defender, o extraídos de un repositorio público de reglas Sigma/Sentinel) y los catalogará, etiquetará con MITRE y validará estáticamente — sin conexión a Sentinel y sin necesidad de `DETECTIONS_AI_API_KEY`/clave de Anthropic para nada de ello.
- **Formatos soportados, desde el primer día:** archivos sin procesar `.kql`/`.txt`/`.yar`/`.spl` o de cualquier extensión, cada uno opcionalmente emparejado con un sidecar `.json`/`.yaml` (`{"file": "myrule.kql", "title": "...", "description": "...", "technique_id": "T1059.001"}`) para metadatos que la propia exportación de Microsoft no necesita declarar por separado; YARA; Suricata; Sigma YAML (de uno o varios documentos); Splunk SPL; y el propio JSON nativo exportado de Analytics Rule/Hunting Query de Microsoft (solo las reglas de tipo `Scheduled` llevan una consulta KQL sin procesar que esta aplicación puede evaluar — cualquier otro tipo se reconoce y se reporta, no se omite silenciosamente).
- **Qué se ejecuta realmente sobre un archivo importado:** validación estática (el mismo motor de durabilidad/hallazgos que usa la ruta de generación por IA) para contenido KQL; también una comprobación de alineación con MITRE, si *sí* tienes un proveedor de IA configurado (un eje independiente de Sentinel — puedes tener uno, ambos o ninguno); todo lo dependiente de Sentinel (backtesting, sondas de telemetría, seguimiento de disposición) queda fuera de alcance y se muestra como "no Sentinel connection configured" en lugar de una celda en blanco engañosa.
- **Dónde aparece:** el contenido importado se convierte en una fila normal de hunt/detección — mismas tablas, mismo flujo de trabajo de revisión, misma visualización de técnica MITRE que cualquier cosa que genere el pipeline de IA — por lo que también aparece en las vistas regulares `ALL DETECTIONS`/`GENERATED HUNTS`, no solo en su propia pestaña. La subpestaña dedicada **Local Detections** (bajo `DETECTIONS`, solo administradores para activar una importación) es donde la apuntas a una carpeta y observas el progreso/resultados por archivo.
- **Configuración:** establece `LOCAL_IMPORT_DIR` a una ruta absoluta en el sistema de archivos del backend (un volumen montado, en un despliegue en contenedor) — todo lo importado debe residir bajo esa raíz; la interfaz de usuario te permite elegir una subruta debajo de ella, nunca una ubicación arbitraria del sistema de archivos. Ver [Variables de entorno](#environment-variables).
- **Pruébalo de inmediato:** `examples/local-detections-samples/` incluye una pequeña carpeta lista para importar — dos reglas KQL válidas (una emparejada con un sidecar `.json` para mostrar ese mecanismo), una regla deliberadamente inválida (para ver el banner de marcado como inválido) y un archivo no reconocido (para ver el banner de importación fallida). Apunta `LOCAL_IMPORT_DIR` a ella para ver los tres estados de resultado en tu primera importación, sin necesidad de escribir reglas.
**Local Detections** — una ejecución de importación completada: el banner de resumen señala los archivos que fueron catalogados pero marcados como inválidos por el análisis estático (aquí, una regla que alerta sobre un único hash codificado) junto a los que se importaron limpiamente, y cada archivo se convierte en una fila normal de hunt/detección a continuación

---
## Capturas de pantalla
Todas las capturas de pantalla a continuación usan datos sintéticos (nombres de organizaciones falsos, IPs de ejemplo RFC 5737, dominios `.example`) generados para documentación — sin inteligencia de amenazas real ni datos de clientes.
**Feed** — explora y filtra entradas de inteligencia de amenazas triadas con severidad, etiquetas, IOCs y TTPs

<br>
**Dashboard** — desglose de severidad de un vistazo y principales técnicas MITRE ATT&CK

<br>
**MITRE ATT&CK** — mapa de calor de matriz completa de cobertura de técnicas a través de la inteligencia ingerida

<br>
**Your Stack** — define tu entorno; las entradas del feed se vuelven a puntuar por relevancia

<br>
**IOCs** — libro mayor buscable de todos los indicadores extraídos con exportación STIX/CSV

<br>
**Integrations** — resumen de conectores para Sentinel, Defender y RunZero: estado configurado/habilitado y atajos a la propia pestaña de cada uno

<br>
**RunZero** — correlación de activos, seguimiento de exposición a nivel de organización y métricas de remediación, todo proveniente de tu inventario de RunZero

<br>
**Exposure** — organizaciones clasificadas por número de coincidencias de amenazas; haz clic en cualquier tarjeta para ver las entradas coincidentes

<br>
**Detections** — el catálogo completo de analíticas registradas (tanto generadas por IA como importadas localmente), cada una con su estado de puerta estática/backtest/revisión y técnica MITRE

<br>
**Settings** — controles de triaje con IA, monitoreo de estado de feeds, puntuaciones de confianza de fuentes y gestión de usuarios

---
## Arquitectura```
┌─────────────────────────────────────────┐
│ Next.js 16 frontend (port 3000) │
│ Tailwind CSS · dark theme │
└──────────────┬──────────────────────────┘
│ REST API (Bearer token)
┌──────────────▼──────────────────────────┐
│ FastAPI backend (port 8000) │
│ APScheduler · slowapi rate limiting │
└──┬──────────┬──────────┬────────────┬───┘
│ │ │ │
Postgres AI provider RunZero API detections.ai
(modular: (asset sync) (coming soon --
Anthropic or see Features below)
Azure AI Foundry)
Backend (backend/) — Python 3.12 + FastAPI. Postgres para todo el almacenamiento (SQLite y Azure Blob Storage han sido completamente retirados). El proveedor de IA y el método de autenticación se seleccionan mediante variables de entorno, no están codificados de forma fija — ver más abajo.
Frontend (frontend/) — Next.js 16, JavaScript plano, Tailwind CSS. Detecta automáticamente el modo de autenticación desde el backend al cargar.
Infra (infra/) — Plantillas de Azure Bicep para Container Apps, Key Vault y Container Registry (apps.bicep + platform.bicep + app-stack.bicep, desplegadas mediante provision.ps1). Solo es relevante para el nivel de Azure + API.
AZURE_AD_TENANT_ID establecido → Modo Entra: SSO de Microsoft Entra ID, roles por usuario (el primer inicio de sesión se convierte en administrador, todos los demás por defecto son visualizadores).
AZURE_AD_TENANT_ID no establecido → Modo local: una única LOCAL_API_KEY compartida otorga acceso de administrador a cualquiera que la tenga. Sin gestión de usuarios, sin dependencia de Azure. El frontend llama a GET /api/auth/mode al cargar y renderiza automáticamente la pantalla de inicio de sesión correspondiente — nada que configurar en el lado del frontend.
Ambos modos emiten el mismo tipo de JWT firmado por la aplicación después, por lo que todas las demás rutas (require_auth/require_admin) funcionan de forma idéntica independientemente de qué modo haya emitido el token.
Ejecuta scripts/setup-basic.sh o scripts/setup-basic-api.sh (ver Niveles de despliegue) — se encargan de Postgres y de la generación del .env por ti. Luego:```bash
cd backend && pip install -r requirements.txt && uvicorn main:app --reload --port 8000
cd frontend && npm install && npm run dev
### Configuración manual```bash
cd backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp env.example .env # fill in required values — see Environment Variables below
uvicorn main:app --reload --port 8000
get_network_connectionsRecupera las conexiones de red activas del sistema.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
protocol | str | No | None | Filtrar por protocolo (tcp, udp, tcp6, udp6) |
state | str | No | None | Filtrar por estado de la conexión (LISTEN, ESTABLISHED, etc.) |
process_name | str | No | None | Filtrar por nombre del proceso |
pid | int | No | None | Filtrar por ID de proceso |
local_port | int | No | None | Filtrar por puerto local |
remote_port | int | No | None | Filtrar por puerto remoto |
local_ip | str | No | None | Filtrar por dirección IP local |
remote_ip | str | No | None | Filtrar por dirección IP remota |
limit | int | No | 100 | Número máximo de conexiones a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones y metadatos
Ejemplo:
# Obtener todas las conexiones TCP en escucha
result = mcp.get_network_connections(
protocol="tcp",
state="LISTEN"
)
# Obtener conexiones de un proceso específico
result = mcp.get_network_connections(
process_name="nginx",
limit=50
)
# Obtener conexiones a un puerto remoto específico
result = mcp.get_network_connections(
remote_port=443,
protocol="tcp"
)
Respuesta:
{
"connections": [
{
"protocol": "tcp",
"local_address": "0.0.0.0:22",
"remote_address": "0.0.0.0:0",
"status": "LISTEN",
"pid": 1234,
"process_name": "sshd",
"family": "AF_INET"
}
],
"total_count": 1,
"filters_applied": {
"protocol": "tcp",
"state": "LISTEN"
}
}
get_network_interfacesRecupera información sobre las interfaces de red del sistema.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
interface_name | str | No | None | Filtrar por nombre de interfaz específico |
include_stats | bool | No | True | Incluir estadísticas de la interfaz |
include_addresses | bool | No | True | Incluir direcciones IP |
Devuelve: Dict[str, Any] - Diccionario con interfaces y metadatos
Ejemplo:
# Obtener todas las interfaces de red
result = mcp.get_network_interfaces()
# Obtener una interfaz específica sin estadísticas
result = mcp.get_network_interfaces(
interface_name="eth0",
include_stats=False
)
Respuesta:
{
"interfaces": [
{
"name": "eth0",
"status": "up",
"mtu": 1500,
"speed": 1000,
"addresses": [
{
"family": "AF_INET",
"address": "192.168.1.100",
"netmask": "255.255.255.0",
"broadcast": "192.168.1.255"
}
],
"stats": {
"bytes_sent": 1234567,
"bytes_recv": 7654321,
"packets_sent": 12345,
"packets_recv": 54321,
"errors_in": 0,
"errors_out": 0,
"drops_in": 0,
"drops_out": 0
}
}
],
"total_count": 1
}
get_network_statsRecupera estadísticas de red del sistema.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
per_interface | bool | No | False | Devolver estadísticas por interfaz |
include_errors | bool | No | True | Incluir estadísticas de errores |
Devuelve: Dict[str, Any] - Diccionario con estadísticas de red
Ejemplo:
# Obtener estadísticas de red agregadas
result = mcp.get_network_stats()
# Obtener estadísticas por interfaz
result = mcp.get_network_stats(per_interface=True)
Respuesta:
{
"total": {
"bytes_sent": 123456789,
"bytes_recv": 987654321,
"packets_sent": 1234567,
"packets_recv": 7654321,
"errors_in": 0,
"errors_out": 0,
"drops_in": 0,
"drops_out": 0
},
"per_interface": {
"eth0": {
"bytes_sent": 123456789,
"bytes_recv": 987654321,
"packets_sent": 1234567,
"packets_recv": 7654321
}
}
}
get_network_io_countersRecupera contadores de E/S de red del sistema.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
per_nic | bool | No | False | Devolver contadores por NIC |
nic_name | str | No | None | Filtrar por nombre de NIC específico |
Devuelve: Dict[str, Any] - Diccionario con contadores de E/S de red
Ejemplo:
# Obtener contadores de E/S de red agregados
result = mcp.get_network_io_counters()
# Obtener contadores de una NIC específica
result = mcp.get_network_io_counters(
per_nic=True,
nic_name="eth0"
)
Respuesta:
{
"total": {
"bytes_sent": 123456789,
"bytes_recv": 987654321,
"packets_sent": 1234567,
"packets_recv": 7654321,
"errin": 0,
"errout": 0,
"dropin": 0,
"dropout": 0
},
"per_nic": {
"eth0": {
"bytes_sent": 123456789,
"bytes_recv": 987654321,
"packets_sent": 1234567,
"packets_recv": 7654321,
"errin": 0,
"errout": 0,
"dropin": 0,
"dropout": 0
}
}
}
get_network_connections_summaryRecupera un resumen de las conexiones de red del sistema.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
group_by | str | No | "protocol" | Agrupar por (protocol, state, process, port) |
include_listening | bool | No | True | Incluir puertos en escucha |
Devuelve: Dict[str, Any] - Diccionario con resumen de conexiones
Ejemplo:
# Obtener resumen agrupado por protocolo
result = mcp.get_network_connections_summary()
# Obtener resumen agrupado por proceso
result = mcp.get_network_connections_summary(
group_by="process"
)
Respuesta:
{
"summary": {
"tcp": {
"total": 25,
"listening": 5,
"established": 20
},
"udp": {
"total": 10,
"listening": 8,
"established": 2
}
},
"total_connections": 35,
"group_by": "protocol"
}
get_network_connections_by_processRecupera conexiones de red agrupadas por proceso.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
process_name | str | No | None | Filtrar por nombre de proceso |
pid | int | No | None | Filtrar por ID de proceso |
include_listening | bool | No | True | Incluir puertos en escucha |
limit | int | No | 100 | Número máximo de procesos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por proceso
Ejemplo:
# Obtener todas las conexiones agrupadas por proceso
result = mcp.get_network_connections_by_process()
# Obtener conexiones de un proceso específico
result = mcp.get_network_connections_by_process(
process_name="nginx"
)
Respuesta:
{
"processes": [
{
"pid": 1234,
"process_name": "nginx",
"connections": [
{
"protocol": "tcp",
"local_address": "0.0.0.0:80",
"remote_address": "0.0.0.0:0",
"status": "LISTEN"
}
],
"connection_count": 1
}
],
"total_processes": 1,
"total_connections": 1
}
get_network_connections_by_portRecupera conexiones de red agrupadas por puerto.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
port | int | No | None | Filtrar por número de puerto específico |
protocol | str | No | None | Filtrar por protocolo |
include_listening | bool | No | True | Incluir puertos en escucha |
limit | int | No | 100 | Número máximo de puertos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por puerto
Ejemplo:
# Obtener todas las conexiones agrupadas por puerto
result = mcp.get_network_connections_by_port()
# Obtener conexiones en un puerto específico
result = mcp.get_network_connections_by_port(
port=443,
protocol="tcp"
)
Respuesta:
{
"ports": [
{
"port": 443,
"protocol": "tcp",
"connections": [
{
"local_address": "0.0.0.0:443",
"remote_address": "0.0.0.0:0",
"status": "LISTEN",
"pid": 1234,
"process_name": "nginx"
}
],
"connection_count": 1
}
],
"total_ports": 1,
"total_connections": 1
}
get_network_connections_by_stateRecupera conexiones de red agrupadas por estado.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
state | str | No | None | Filtrar por estado específico |
protocol | str | No | None | Filtrar por protocolo |
limit | int | No | 100 | Número máximo de estados a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por estado
Ejemplo:
# Obtener todas las conexiones agrupadas por estado
result = mcp.get_network_connections_by_state()
# Obtener conexiones en estado ESTABLISHED
result = mcp.get_network_connections_by_state(
state="ESTABLISHED"
)
Respuesta:
{
"states": [
{
"state": "ESTABLISHED",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_states": 1,
"total_connections": 1
}
get_network_connections_by_addressRecupera conexiones de red agrupadas por dirección.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
address | str | No | None | Filtrar por dirección IP específica |
address_type | str | No | "local" | Tipo de dirección (local, remote) |
limit | int | No | 100 | Número máximo de direcciones a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por dirección
Ejemplo:
# Obtener todas las conexiones agrupadas por dirección local
result = mcp.get_network_connections_by_address()
# Obtener conexiones a una dirección remota específica
result = mcp.get_network_connections_by_address(
address="93.184.216.34",
address_type="remote"
)
Respuesta:
{
"addresses": [
{
"address": "93.184.216.34",
"address_type": "remote",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_addresses": 1,
"total_connections": 1
}
get_network_connections_by_familyRecupera conexiones de red agrupadas por familia de direcciones.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
family | str | No | None | Filtrar por familia (AF_INET, AF_INET6) |
limit | int | No | 100 | Número máximo de familias a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por familia
Ejemplo:
# Obtener todas las conexiones agrupadas por familia
result = mcp.get_network_connections_by_family()
# Obtener conexiones IPv6
result = mcp.get_network_connections_by_family(
family="AF_INET6"
)
Respuesta:
{
"families": [
{
"family": "AF_INET",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_families": 1,
"total_connections": 1
}
get_network_connections_by_typeRecupera conexiones de red agrupadas por tipo.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
connection_type | str | No | None | Filtrar por tipo (stream, dgram, raw) |
limit | int | No | 100 | Número máximo de tipos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por tipo
Ejemplo:
# Obtener todas las conexiones agrupadas por tipo
result = mcp.get_network_connections_by_type()
# Obtener conexiones de tipo stream
result = mcp.get_network_connections_by_type(
connection_type="stream"
)
Respuesta:
{
"types": [
{
"type": "stream",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_types": 1,
"total_connections": 1
}
get_network_connections_by_statusRecupera conexiones de red agrupadas por estado.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
status | str | No | None | Filtrar por estado específico |
limit | int | No | 100 | Número máximo de estados a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por estado
Ejemplo:
# Obtener todas las conexiones agrupadas por estado
result = mcp.get_network_connections_by_status()
# Obtener conexiones en estado TIME_WAIT
result = mcp.get_network_connections_by_status(
status="TIME_WAIT"
)
Respuesta:
{
"statuses": [
{
"status": "TIME_WAIT",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_statuses": 1,
"total_connections": 1
}
get_network_connections_by_protocolRecupera conexiones de red agrupadas por protocolo.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
protocol | str | No | None | Filtrar por protocolo específico |
limit | int | No | 100 | Número máximo de protocolos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por protocolo
Ejemplo:
# Obtener todas las conexiones agrupadas por protocolo
result = mcp.get_network_connections_by_protocol()
# Obtener conexiones TCP
result = mcp.get_network_connections_by_protocol(
protocol="tcp"
)
Respuesta:
{
"protocols": [
{
"protocol": "tcp",
"connections": [
{
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_protocols": 1,
"total_connections": 1
}
get_network_connections_by_local_addressRecupera conexiones de red agrupadas por dirección local.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
local_address | str | No | None | Filtrar por dirección local específica |
limit | int | No | 100 | Número máximo de direcciones a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por dirección local
Ejemplo:
# Obtener todas las conexiones agrupadas por dirección local
result = mcp.get_network_connections_by_local_address()
# Obtener conexiones desde una dirección local específica
result = mcp.get_network_connections_by_local_address(
local_address="192.168.1.100"
)
Respuesta:
{
"local_addresses": [
{
"local_address": "192.168.1.100",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_addresses": 1,
"total_connections": 1
}
get_network_connections_by_remote_addressRecupera conexiones de red agrupadas por dirección remota.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
remote_address | str | No | None | Filtrar por dirección remota específica |
limit | int | No | 100 | Número máximo de direcciones a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por dirección remota
Ejemplo:
# Obtener todas las conexiones agrupadas por dirección remota
result = mcp.get_network_connections_by_remote_address()
# Obtener conexiones hacia una dirección remota específica
result = mcp.get_network_connections_by_remote_address(
remote_address="93.184.216.34"
)
Respuesta:
{
"remote_addresses": [
{
"remote_address": "93.184.216.34",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_addresses": 1,
"total_connections": 1
}
get_network_connections_by_local_portRecupera conexiones de red agrupadas por puerto local.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
local_port | int | No | None | Filtrar por puerto local específico |
limit | int | No | 100 | Número máximo de puertos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por puerto local
Ejemplo:
# Obtener todas las conexiones agrupadas por puerto local
result = mcp.get_network_connections_by_local_port()
# Obtener conexiones desde un puerto local específico
result = mcp.get_network_connections_by_local_port(
local_port=54321
)
Respuesta:
{
"local_ports": [
{
"local_port": 54321,
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_ports": 1,
"total_connections": 1
}
get_network_connections_by_remote_portRecupera conexiones de red agrupadas por puerto remoto.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
remote_port | int | No | None | Filtrar por puerto remoto específico |
limit | int | No | 100 | Número máximo de puertos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por puerto remoto
Ejemplo:
# Obtener todas las conexiones agrupadas por puerto remoto
result = mcp.get_network_connections_by_remote_port()
# Obtener conexiones hacia un puerto remoto específico
result = mcp.get_network_connections_by_remote_port(
remote_port=443
)
Respuesta:
{
"remote_ports": [
{
"remote_port": 443,
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED",
"pid": 5678,
"process_name": "firefox"
}
],
"connection_count": 1
}
],
"total_ports": 1,
"total_connections": 1
}
get_network_connections_by_pidRecupera conexiones de red agrupadas por ID de proceso.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
pid | int | No | None | Filtrar por ID de proceso específico |
limit | int | No | 100 | Número máximo de PIDs a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por PID
Ejemplo:
# Obtener todas las conexiones agrupadas por PID
result = mcp.get_network_connections_by_pid()
# Obtener conexiones de un PID específico
result = mcp.get_network_connections_by_pid(
pid=5678
)
Respuesta:
{
"pids": [
{
"pid": 5678,
"process_name": "firefox",
"connections": [
{
"protocol": "tcp",
"local_address": "192.168.1.100:54321",
"remote_address": "93.184.216.34:443",
"status": "ESTABLISHED"
}
],
"connection_count": 1
}
],
"total_pids": 1,
"total_connections": 1
}
get_network_connections_by_process_nameRecupera conexiones de red agrupadas por nombre de proceso.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
process_name | str | No | None | Filtrar por nombre de proceso específico |
limit | int | No | 100 | Número máximo de nombres de proceso a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por nombre de proceso
Ejemplo:
# Obtener todas las conexiones agrupadas por nombre de proceso
result = mcp.get_network_connections_by_process_name()
# Obtener conexiones de un proceso específico
result = mcp.get_network_connections_by_process_name(
process_name="nginx"
)
Respuesta:
{
"process_names": [
{
"process_name": "nginx",
"connections": [
{
"protocol": "tcp",
"local_address": "0.0.0.0:80",
"remote_address": "0.0.0.0:0",
"status": "LISTEN",
"pid": 1234
}
],
"connection_count": 1
}
],
"total_process_names": 1,
"total_connections": 1
}
get_network_connections_by_commandRecupera conexiones de red agrupadas por línea de comandos.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
command | str | No | None | Filtrar por línea de comandos específica |
limit | int | No | 100 | Número máximo de comandos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por comando
Ejemplo:
# Obtener todas las conexiones agrupadas por comando
result = mcp.get_network_connections_by_command()
# Obtener conexiones de un comando específico
result = mcp.get_network_connections_by_command(
command="nginx: worker process"
)
Respuesta:
{
"commands": [
{
"command": "nginx: worker process",
"connections": [
{
"protocol": "tcp",
"local_address": "0.0.0.0:80",
"remote_address": "0.0.0.0:0",
"status": "LISTEN",
"pid": 1234
}
],
"connection_count": 1
}
],
"total_commands": 1,
"total_connections": 1
}
get_network_connections_by_userRecupera conexiones de red agrupadas por usuario.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
username | str | No | None | Filtrar por nombre de usuario específico |
limit | int | No | 100 | Número máximo de usuarios a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por usuario
Ejemplo:
# Obtener todas las conexiones agrupadas por usuario
result = mcp.get_network_connections_by_user()
# Obtener conexiones de un usuario específico
result = mcp.get_network_connections_by_user(
username="www-data"
)
Respuesta:
{
"users": [
{
"username": "www-data",
"connections": [
{
"protocol": "tcp",
"local_address": "0.0.0.0:80",
"remote_address": "0.0.0.0:0",
"status": "LISTEN",
"pid": 1234,
"process_name": "nginx"
}
],
"connection_count": 1
}
],
"total_users": 1,
"total_connections": 1
}
get_network_connections_by_groupRecupera conexiones de red agrupadas por grupo.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
groupname | str | No | None | Filtrar por nombre de grupo específico |
limit | int | No | 100 | Número máximo de grupos a devolver |
Devuelve: Dict[str, Any] - Diccionario con conexiones agrupadas por grupo
Ejemplo:
# Obtener todas las conexiones agrupadas por grupo
result = mcp.get_network_connections_by_group()
# Obtener conexiones de un grupo específico
result = mcp.get_network_connections_by_group(
groupname="www-data"
)
Respuesta:
{
"groups": [
{
"groupname": "www-data",
"connections": [
{
"protocol": "tcp",
"local_address": "0.0.0.0:80",
"remote_address": "0.0.0.0:0",
"status": "LISTEN",
"pid": 1234,
"process_name": "nginx"
}
],
"connection_count": 1
}
],
"total_groups": 1,
"total_connections": 1
}
get_network_connections_by_serviceRecupera conexiones de red agrupadas por servicio.
Parámetros:
| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|---|
| `service_name````bash | ||||
| cd frontend | ||||
| npm install | ||||
| cp env.local.example .env.local # set NEXT_PUBLIC_API_URL=http://localhost:8000 | ||||
| npm run dev |
### Docker Compose (ambos servicios)```bash
cp backend/env.example backend/.env # fill in required values
docker compose up --build
Frontend → http://localhost:3000 Backend API docs → http://localhost:8000/docs
Copia backend/env.example a backend/.env y complétalo. Agrupadas según qué tier las necesita:
Siempre requeridas:
| Variable | Descripción |
|---|---|
PG_DSN | Cadena de conexión de Postgres |
JWT_SECRET_KEY | Secreto para firmar los tokens de sesión de la app (python -c "import secrets; print(secrets.token_hex(32))") |
Auth — elige un modo:
| Variable | Descripción |
|---|---|
LOCAL_API_KEY | Modo local: clave compartida que otorga acceso de administrador. Deja AZURE_AD_TENANT_ID sin definir para activar este modo |
AZURE_AD_TENANT_ID | Modo Entra: ID de tenant para SSO. Definir esto activa el modo Entra |
AZURE_AD_CLIENT_ID | Modo Entra: client ID del registro de aplicación |
AZURE_AD_CLIENT_SECRET | Modo Entra: secreto del registro de aplicación (solo frontend) |
NEXTAUTH_SECRET | Modo Entra: secreto de cifrado de sesión de NextAuth (solo frontend) |
Triaje con IA — opcional, elige un proveedor (omite ambos para ejecutar con el triaje deshabilitado):
| Variable | Descripción |
|---|---|
AI_PROVIDER | anthropic (por defecto) o azure |
ANTHROPIC_API_KEY | Clave de API directa de Anthropic |
AZURE_FOUNDRY_ENDPOINT | Endpoint de Azure AI Foundry, p. ej. https://<resource>.services.ai.azure.com/anthropic |
AZURE_FOUNDRY_API_KEY | Clave de API de Azure AI Foundry |
AZURE_FOUNDRY_DEPLOYMENT | Nombre del deployment de Foundry (por defecto claude-haiku-4-5) |
AZURE_FOUNDRY_API_VERSION | Versión de la API de Foundry (por defecto 2025-05-01) |
Opcionales:
| Variable | Descripción |
|---|---|
RUNZERO_API_TOKEN | Habilita la sincronización y correlación de activos de RunZero |
ALLOWED_ORIGINS | Lista de permitidos CORS separada por comas (por defecto http://localhost:3000) |
ENABLE_SCHEDULER | Establece false para deshabilitar el poller de feeds en segundo plano (por defecto true) |
ARCHIVE_AFTER_DAYS | Umbral de archivado automático en días (por defecto 90) |
PG_POOL_MIN / PG_POOL_MAX / PG_POOL_TIMEOUT | Ajuste del pool de conexiones de Postgres (por defecto 1 / 10 / 30) |
LOCAL_IMPORT_DIR | Habilita la Importación de detecciones locales — ruta absoluta en el sistema de archivos del backend a la que queda confinada cada importación. Sin definir, deshabilita la funcionalidad por completo (su pestaña muestra un mensaje de "no configurado") |
Frontend (frontend/.env.local o frontend/env.local.example):
| Variable | Descripción |
|---|---|
NEXT_PUBLIC_API_URL | URL del backend tal como la ve el navegador. Se incorpora al bundle de JS en tiempo de compilación. Déjala sin definir para enrutar las llamadas a la API a través del proxy integrado del mismo origen (frontend/pages/api/[...proxy].js) — necesario siempre que el backend no tenga ingress público (p. ej. el Container App de solo uso interno del tier Azure + API) |
BACKEND_URL | URL del backend tal como la ve el propio servidor de Next.js. La usa el intercambio de login de NextAuth y, cuando NEXT_PUBLIC_API_URL no está definida, el proxy del mismo origen que reenvía del lado del servidor cada petición del navegador a /api/* |
Orquestador de detections.ai — próximamente (aún no forma parte de esta versión pública; documentado aquí para cuando se publique. Tier Azure + API, desplegable por separado — consulta backend/detection_pipeline/orchestrator.py):
| Variable | Descripción |
|---|---|
DETECTIONS_AI_API_KEY | Requerida para ejecutar el orquestador en absoluto |
SENTINEL_WORKSPACE_ID | Customer ID (GUID) del workspace de Log Analytics, para backtesting. Opcional |
PIPELINE_BATCH_SIZE | Entradas por ejecución (por defecto 5) |
PIPELINE_DRY_RUN | true para reclamar y registrar sin llamar a la API |
PIPELINE_LANGUAGE | Lenguaje de consulta de detecciones (por defecto kql) |
Sincronización de Sentinel Hunts (opcional, desactivada por defecto — consulta Settings > API Settings para el modo on/off/manual/auto):
| Variable | Descripción |
|---|---|
SENTINEL_HUNTING_SYNC_ENABLED | true para permitir cualquier intento de sincronización de hunts en absoluto. Sin definir/false es un no-op puro — cero llamadas a ARM |
AZURE_SUBSCRIPTION_ID | Suscripción que contiene el workspace de Sentinel |
AZURE_RESOURCE_GROUP | Grupo de recursos que contiene el workspace de Sentinel |
SENTINEL_WORKSPACE_NAME | El nombre del workspace, no su customer ID — un valor distinto del SENTINEL_WORKSPACE_ID de arriba, que en su lugar usa el cliente del plano de datos de backtesting |
El IaC real y actual es infra/apps.bicep + infra/platform.bicep + infra/app-stack.bicep, desplegado mediante infra/provision.ps1 (o el wrapper ligero scripts/setup-azure.ps1). Aprovisiona Container Apps, secretos respaldados por Key Vault e identidades administradas — Postgres en sí no lo aprovisiona este repositorio; apunta PG_DSN (almacenado como el secreto pg-dsn de Key Vault) a cualquier servidor Postgres accesible.```powershell
./scripts/setup-azure.ps1
cd infra cp migration.psd1.example migration.psd1 # fill in your resource group, apps, etc. ./provision.ps1
`provision.ps1` es idempotente — es seguro volver a ejecutarlo después de editar el manifiesto. Consulta su propio comentario de encabezado para ver el paso a paso completo (plataforma → pila de aplicaciones → secretos → Easy Auth → importación de imágenes → aplicaciones → comprobaciones posteriores).
El orquestador de detections.ai (un Container Apps Job programado controlado por un bloque `Orchestrator` en `migration.psd1` — consulta `migration.psd1.example` para ver la forma, y almacena tu clave como el secreto `DETECTIONSAIAPIKEY` de Key Vault) aún no forma parte de esta versión pública — consulta [Two detections-related features](#two-detections-related-features) más arriba.
---
## Estructura del proyecto```
├── backend/
│ ├── main.py # FastAPI app, all endpoints
│ ├── db.py # Postgres queries
│ ├── pgcompat.py # connection pool + SQLite-style placeholder translation
│ ├── feed_manager.py # RSS polling, AI triage (provider-modular), scheduler
│ ├── enrichment.py # IOC extraction, KEV cache, stack rematch
│ ├── runzero_sync.py # RunZero asset sync and correlation engine
│ ├── dedup.py # CVE deduplication logic
│ ├── auth.py # Entra ID SSO + local API-key auth, app JWT sign/verify
│ ├── ioc_export.py # STIX 2.1 and CSV export
│ ├── stack_presets.py # Pre-built tech stack templates
│ ├── detection_pipeline/ # detections.ai orchestrator, MITRE alignment-check,
│ │ # Sentinel hunts/analytics-rules sync + tuning,
│ │ # audit log, local_import.py (Local Detections Import)
│ └── tests/ # pytest test suite, incl. fixtures/local_import/
├── frontend/
│ ├── pages/
│ │ ├── index.js # Main app shell + tab routing
│ │ └── login.js # Entra ID or local API-key login, auto-detected
│ ├── lib/
│ │ ├── authMode.js # GET /api/auth/mode, cached per page load
│ │ ├── authFetch.js # Bearer auth + 401-retry wrapper
│ │ └── authSession.js # token storage, JWT decode/expiry helpers
│ └── components/
│ ├── layout/ # TopBar, Sidebar, TabBar, TopFilterBar, TimeRangeToggle
│ ├── feed/ # FeedList, FeedCard
│ ├── integrations/ # IntegrationsPanel, ExposurePanel, RunZeroPanel,
│ │ # RunZeroMatchesPanel, RunZeroMetricsPanel
│ ├── detections/ # DetectionsPanel (tab shell) + one component per
│ │ # sub-tab: DetectionsCatalogPanel, AlignmentReviewPanel,
│ │ # DispositionAlertsPanel, HuntsPanel, SentinelHuntsPanel,
│ │ # SentinelAnalyticsRulesPanel, LocalDetectionsPanel,
│ │ # AuditPanel, plus shared TuningSuggestionBadge
│ ├── settings/ # SettingsPanel, CadencePicker, SeverityCards
│ └── mitre/ # MitreMatrix
├── infra/ # Azure Bicep templates + provision.ps1
├── scripts/ # Tiered setup scripts (see Deployment tiers)
└── docker-compose.yml
63 feeds en tres niveles:
Authorization: Bearer <token>secrets.compare_digest) para la clave de autenticación local.env / Azure Key VaultMIT — ver LICENSE.