
Servidor complementar de forense DFIR + extensão de captura
Triagem DFIR assistida por IA — na sua máquina. Transforma capturas de tela de investigação e artefatos importados em uma linha do tempo forense, achados, IOCs, um grafo ativo↔IoC e relatórios compartilháveis; faça perguntas ao caso em linguagem natural e colabore com outros investigadores.
Um companheiro de forense digital / resposta a incidentes em localhost. Uma extensão de navegador captura capturas de tela da sua investigação (Velociraptor, painéis EDR/SIEM, Security Onion, Splunk4DFIR, VolWeb, VirusTotal, etc.) como evidência; um servidor local as armazena, executa análise de visão por IA em janelas em um estado de investigação acumulativo por caso, e serve um painel ao vivo mais relatórios exportáveis.
Tudo roda na sua máquina — o companheiro se vincula apenas a 127.0.0.1, a evidência
permanece em disco, e o provedor de IA é você quem escolhe.
Camada de análise pós-detecção. O DFIR Companion NÃO é um motor de detecção — ele ingere veredictos do Velociraptor, Security Onion, Chainsaw, Hayabusa, THOR, Cyber Triage, EDR/SIEM, correlaciona-os em uma única linha do tempo forense, e sintetiza achados, caminho do atacante, IOCs e relatórios. O valor é o "e daí", não re-derivar alertas.
Caso de demonstração: https://dfir-companion-production.up.railway.app/dashboard?caseId=demo
Laboratório prático: https://killercoda.com/dfir-companion/scenario/killercoda
Manual do usuário: https://hasamba.github.io/DFIR-Companion/manual/
companion/.env)Caso de demonstração: GlobalTech Industries — BEC e Precursor de Ransomware, Maio de 2026.
Um caso totalmente pré-preenchido que você pode explorar sem importar nenhuma evidência real — achados, IOCs, técnicas MITRE, tags/comentários de analistas, dados de exposição do cliente e metadados de relatório estão todos pré-carregados para que cada painel do dashboard tenha algo a mostrar.
Carregue-o com um clique — clique no botão Demo case na barra de ferramentas do dashboard. Funciona também com o EXE portátil do Windows (sem necessidade de Node ou
npm). O botão confirma antes de sobrescrever se o caso já existir.Ou faça o seed pela CLI (dev / Docker):
cd companion && npm run seed-demo # cria o id de caso "demo" npm run seed-demo -- --force # sobrescreve um caso de demonstração existente npm run seed-demo -- --case-id globaltech # usa um id personalizadoDepois abra
http://127.0.0.1:4773/dashboarde conecte-se ao caso.
Resumo do caso gerado por IA, narrativa minuto a minuto e relato do caminho do atacante — do acesso inicial à implantação do ransomware.

Eventos analisados com filtros de severidade, tags de triagem, links de detalhe por linha e rastreamento de alterações de importação (banner de novos eventos com diff expansível).

Todos os eventos já importados, antes da filtragem por escopo/severidade — filtre, marque, favorite e promova linhas para a linha do tempo forense analisada; nada é removido, esta é uma visão superconjunto.

Gráfico visual de eventos por ativo (eixo Y) e tempo (eixo X), colorido por severidade — arraste o eixo de tempo para filtrar a linha do tempo forense para um intervalo.

Achados gerados por IA com pontuações de confiança, tags de triagem de analistas e links de técnicas MITRE ATT&CK; rastreia o que mudou desde a execução de síntese anterior.

Eventos agrupados por tática MITRE ATT&CK — uma categorização, não um estágio confirmado de kill-chain, derivada deterministicamente sem IA.

Perguntas padrão de DFIR respondidas automaticamente a partir do caso sintetizado (respondidas / parciais / desconhecidas), cada uma com um ponteiro de evidência ou uma diretiva "colete isto a seguir".

Checklist de remediação acionável derivado automaticamente dos achados e próximos passos recomendados; ressincronizado a cada execução de síntese enquanto preserva o status do analista, responsável e prazos.

Quais hosts/contas carregam o ataque, pontuados por sinal (eventos ponderados por severidade + técnicas + IOCs conectivos) em vez de volume, com uma janela de escopo sugerida.

Árvores de processos, movimento lateral e linhagem de arquivos costurados em um grafo causal de ataque. Derivado deterministicamente de campos populados pelo importador — sem IA, sem custo, roda offline.

Quem fez logon onde — contas e hosts vinculados a partir de eventos de logon da super-linha do tempo, distinguindo logons bem-sucedidos, falhos e de risco (RDP/runas/netonly).

Canais de saída periódicos regulares demais para serem tráfego humano — uma pista de caça, não um veredicto, com intervalo, jitter e contagem de eventos por candidato.

Indicadores (IPs · domínios · hashes · arquivos · processos · contas) enriquecidos com VirusTotal,
AbuseIPDB, ThreatFox e outros provedores — badges de veredicto, pontuações de detecção, destaques de importação
NEW e rótulos de triagem de analistas.

Grafo interativo vinculando hosts e contas vítimas aos indicadores que tocaram cada um, mais uma lista de hosts e usuários comprometidos conhecidos.

drop/ de um caso são importados em segundo plano, movidos para _processed/ ou _failed/, e registrados em drop-log.txt; uma subpasta asset=<HOST> nomeia o host.evtx bruto mantido byte a byte, versão do parser e código de saída na custódia, fail-closed, desativado por padrãoDFIR_DEDUP=off)DFIR_OCR_SEARCH=off para desativar; npm run ocr-index para preencher retroativamente)127.0.0.1 com CORS + Private-Network-Access para a extensão; recusa hostnames não reconhecidos, fechando ataques de DNS-rebinding (DFIR_ALLOWED_HOSTS)Todos os importadores são determinísticos (sem chamada de IA), leem os próprios timestamps do artefato e marcam eventos com o nome real da ferramenta para correlação entre fontes. O mesmo arquivo pode ser reimportado sem duplicar a linha do tempo.
object/action/properties, timestamp_ms em epoch-ms) — eventos de processo/flow/logon/registry/module/file/thread | Evidência Info; incremento por LOLBin/linha de comando codificada (IPs públicos → IOCs) |
| Windows Event Log XML | Event Viewer "Save As XML", wevtutil qe /f:xml, Get-WinEvent … ToXml() (Security, Sysmon, System, qualquer canal) | Tabela por EID do Windows/Sysmon |
| Chainsaw | JSON/JSONL de hunt EVTX (chainsaw hunt --json); executável diretamente em .evtx bruto via o executor de ferramentas | Nível da regra Sigma correspondida |
| Hayabusa | json-timeline ou csv-timeline | Nível da regra Sigma correspondida |
| Velociraptor | Array JSON, JSONL ou mapa de artefatos | Veredito Sigma/YARA ou por EID |
| THOR (Nextron) | Saída de scan JSON-Lines | Nível de alerta do THOR |
| Suricata / Zeek | eve.json, logs JSON do Zeek; telemetria → apenas IOCs | Prioridade do alerta / severidade do aviso |
| Snort / Suricata IDS (fast) | Log de alerta de linha única alert_fast | Priority da regra (1→High / 2→Medium / 3→Low) |
| YARA | Saída de scan CLI yara -s -m (correspondências de regras + strings/meta) | Info→Medium por correspondência; incremento no meta score/threat_level da regra |
| Log de acesso web/proxy | Formato de log combined do Apache/Nginx/Squid (log de acesso de servidor web ou proxy direto); URL da requisição, HTTP Referer e User-Agent capturados (segredos na URL/Referer + UAs de scanner/bot/injeção sobrevivem como eventos + IOCs) | Info por padrão; acesso negado (401/403/407) → Low; clone/push git smart-HTTP → T1213 |
| Syslog de firewall Cisco ASA | Mensagens Built/Teardown/Deny %ASA-#-######: | Info por padrão (telemetria); Deny explícito → Low |
| Syslog (simples) | RFC 5424 (<PRI>1 …) + RFC 3164 (Mmm dd …) logs de host Linux/Unix | Info por padrão (telemetria); falha de autenticação ou PRI crit/alert/emerg → Low |
| Security Onion | Eventos SOC Alerts/Hunt (ECS); enviados pela extensão ou por uma exportação da API do SOC | event.severity_label (rótulo Suricata/SO) |
| SO-CRATES | Alertas Suricata + correspondências de arquivos YARA (/api/events) e detecções Sigma (/api/sigma-alerts); enviados pela extensão ou por uma exportação bruta | Prioridade Suricata / nível Sigma / correspondência YARA |
| Cyber Triage | Linha do tempo JSONL / JSON / CSV | Pontuação de item do Cyber Triage |
| M365 / Entra ID | UAL, logs de entrada e auditoria do Entra | Tabela de tradecraft de BEC / riskLevel do Entra |
| Okta | Exportação do System Log | Tabela de tradecraft de IdP (MFA desabilitado, concessão de admin, token de API emitido, sessão personificada) — não a nota operacional do fornecedor |
| Google Workspace | Auditoria de Admin + login | Tabela de tradecraft de IdP (2SV desabilitado, papel concedido, OAuth consentido, monitor de e-mail adicionado) |
| Hindsight (navegador) | Histórico, downloads, interpretações do Chrome/Edge/Brave (JSON ou CSV) | — (Eventos Info: artefatos de navegador são evidências, não vereditos) |
| macOS | Log unificado (log show --style json), eventos de download LSQuarantine, atributos com.apple.quarantine, plists de launchd, itens de login (plist clássico, .sfl2, BTM) | Registro de quarentena ↔ atributo de arquivo ↔ visita do navegador ↔ início de processo unidos por identificador; um plist é lido como configuração, nunca como execução |
| iLEAPP / ALEAPP | Artefatos de extração iOS + Android de exportações TSV do LEAPP | — (Eventos Info; parser genérico baseado na coluna de timestamp) |
| AWS CloudTrail | Records JSON, NDJSON, Athena | Tabela de ações de API (IAM/logging/S3/secrets) |
| GCP / Azure | Cloud Audit Logs, Azure Activity Log | Tabela de ações (IAM/logging/secrets) |
| Auditoria do Kubernetes | Log de auditoria do servidor de API (audit.k8s.io JSON-lines / EventList) | Tabela (verbo, recurso) — pod exec/attach T1609, acesso a secret T1552.007, alteração de RBAC T1098, pod privilegiado T1610/T1611, acesso anônimo T1078 |
| osquery | Log de resultado de consulta agendada (columns diferencial + snapshot) | Telemetria Info; incremento conservador de tradecraft em uma coluna de linha de comando |
| Plaso | CSV do psort (dynamic + l2tcsv) | — (Eventos Info) |
| Relatórios de sandbox | report.json do CAPEv2, resumo do Falcon Sandbox | Veredito da amostra + assinaturas comportamentais |
| Forense de memória | Volatility 3 (-r json) + Rekall: pslist/pstree, netscan, malfind, cmdline, svcscan; um envelope JSON de execução (comando, status de saída, stderr) é importado ao lado da exportação | Código injetado pelo malfind → High (T1055); listagens → Info/Low; uma execução com zero linhas ou falha diz o que estabelece |
| Intact (VolWeb reduzido) | Tabelas de plugin memory_payload.json + yarascan_results.jsonl | Mesmo mapeamento de plugin; acertos YARA em memória → Low, um cluster denso de muitas regras → Info; limites de linhas divulgados |
| TheHive | Exportação JSON de caso/alerta, lista de observáveis (TheHive 5) | Severidade do TheHive 1–4; MITRE a partir de tags marcadas com ATT&CK |
| E-mail | .eml (RFC 2822), .msg em melhor esforço | Falha de SPF/DKIM/DMARC → heurísticas de spoofing de remetente (T1566 Phishing) |
| Histórico de shell | .bash_history / .zsh_history (HISTTIMEFORMAT #epoch do bash + histórico estendido do zsh) | Info por padrão; incremento conservador em tradecraft (reverse shell, download-and-exec, acesso a credenciais, adulteração de log/histórico, SSH lateral) |
| Persistência Linux | Chaves autorizadas SSH, cron, units systemd, perfis de shell, listagens SUID e PATH de uma única coleta | Payloads graváveis por qualquer usuário, root executando arquivos graváveis pelo usuário, interpretadores setuid; nada é classificado por meramente existir |
| auditd do Linux | Registros brutos de audit.log / ausearch, tabelas do aureport | Tabela de tipo de registro (logins, gerenciamento de contas, sudo, SELinux, adulteração de auditoria) |
| journald do systemd | journalctl -o json / -o json-pretty | PRIORITY do syslog + incrementos de tradecraft (sshd, sudo, useradd) |
| sysdig / Falco | JSON de alerta do Falco, JSON de evento -j do sysdig | Prioridade da regra Falco; syscalls brutas → telemetria Info |
| Wazuh | alerts.json / NDJSON, ou exportação da API (GET /security/events) | rule.level (≥13 Critical, ≥10 High, ≥7 Medium) |
| CSV | Exportações do Velociraptor / EDR | — |
| Logs genéricos | Firewall, syslog, VPN; linhas repetitivas → padrões contados | Triado por IA |Classificação determinística de tradecraft — Linhas de comando do Windows/Sysmon, ECAR e memória são classificadas segundo regras colhidas de mais de 110 intrusões reais (The DFIR Report, Huntress): tradecraft de alta confiança → High com sua técnica ATT&CK (desativação do Defender, inibição de recuperação, dumping de credenciais, túneis reversos, Impacket, RMM/C2, exfiltração em nuvem …), uso dual → Medium; descoberta pura é marcada mas nunca escalada.
runas /netonly) → Medium$SI/$FN do MFT como provável timestomping → Mediumrclone/restic/megasync/megacmd no PrefetchZone.Identifier é lida em conjunto com Prefetch, inícios de processo e registros de presença do mesmo arquivo e elevada apenas quando a execução é datada depois dela; um payload em fluxo oculto é classificado pelo conteúdo, não pelo nomecmd.exe renomeado, uma ferramenta dropada)nltest, Get-AD*, ntdsutil … ifm e similares são extraídos de registros 4104/4103 com suas técnicasssl/x509, Suricata tls) torna-se uma linha por relacionamento e por certificado; respostas DNS são unidas às conexões posteriores do mesmo cliente dentro do TTL; cadeias de requisições web se unem apenas por identificadores que ambos os registros carregamDFIR_JEV_ENABLED)DFIR_SYNTH_ADVERSARY_HINTS)tags.yaml) — motor de regras marca eventos, eleva severidade e une técnicas MITRE-enc, [Convert]::FromBase64String); extrai IOCs ocultos; mostra blocos [Decoded]process_creation também caçam histórico Sysmon / 4688POST /cases/:id/push (webhook SIEM, monitor Velociraptor, scripts)DFIR_FORENSIC_MIN_SEVERITY + uma substituição por caso, a promoção contorna o portão, e IOCs ainda são extraídos de cada eventoDetectRaptor.Windows.Detection.MFT), tanto na linha do tempo forense quanto na superj/k move um destaque de linha focada na Forensic Timeline, f favorita, i preenche o formulário manual de IOC, p fixa a descoberta citada, n abre um comentário, ? mostra uma folha de referência; alternável em Configurações → Geral, ativado por padrãoPUT /cases/:id/correlation-profileDFIR_SHODAN_KEY? ao lado da engrenagem de definições abre o manual do utilizador online num novo separadormanual, sobrevivem à reanálise)DFIR_CROSS_CASE=on/dfir findings, /dfir iocs malicious, /dfir ask … a partir do canal de incidentes; associar um canal a um caso, allowlist de quem pode gastar orçamento de IA (#235)/mobile) para descobertas/linha temporal/IOCs com veredictos; app-shell offline/cases/:id/present) para briefings de handoff e walkthroughs executivos: cartões grandes, navegação por teclado, avanço automático, filtro de gravidade, branding do modelo de relatório; exportar um deck HTML offline autónomo (#177)$0.00 fabricado quando um fornecedor não o reporta)DFIR_MAX_EVENTS) — substitui o limite de segurança predefinido de 2000 eventos por importaçãoDFIR_LOG_LEVEL; debug traça IA/capturas/OCR/anonimizaçãochoco install dfir-companion; descarrega + verifica a build portátil + inclui a extensão de captura, dados em %LOCALAPPDATA%docker compose up; evidência em volume do host, sem backend de IA incluídonpm run seed-demo para semear o cenário GlobalTechreanalyze, synthesize, coverage, verify:ai, clean-timelineO Companion pode apontar evidência de casos a servidores MCP que você executa — uma estação de trabalho SIFT, uma máquina REMnux, um serviço de baseline de triagem Windows — para que a evidência seja analisada numa máquina que tem o ferramental.
Só os alcança através do Claude Code. O Companion não é um cliente MCP: não guarda nenhum URL de servidor,
nenhum bearer token, e não inicia nenhum npx ou uvx próprio. O Claude Code já está configurado com
os seus servidores e já detém as suas credenciais, por isso é ele que fala e o Companion pede-lhe
que o faça.
Toda esta funcionalidade funciona apenas se:
DFIR_AI_CLAUDE_CODE_BIN se claude não estiver no seu PATH.claude mcp add …, ou o seu ficheiro de configuração), e
claude mcp list os mostrar ligados.Não há alternativa. Se executar o Companion em Docker, a partir do AppImage, ou a partir da build portátil Windows sem o Claude Code ao lado, as rotas MCP dir-lhe-ão isso e nada mais.
Duas consequências que vale a pena conhecer antes de confiar nisto. Cada chamada MCP passa por um modelo, por isso gasta tokens e não é a chamada determinística bit-a-bit que um pedido JSON-RPC direto seria — o prompt torna-o um transporte (uma ferramenta, argumentos exatos, saída verbatim) mas um modelo continua no meio. E porque os servidores vêm da própria configuração do Claude Code em vez de uma gerada, o Claude Code inicia todos os servidores com que está configurado em cada execução, não apenas o que está a ser usado; a allowlist limita o que pode ser chamado, não o que é lançado.
Em Settings → Tools, prima Refresh from Claude Code para carregar a sua lista de servidores, depois permita um e diga o que pode fazer. Não há nada para escrever além de política — os nomes dos servidores vêm do próprio Claude Code, por isso um erro de escrita não o pode deixar com uma entrada que silenciosamente não corresponde a nada.
POST /cases/<id>/mcp/<serverId>/run com { tool, args, targetPath }. Coloque <target> onde quer que a
ferramenta espere o caminho da evidência — é substituído pelo caminho no host de análise após a entrega
ter sido executada, por isso o argumento que escreve é o argumento que a ferramenta recebe:```json
{ "tool": "run_command",
"args": { "command": ["vol.py", "-f", "", "pslist"] },
"targetPath": "imports/memory.raw" }
`targetPath` é resolvido dentro do diretório do caso; qualquer coisa fora dele é recusada. Para uma amostra que o navegador possui e o servidor não tem caminho para acessar, `POST /cases/<id>/mcp/<serverId>/run-upload` recebe `{ filename, dataBase64 }` em vez disso e armazena os bytes dentro do caso primeiro.
Ambos retornam **202 com um id de job** em vez de bloquear. Uma execução real do Volatility ultrapassa qualquer timeout de requisição razoável, então a execução é um job em segundo plano com progresso, um botão de cancelar e uma transmissão WebSocket `job_changed`. O resultado flui para o caso através da mesma cadeia de importação que todas as outras ferramentas — eventos de timeline, achados e IOCs, com um checkpoint de desfazer — então nada sobre ler o resultado difere de uma importação comum. Saída estruturada é encaminhada para o importador correspondente; texto não estruturado cai no caminho de log genérico em vez de ser rejeitado.
Uma ferramenta que reporta sua própria falha faz o job falhar em vez de ser ingerida: uma mensagem de erro é um diagnóstico, não um artefato, e arquivá-la na timeline a faria parecer evidência.
### Pré-visualização antes de importar
**Ativada por padrão**, e vale a pena deixá-la ativada. Um servidor MCP retornará dados de referência tão prontamente quanto evidências — pergunte ao SIFT quais ferramentas ele tem e você recebe um inventário JSON que é estruturalmente idêntico a uma tabela do Volatility: um array de objetos sem timestamps. Nenhum detector consegue distingui-los, então os importadores fazem o que foram construídos para fazer e extraem cada caminho nele como um indicador de arquivo. Uma listagem de capacidades são algumas dezenas de IOCs que o caso nunca quis.
Com a pré-visualização ativada, a execução busca a saída e para. Você vê os bytes, o tamanho e o tipo que ele *iria* importar, e escolhe. Aprovar ingere **exatamente os bytes já buscados** — nunca reexecuta a ferramenta, então uma execução de vinte minutos do Volatility custa vinte minutos uma vez, e uma ferramenta com efeitos colaterais os executa uma vez. Descartar joga a saída fora e o caso permanece intacto.
Envie `preview: true` na execução para usá-la a partir da API, depois `GET`, `POST …/import` ou `DELETE` em `/cases/<id>/mcp/preview/<jobId>`.
Nada aqui substitui o julgamento sobre o que executar, e importar sem pré-visualização não é perigoso — toda importação MCP empurra um checkpoint de desfazer, então uma execução que se revela ruído está a um clique de ser revertida.
### O que usar um servidor concede
**Por padrão, tudo o que o servidor oferece.** Isso é deliberado: o Claude Code já permite que você chame qualquer ferramenta em qualquer servidor que você configurou, então exigir que você as reenumere aqui teria sido mais restritivo do que seu próprio uso diário — e um segundo lugar para descrever o mesmo servidor.
Vale saber o que "tudo" inclui. Alguns servidores expõem ferramentas granulares — `check_service`, `check_autorun`, uma por pergunta. Outros expõem um único **executor de comandos** que executa o que você entregar: o `run_command` do SIFT declara que pode executar "a maioria das ferramentas instaladas no SIFT … incluindo curl, wget, dd, fdisk e python3", e o `run_tool` do REMnux aceita um pipeline de shell inteiro. Usar tal servidor a partir do Companion significa execução de comandos naquele host — razoável em uma rede forense isolada, onde as máquinas de análise são suas e a evidência já está na sua LAN, e não razoável em qualquer outro lugar.
Duas listas **opcionais** restringem isso quando você quiser:
| Configuração | Aplica-se a | Em branco significa |
|---|---|---|
| **Restringir a ferramentas** | toda chamada | toda ferramenta que o servidor oferece |
| **Restringir a comandos** | chamadas que carregam um argumento de comando | nenhuma restrição de comando |
Comandos são correspondidos **pelo basename**, então `grep` e `/usr/bin/grep` são uma única regra. Cada estágio de um pipeline é verificado, não apenas o primeiro — `oledump.py s.doc | curl -T - http://elsewhere` precisa que tanto `oledump.py` quanto `curl` sejam permitidos. Um comando usando substituição de shell (`$(…)`, crases, `${…}`) é recusado de imediato, porque o que ele executaria não pode ser conhecido antecipadamente.
**O que a lista de comandos não faz.** Ela limita *quais* binários executam, nunca o que um permitido pode fazer — permitir `dd` permite escrever em qualquer caminho que o usuário daquele servidor possa escrever; permitir `python3` permite código arbitrário. Ela também se baseia em nomes de parâmetros bem conhecidos (`command`, `cmd`, `argv`), então um servidor que nomeia seu parâmetro de comando de forma incomum não é capturado. Ela existe para ajudar um operador que quer restringir seu próprio acesso, não para conter um servidor que ele não deveria ter configurado em primeiro lugar.
### Levando evidências ao servidor
O MCP não tem primitiva de transferência de arquivos e uma imagem de memória de vários gigabytes não pode viajar dentro de um argumento de ferramenta, então o arquivo já precisa estar em algum lugar onde o servidor possa abri-lo. Esta parte continua sendo trabalho do Companion — o Claude Code não consegue mover uma imagem para uma máquina de análise. Cada servidor escolhe uma de duas rotas:
**`remote-path`** (padrão) — a evidência já está visível para o host de análise através de um mount compartilhado. Defina um prefixo local e um prefixo remoto e o caminho é reescrito (`/srv/cases/…` → `/mnt/dfir/…`); deixe ambos vazios quando o mount está no mesmo caminho dos dois lados. Nada é copiado.
**`scp`** — o Companion envia o arquivo para um diretório de staging, a ferramenta executa, e a cópia em staging é excluída depois. Configure `host`, `remoteDir`, opcionalmente `user`, `port` e `identityFile`.
Quatro coisas a saber antes de escolher `scp`:
- **A chave do host já deve ser confiável.** `BatchMode` está ativado e `StrictHostKeyChecking` *não* está desabilitado, então um host desconhecido falha com `Host key verification failed` em vez de confiar em qualquer coisa que respondeu ao endereço. Conecte-se uma vez manualmente (ou adicione a chave a `known_hosts`) primeiro. Isso é deliberado: aceitar silenciosamente uma chave não verificada entregaria evidências a qualquer um que detenha o IP.
- **A autenticação é apenas baseada em chave.** `BatchMode` significa que o ssh nunca solicita, então um host apenas com senha não pode funcionar. Aponte `identityFile` para uma chave sem passphrase, ou carregue-a em um agente que o processo do servidor possa alcançar.
- **Não há progresso nem retomada.** Uma cópia de 16 GB é opaca até terminar ou falhar, e uma conexão perdida significa começar de novo. A transferência é cancelável e tem seu próprio timeout de uma hora, separado do timeout de chamada de ferramenta.
- **Host, usuário e diretório remoto são restritos a um conjunto de caracteres conservador** (letras, dígitos, ponto, hífen, sublinhado e `/` para o diretório). `user@host` chega ao ssh sem aspas, então qualquer coisa com significado de shell é recusada quando você a salva, em vez de no momento da transferência. O nome de arquivo em staging é derivado do nome da evidência e sanitizado da mesma forma.
Qualquer rota registra um **evento `transferred` de cadeia de custódia** nomeando o destino, então um arquivo de caso mostra que a evidência saiu desta máquina, quando e para onde. Uma transferência que falha não registra nada — a cadeia nunca afirma uma cópia que não aconteceu.
### Investigações MCP em linguagem simples
Uma única chamada de ferramenta não consegue seguir um fio. "Investigue este dump" quer um loop — executar pslist, notar algo, pivotar para malfind — e é isso que o modo agêntico faz: ele permite que o Claude Code conduza contra o servidor que você autorizou, depois mescla o que ele reporta. Este é o fluxo de trabalho MCP principal no dashboard: escreva o objetivo em linguagem simples, selecione ou navegue até a evidência, escolha o app MCP e pressione **Investigate**. Nomes de ferramentas e argumentos JSON estão disponíveis apenas na seção avançada de chamada manual.
`POST /cases/<id>/mcp/agent` com `{ prompt, servers?, targetPath?, preview? }`, ou `POST /cases/<id>/mcp/agent-upload` com `{ prompt, servers, filename, dataBase64, preview? }`.
**Leia isto antes de autorizar um servidor.** Em uma execução manual o Companion controla cada chamada, então toda chamada passa pelas allowlists de ferramenta *e* de comando. No modo agêntico não é assim: o `claude` fala com os servidores diretamente. Apenas a allowlist de ferramentas sobrevive, como `--allowed-tools`. **A allowlist de comandos não pode ser aplicada.** Permitir que um agente use uma ferramenta de execução de comandos, portanto, concede a um loop autônomo a capacidade de escolher suas próprias linhas de comando naquele host.
Permitir e habilitar um servidor MCP no Companion é a fronteira de permissão para este modo. A restrição de ferramentas do servidor ainda se aplica. Uma restrição de comandos não pode restringir o loop autônomo; ela se aplica apenas a chamadas manuais avançadas.
O que o modo ainda garante: uma restrição explícita de ferramentas é repassada ferramenta por ferramenta; uma restrição em branco deliberadamente permite toda ferramenta que aquele servidor expõe. Configurações de projeto/local, arquivos `CLAUDE.md` e hooks são excluídos, e a execução é limitada por turnos. As configurações de usuário do Claude Code permanecem habilitadas porque é onde vivem suas conexões de servidor MCP.
A resposta do agente é validada por schema e despojada de alegações de proveniência antes de ser mesclada — tudo o que ele viu veio de saída de ferramenta, que não é confiável. Nunca se pede a ele um resumo do caso, então uma execução adiciona achados, IOCs e eventos sem reescrever suas conclusões. A pré-visualização funciona aqui também, e importa mais: um loop autônomo decide por si mesmo o que reportar.
A investigação é limitada a 40 turnos. Se o Claude Code consumir esse orçamento enquanto usa ferramentas, o Companion retoma a mesma sessão uma vez com todas as ferramentas desabilitadas e pede que ele reporte apenas a partir da evidência já coletada. Isso preserva a fronteira de segurança sem perder uma investigação concluída apenas porque seu JSON final teria sido o próximo turno.
### Credenciais
Não há nenhuma para configurar aqui. Bearer tokens, headers e transports vivem todos na própria configuração MCP do Claude Code, que é o único lugar que os detém. O Companion armazena um *nome* de servidor, uma allowlist e um bloco de entrega — nada que lhe permita conectar-se a qualquer coisa por conta própria.
Uma ressalva se você for procurar: `claude mcp list` imprime a linha de comando completa de cada servidor, que para uma entrada `mcp-remote` inclui o bearer token em texto claro. O Companion analisa apenas o nome e o veredito de saúde dessa saída e nunca armazena, registra ou renderiza o resto — mas tenha cuidado onde você executa esse comando por conta própria.
## Layout do repositório```
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álise em duas fases:** um modelo de visão leve lê cada captura de ecrã para a linha
temporal forense; um modelo mais forte faz a única chamada de síntese holística (constatações, MITRE,
caminho do atacante, perguntas). Configure ambos via `.env` — consulte `companion/README.md`.
## Início rápido
> **Pré-requisito:** [Node.js](https://nodejs.org/) **22.19 ou superior** (que inclui o `npm`).
> Verifique com `node --version`. Tudo abaixo usa `npm`, pelo que não é necessário qualquer outro runtime.
> O armazenamento de casos indexado usa o módulo integrado `node:sqlite`, pelo que versões mais antigas do Node não conseguem abrir
> casos. A compilação portátil inclui um runtime compatível.
1. **Companion** (o 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)
Extensão (captura):
Mais fácil: instale diretamente da
Chrome Web Store.
No Firefox 140+, baixe dfir-capture-extension-firefox-*.zip da
última versão e descompacte-o.
Ou compile a partir do código-fonte: ``` 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
No Firefox, carregue-o a partir de about:debugging#/runtime/this-firefox → Load Temporary Add-on…
e escolha o ficheiro manifest.json (o Chrome pede a pasta; o Firefox não). O Firefox
descarta os add-ons temporários ao reiniciar, por isso repita isso a cada sessão — ainda não há
listagem na AMO, então o zip da release não é assinado e não pode ser instalado permanentemente.
O que ele recolhe, já que um carregamento temporário nunca pergunta. O Firefox mostra o seu aviso de recolha de dados apenas para um add-on assinado instalado normalmente; o
about:debuggingconcede tudo silenciosamente. A extensão declara atividade de navegação (uma captura transporta o URL e o título do separador) e conteúdo de sites (a captura de ecrã, e as linhas que um Push extrai). A extensão envia isso para o endereço companion que configurar e para mais nenhum lugar; o que esse companion reencaminha depois — um modelo de visão lê as capturas de ecrã, a síntese por IA lê as linhas, o enriquecimento consulta serviços de reputação — é a configuração do próprio companion. Ver extension/PRIVACY.md.
O popup apenas se liga a um caso existente — você cria casos no dashboard.
http://127.0.0.1:4773/dashboard, clique em + New case para criar o seu caso (ele
liga-se automaticamente). Depois, no popup da extensão, escolha esse caso no dropdown Case
(Refresh cases se ainda não estiver listado) e Start. Navegue pelas suas evidências —
o dashboard atualiza em tempo real.A atualizar um checkout existente? Depois de
git pull, volte a executarnpm installem amboscompanion/eextension/— novas funcionalidades podem adicionar dependências (por exemplo, a redação OCR de capturas de ecrã adicionoutesseract.js). Depois reinicienpm run dev(o código do servidor carrega uma vez no arranque).
A configuração completa, os endpoints HTTP, a estrutura de pastas dos casos e o modelo de análise estão documentados em companion/README.md.
Execute tudo — servidor companion + dashboard + o add-on do navegador — num único contentor.
Não são incluídos Ollama nem LiteLLM; para IA, aponte DFIR_AI_* para qualquer endpoint
compatível com OpenAI (um modelo que aloja, um fornecedor remoto, ou um Ollama/LiteLLM que execute separadamente). Com a IA
deixada por definir, o contentor ainda faz a captura completa e todos os importadores determinísticos.
Pré-requisito: Docker com o plugin Compose (
docker compose version).
Apenas localhost por design: o contentor liga-se a 0.0.0.0 internamente, mas o Compose publica a
porta em 127.0.0.1 no seu host — por isso o dashboard nunca é exposto na sua rede.
Ou baixe a imagem pré-construída do GHCR em vez de construir: ``` docker compose pull && docker compose up -d
2. **Carregue o add-on** (captura). O contentor escreve a extensão pré-construída e descompactada em
`./addon` no primeiro arranque. No Chrome/Comet abra `chrome://extensions`, ative o **Modo de
programador**, clique em **Carregar descompactada** e selecione **`./addon/dist`** (um
`dfir-companion-extension.zip` empacotado também é colocado lá).
3. Abra `http://127.0.0.1:4773/dashboard`, clique em **+ Novo caso**, depois escolha esse caso no
popup da extensão e clique em **Iniciar**.
**Dados e configuração:**
- As evidências e o estado do caso persistem em **`./cases`** no host (volume montado) — sobrevivem
a reinícios e reconstruções da imagem.
- Configure através do bloco `environment:` em [`docker-compose.yml`](https://github.com/hasamba/dfir-companion/blob/master/docker-compose.yml), ou
descomente `env_file: - .env` para usar um ficheiro `.env` (copie `companion/.env.example`).
- Para alcançar um endpoint de IA em execução no host, use `http://host.docker.internal:<port>/v1`
(no Linux sem Docker Desktop, descomente também a linha `extra_hosts` no ficheiro compose).
## Windows (Chocolatey)
Instale a compilação portátil para Windows com o [Chocolatey](https://chocolatey.org/) — não é
necessário Node.js. Numa shell elevada:```
choco install dfir-companion
dfir-companion # → http://127.0.0.1:4773/dashboard
choco upgrade dfir-companion baixa a próxima versão; choco uninstall dfir-companion
remove o binário e o shim do PATH. O instalador baixa o mesmo zip portátil publicado na
página de Releases e verifica o seu
SHA256.
Os seus dados residem no seu perfil de utilizador, não no diretório de instalação
propriedade do administrador: casos em %LOCALAPPDATA%\DFIR-Companion\cases e configuração
em %LOCALAPPDATA%\DFIR-Companion\.env (criado a partir do exemplo; edite-o para as chaves
de IA / threat-intel — todas opcionais). A desinstalação mantém essa pasta para que as
evidências nunca sejam eliminadas. Nenhuma regra de firewall é criada — o servidor liga-se
apenas a 127.0.0.1.
A extensão de captura está incluída no disco em %LOCALAPPDATA%\DFIR-Companion\extension
para instalação offline (útil em estações de trabalho isoladas) — carregue-a via
chrome://extensions → Modo de programador → Carregar sem compactação → essa pasta, ou
instale-a a partir da Chrome Web Store quando for publicada. Não é instalada automaticamente
no navegador.
Ainda não está no repositório comunitário do Chocolatey? Até ser publicada lá, obtenha o
dfir-companion.<version>.nupkga partir do release e executechoco install dfir-companion --source .a partir da sua pasta. O empacotamento reside empackaging/chocolatey/.
Descarregue dfir-companion-<version>-x86_64.AppImage a partir da
página de Releases, depois:```
chmod +x dfir-companion--x86_64.AppImage
./dfir-companion--x86_64.AppImage # → http://127.0.0.1:4773/dashboard
Não é necessário Node — ele empacota o servidor, o dashboard e as ferramentas de imagem. **Seus dados ficam no diretório a partir do qual você o executa:** `cases/` (evidências + estado) e um `.env` opcional (configuração de IA / threat-intel) são criados/lidos ao lado de onde você inicia o AppImage. Substitua com `DFIR_CASES_ROOT` (caminho absoluto) e `DFIR_ENV_FILE` (caminho absoluto para um arquivo de configuração).
### Onde os dados ficam
| Instalação | Casos + estado | Configuração (`.env`) |
| ---------------------- | ------------------------------------- | ------------------------------------- |
| Código-fonte / `npm run dev` | `companion/cases/` | `companion/.env` |
| EXE portátil do Windows | `cases/` ao lado do EXE | `.env` ao lado do EXE |
| Windows (Chocolatey) | `%LOCALAPPDATA%\DFIR-Companion\cases` | `%LOCALAPPDATA%\DFIR-Companion\.env` |
| AppImage do Linux | `$PWD/cases` (diretório de inicialização) | `$PWD/.env` (ou `DFIR_ENV_FILE`) |
| Docker / Compose | volume `./cases` montado | `environment:` / `--env-file` |
Todos os locais podem ser substituídos com `DFIR_CASES_ROOT` (caminho absoluto).
## Variáveis de ambiente (`companion/.env`)
Todo o comportamento do companion é configurado via variáveis de ambiente (`companion/.env` ou shell). Copie `companion/.env.example` para começar — ele tem comentários inline para cada variável.
### Núcleo
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_CASES_ROOT` | `./cases` | Localização da pasta de casos; caminhos relativos são resolvidos em relação a `companion/` |
| `DFIR_PORT` | `4773` | Porta do servidor (deve corresponder à extensão e ao dashboard) |
| `DFIR_HOST` | `127.0.0.1` | Interface de bind. Um bind não-loopback sem autenticação é recusado; o Docker Compose documenta sua exceção de apenas host-loopback |
| `DFIR_MAX_BODY_MB` | `256` | Tamanho máximo de upload em MB; aumente se grandes exportações de SIEM/EDR falharem com HTTP 413 |
| `DFIR_ALLOWED_ORIGINS` | _(nenhum)_ | Origens de navegador extras autorizadas a chamar a API, separadas por vírgula. A extensão de captura, o loopback e qualquer origem que o próprio companion tenha servido são sempre confiáveis, então localhost/LAN/Docker não precisam de configuração; qualquer outra origem web é recusada. Chamadores que não enviam `Origin` (curl, scripts, Velociraptor) não são afetados. Necessário quando o dashboard é servido a partir de um **hostname** — um proxy reverso ou uma implantação hospedada |
| `DFIR_ALLOWED_HOSTS` | _(nenhum)_ | Hostnames extras aos quais este companion responde, separados por vírgula. Loopback e endereços IP puros são sempre aceitos, então localhost, Docker e acessar o dashboard pela LAN em `http://192.168.1.50:4773` não precisam de configuração. Qualquer **nome** que não esteja listado é recusado — é isso que impede DNS rebinding (um site hostil apontando seu próprio domínio para a sua máquina). Defina isto quando um proxy reverso encaminha um `Host` diferente da origem que você colocou em `DFIR_ALLOWED_ORIGINS` |
| `DFIR_ALLOWED_HOST_SUFFIXES` | _(nenhum)_ | Igual ao acima, mas correspondido por sufixo de domínio, por exemplo `.lab.example.com`, para plataformas que geram um hostname novo por sessão. A correspondência é em limite de rótulo, então `.acme.com` nunca corresponde a `evilacme.com` |
| `DFIR_LOG_LEVEL` | `info` | Verbosidade do log (`debug`/`info`/`warn`/`error`). Grava no console + `logs/session-<time>.log` (global) + `cases/<id>/logs/session-<time>.log` (por caso). `debug` rastreia chamadas de IA, capturas, OCR, anonimização, enriquecimento. Altere ao vivo (sem reiniciar) via Configurações → Verbosidade do log |
| `DFIR_LOG_DIR` | `logs/` ao lado da raiz de casos | Pasta para o log de sessão **global**. Caminhos relativos ancoram em `companion/`. Logs por caso sempre ficam na pasta do caso |
### Autenticação (implantação de equipe opcional)
`DFIR_AUTH_MODE=team` habilita login OIDC/local, sessões seguras de navegador, papéis por caso e
identidades de serviço com escopo de caso. As configurações de autenticação e provedor de identidade são controles de segurança de implantação: configure-as em `.env` ou em um cofre de segredos, depois reinicie. Consulte o
[Guia de Contas de Equipe e Papéis de Caso](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/team-authentication.md) para a
lista completa de variáveis, configuração de HTTPS, bootstrap do primeiro administrador, matriz de papéis, token de extensão e
modelo de processo de escritor único.
### IA — extração (obrigatório para habilitar a análise)
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_VISION_PROVIDER` | — | `openai` \| `openrouter` \| `ollama` \| `litellm` \| `gemini` \| `anthropic` \| `claude-code`; não definido = apenas captura |
| `DFIR_VISION_MODEL` | — | Id do modelo (por exemplo `gpt-4o-mini`, `gemini-2.5-flash`); **deve suportar visão** para extração de screenshots |
| `DFIR_VISION_KEY` | — | Chave de API do provedor; deixe em branco para um proxy local sem autenticação ou para `claude-code` (usa sua assinatura da CLI `claude` já autenticada) |
| `DFIR_AI_CLAUDE_CODE_BIN` | `claude` no PATH | Apenas `claude-code`: caminho absoluto para o binário `claude` se ele não estiver no PATH |
| `DFIR_VISION_BASE_URL` | padrão do provedor | Substitui a URL base — para um proxy LiteLLM local ou qualquer endpoint compatível com OpenAI |
| `DFIR_AI_TIMEOUT_MS` | `900000` | Timeout por requisição (ms); provedores CLI (claude-code, codex) precisam de minutos em uma timeline grande |
| `DFIR_AI_MAX_TOKENS` | `16000` | Máximo de tokens de conclusão; muito baixo trunca a síntese, evita OpenRouter 402 em saldo baixo |
| `DFIR_AI_SYNTH_MAX_EVENTS` | `600` | Limite de eventos forenses enviados para síntese; Critical/High sempre recebem um achado independentemente |
| `DFIR_REPORT_SYNTH_COVERAGE` | _(desligado)_ | Defina como verdadeiro para adicionar uma nota de rodapé **§3.4 Cobertura da síntese** ao relatório — "considerados N de M eventos na janela (K omitidos: orçamento/filtrados)", a estimativa de tokens e quantas omissões de alta severidade o backfill da rede de segurança recuperou. O cartão synth-meta do dashboard sempre mostra esta linha; este flag controla apenas se ela também aparece no relatório exportado |
| `DFIR_REPORT_MODEL_PERF` | _(desligado)_ | Defina como verdadeiro para adicionar uma nota de rodapé **§3.5 Desempenho do modelo** ao relatório — o modelo de síntese, contagem de achados vs quantos o backfill da rede de segurança teve que adicionar, tentativas de parsing e (quando uma segunda opinião foi executada) com que frequência `DFIR_AI_SECOND_OPINION_MODEL` concordou com `DFIR_AI_MODEL`/`DFIR_AI_SYNTH_MODEL`. O cartão synth-meta do dashboard sempre mostra isto; este flag controla apenas se ele também aparece no relatório exportado |
| `DFIR_AI_CONTEXT_TOKENS` | `128000` | Janela de contexto do modelo; aumente para Claude/Gemini (200k/1M) para enviar mais por chamada |
| `DFIR_VISION_IMAGE_DETAIL` | `high` | `high` \| `low` \| `auto` (OpenAI/OpenRouter); `high` divide em tiles em resolução total para OCR de texto pequeno |
| `DFIR_AI_AUTO_SYNTHESIZE` | `on` | Re-sintetizar durante a captura: `on` \| `off` |
| `DFIR_AI_AUTO_SYNTHESIZE_MS` | `8000` | Janela de debounce antes da auto-síntese disparar (ms) |
| `DFIR_FLUSH_INTERVAL_MS` | `300000` | Flush de rede de segurança dos buffers de captura restantes (ms); `0` desabilita |
| `DFIR_ANONYMIZE` | `on` | Tokeniza IPs/hosts/usuários/caminhos da vítima antes das chamadas de IA: `on` \| `off` |
| `DFIR_PRESIDIO_URL` | _(não definido)_ | Opcional: URL base de um contêiner Analyzer [Presidio](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/presidio.md) autoexecutado (por exemplo `http://localhost:5002`) que escaneia texto já mascarado em busca de nomes e outras PII que regex não consegue capturar. Não definido = recurso desligado. |
| `DFIR_PRESIDIO_MIN_SCORE` | `0.6` | Piso de confiança (0–1) para achados do Presidio; em branco/não numérico recai no padrão, valores fora do intervalo são limitados |
| `DFIR_PRESIDIO_TIMEOUT_MS` | `60000` | Orçamento para uma requisição `/analyze` (os scans são divididos em chunks; cada chunk recebe o orçamento completo). Aumente para um analisador lento ou compartilhado; em branco/não numérico/≤0 recai no padrão |
> As variáveis de screenshot/visão acima (`DFIR_VISION_PROVIDER` / `DFIR_VISION_MODEL` / `DFIR_VISION_KEY` / `DFIR_VISION_BASE_URL` / `DFIR_VISION_IMAGE_DETAIL`) foram renomeadas do prefixo `DFIR_AI_*`; os nomes legados `DFIR_AI_PROVIDER` / `DFIR_AI_MODEL` / `DFIR_AI_KEY` / `DFIR_AI_BASE_URL` / `DFIR_AI_IMAGE_DETAIL` ainda funcionam como fallback obsoleto (o novo nome vence quando ambos estão definidos).
**Claude Code** — usa sua assinatura Claude autenticada via CLI `claude`, sem chave de API; lida com
visão + texto (extração de screenshots *e* síntese). Requer a CLI `claude` instalada e
`claude auth login` concluído no host. Consome os limites de taxa da sua assinatura (extração pesada
pode esgotá-los); o custo reportado é equivalente ao da API, não do bolso. Configurações → IA mostra um
status de conexão (não instalado / não conectado / conectado) com uma ação Conectar com um clique.
### IA — modelo de texto (dois níveis, opcional)
A divisão é **visão vs texto**: `DFIR_VISION_MODEL` lê screenshots (deve ser multimodal); o modelo `DFIR_AI_SYNTH_*` faz **todo o trabalho de texto** — extração de CSV, triagem de logs, síntese, perguntar/explicar. Se não definido, o trabalho de texto reutiliza `DFIR_VISION_MODEL`.
**Codex** — defina `DFIR_AI_SYNTH_PROVIDER=codex` (também válido para os provedores velo / segunda opinião)
para executar o trabalho de texto através da **Codex CLI** local da OpenAI (`codex exec`), usando sua
autenticação codex ambiente — `codex login` ou `OPENAI_API_KEY`, **sem `DFIR_AI_KEY`**. O Codex é **apenas texto** (não consegue
ler screenshots), então combine-o com um provedor de visão para extração; ele envia dados para a OpenAI
(não local). Requer `@openai/codex` instalado. O opcional `DFIR_AI_CODEX_BIN` aponta para um
`codex` fora do PATH. Configurações → IA mostra um status de conexão do codex (não instalado / não conectado /
conectado) com uma ação Conectar com um clique.
Recomendado: modelo de visão barato para screenshots, modelo de raciocínio forte para texto. Não economize no modelo de texto — um fraco falha na triagem de logs *silenciosamente*, retornando nenhum evento em vez de eventos errados (`npm run eval:real` mede exatamente isso).
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_AI_SYNTH_PROVIDER` | = `DFIR_VISION_PROVIDER` | Provedor para trabalho de texto (CSV/log/síntese) |
| `DFIR_AI_SYNTH_MODEL` | = `DFIR_VISION_MODEL` | Id do modelo de texto — extração de CSV/log + síntese (por exemplo `gpt-4o`, `gemini-2.5-pro`, `claude-sonnet-4-6`) |
| `DFIR_AI_SYNTH_KEY` | = `DFIR_VISION_KEY` | Chave de API do modelo de texto |
| `DFIR_AI_SYNTH_BASE_URL` | = `DFIR_VISION_BASE_URL` | URL base da síntese |
### IA — modelo de caça Velociraptor (opcional)
Um modelo dedicado usado **apenas** para gerar caças VQL do Velociraptor (os recursos *Sugerir caças Velociraptor* / *Caças de Frota*), separado de extração/síntese/OCR — muitos modelos erram o VQL. Também editável em **Configurações → IA**.
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_AI_VELO_PROVIDER` | `openrouter` | Provedor para geração de caças VQL |
| `DFIR_AI_VELO_MODEL` | `anthropic/claude-haiku-4.5` | Id do modelo para geração de caças VQL |
| `DFIR_AI_VELO_KEY` | = `DFIR_VISION_KEY` | Chave de API (reutiliza a chave principal quando em branco) |
| `DFIR_AI_VELO_BASE_URL` | = `DFIR_VISION_BASE_URL` | Substituição da URL base |
### IA — prompts personalizados (opcional)
Cada prompt tem duas formas de substituição (ordem de prioridade): `DFIR_AI_<NAME>_PROMPT` (texto inline, lido na inicialização) e `DFIR_AI_<NAME>_PROMPT_FILE` (caminho para arquivo, relido a cada chamada — edite e ele se aplica imediatamente). `npm run prompts:eject` grava os padrões embutidos como ponto de partida.
| Nome do prompt | Token `<NAME>` |
|---|---|
| Extração por screenshot | `SYSTEM` |
| Triagem de importação de CSV | `CSV` |
| Triagem de importação de log | `LOG` |
| Síntese holística | `SYNTH` |
| Perguntas e respostas do caso | `ASK` |
| Resumo executivo | `EXEC` |
| Timeline narrativa | `NARRATIVE` |
| Caças de frota sugeridas | `HUNTS` |
| Caças de playbook sugeridas | `PBHUNTS` |
| Hipóteses de lacunas na timeline | `GAPHYP` |
| Tradutor de Consultas (NL → consulta) | `QUERYXLATE` |
### Enriquecimento de threat-intel (opcional — desligado por padrão)
Adicione uma chave para habilitar aquele provedor. Todos os provedores externos são opt-in por caso a partir do dashboard.
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_VT_KEY` | — | Chave de API do VirusTotal (hash / IP / domínio / URL) |
| `DFIR_HUNTINGCH_KEY` | — | Auth-Key do abuse.ch para Hunting.ch (MalwareBazaar · ThreatFox · URLhaus · YARAify); recai em `DFIR_MB_KEY` |
| `DFIR_MB_KEY` | — | Chave legada do abuse.ch — alimenta o Hunting.ch; prefira `DFIR_HUNTINGCH_KEY` |
| `DFIR_ABUSEIPDB_KEY` | — | Chave de API do AbuseIPDB (reputação de IP) |
| `DFIR_CROWDSTRIKE_CLIENT_ID` | — | Client ID OAuth2 do CrowdStrike Falcon TI |
| `DFIR_CROWDSTRIKE_CLIENT_SECRET` | — | Segredo OAuth2 do CrowdStrike (precisa de *Indicators: Read* + *MalQuery: Read*) |
| `DFIR_CROWDSTRIKE_CLOUD` | `us-1` | Nuvem do tenant: `us-1` \| `us-2` \| `eu-1` \| `gov-us-1` \| `gov-us-2` |
| `DFIR_CROWDSTRIKE_BASE_URL` | da nuvem | URL base explícita da API (substitui `DFIR_CROWDSTRIKE_CLOUD`) |
| `DFIR_ROCKYRACCOON_KEY` | — | Chave do RockyRaccoon para prevalência de processos Windows / LOLBIN / ATT&CK |
| `DFIR_MISP_URL` | — | URL da instância MISP — URL + chave obrigatórias para enriquecimento e push |
| `DFIR_MISP_KEY` | — | Chave de autenticação da API MISP |
| `DFIR_MISP_CA` | — | Bundle de CA PEM para MISP com CA interna (verificação permanece ativada) |
| `DFIR_MISP_INSECURE` | — | `=1` para pular a verificação TLS (apenas laboratório) |
| `DFIR_MISP_DISTRIBUTION` | `0` | Distribuição de novo evento: `0`=org, `1`=comunidade, `2`=conectado, `3`=todos |
| `DFIR_MISP_ANALYSIS` | `1` | Estado de análise de novo evento: `0`=inicial, `1`=em andamento, `2`=completo |
| `DFIR_MISP_TIMELINE_LIMIT` | `5000` | Máximo de eventos da timeline forense por push; além do limite os mais severos são mantidos e o push avisa |
| `DFIR_YETI_URL` | — | URL da instância YETI — URL + chave obrigatórias |
| `DFIR_YETI_KEY` | — | Chave de API do YETI |
| `DFIR_YETI_CA` | — | Bundle de CA PEM para YETI com CA interna |
| `DFIR_YETI_INSECURE` | — | `=1` para pular a verificação TLS (apenas laboratório) |
| `DFIR_OPENCTI_URL` | — | URL da instância OpenCTI — URL + chave obrigatórias (hash/ip/domínio/url) |
| `DFIR_OPENCTI_KEY` | — | Token de API do OpenCTI |
| `DFIR_OPENCTI_CA` | — | Bundle de CA PEM para OpenCTI com CA interna |
| `DFIR_OPENCTI_INSECURE` | — | `=1` para pular a verificação TLS (apenas laboratório) |
| `DFIR_OPENCTI_MALICIOUS_SCORE` | `75` | Limite de `x_opencti_score` para veredito malicioso |
| `DFIR_RDAP_URL` | `https://rdap.org` | Base WHOIS-over-RDAP (sem chave; bootstrap IANA para o RIR proprietário) |
| `DFIR_GEOIP_URL` | `https://ipinfo.io/{ip}/json` | Template de URL GeoIP (HTTPS sem chave; `{ip}` substituído; o parser também tolera ip-api.com + ipwho.is) |
| `DFIR_GEOIP_KEY` | — | Chave GeoIP opcional (preenche `{key}`, senão anexada como `?token=`) para um backend pago/auto-hospedado |
| `DFIR_SHODAN_KEY` | — | Chave de API do Shodan — também alimenta o enriquecedor de IP de consulta de host do Shodan (compartilhado com exposição de cliente) |
| `DFIR_HASHLOOKUP_URL` | `https://hashlookup.circl.lu` | Base do hashlookup do CIRCL (consulta de arquivo conhecido sem chave para IOCs de hash); substitua para um espelho auto-hospedado / air-gapped |
| `DFIR_ENRICH_DELAY_MS` | `1500` | Throttle entre consultas (ms) |
| `DFIR_ENRICH_JITTER_MS` | `0` | ± jitter aleatório adicionado à espera entre chamadas (ms); distribui execuções alinhadas/paralelas para que não atinjam juntas a janela de limite de taxa de um provedor |
| `DFIR_ENRICH_RETRIES` | `2` | Tentativas de retry para uma chamada de provedor que atinge um 429, honrando `Retry-After` quando o provedor envia um, antes de ser contada como erro |
| `DFIR_ENRICH_RETRY_BACKOFF_MS` | `1000` | Backoff base antes do primeiro retry de 429 (dobra a cada tentativa, limitado a 30s) quando o provedor não deu `Retry-After` |
| `DFIR_ENRICH_MAX` | `100` | Máximo de IOCs consultados por lote de enriquecimento (hashes/IPs primeiro) |
| `DFIR_ENRICH_MAX_BATCHES` | `20` | Quantos lotes limitados um disparo de enriquecimento pode encadear. Um caso com mais IOCs que `DFIR_ENRICH_MAX` não para mais no limite: a execução salva, depois inicia o próximo lote de onde parou, até este número. `1` restaura o comportamento antigo de execução única. O que o limite ainda deixar é reportado na linha de status, não descartado silenciosamente |
| `DFIR_ENRICH_HEALTH_TTL_MS` | `60000` | Cache do veredito up/down para provedores auto-hospedados (ms) |
| `DFIR_ENRICH_HEALTH_POLL_MS` | `60000` | Intervalo de re-sondagem para provedores down; `0` desabilita o poller em segundo plano |
### Exposição do cliente (opcional)
Verifica os domínios/emails da **própria organização vítima** contra bancos de dados de vazamentos — nunca domínios de adversário/IOC.
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_HIBP_KEY` | — | Chave de API do Have I Been Pwned |
| `DFIR_HIBP_USER_AGENT` | `DFIR Companion` | Cabeçalho User-Agent do HIBP |
| `DFIR_LEAKCHECK_KEY` | — | Chave de API do LeakCheck Pro |
| `DFIR_LEAKCHECK_DOMAIN_LIMIT` | `1000` | Máximo de registros por busca de domínio |
| `DFIR_DEHASHED_KEY` | — | Chave de API do DeHashed v2 |
| `DFIR_DEHASHED_BASE_URL` | padrão do DeHashed | Substitui a URL base da API do DeHashed |
| `DFIR_SHODAN_KEY` | — | Chave do Shodan (domínio → hosts expostos / portas / CVEs; sem busca de email) |
| `DFIR_EXPOSURE_DELAY_MS` | `1500` | Throttle entre consultas de provedores (ms) |
### Push / importação DFIR-IRIS (opcional)
URL e chave são obrigatórias para habilitar. A mesma conexão alimenta **Push para DFIR-IRIS** e
**Importar do IRIS** (puxar ativos/IOCs/timeline de um caso IRIS existente para um caso).
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_IRIS_URL` | — | URL da instância IRIS |
| `DFIR_IRIS_KEY` | — | Chave de API do IRIS |
| `DFIR_IRIS_CA` | — | Bundle de CA PEM para IRIS com CA interna |
| `DFIR_IRIS_INSECURE` | — | `=1` para pular a verificação TLS (apenas laboratório) |
| `DFIR_IRIS_CUSTOMER_ID` | `1` | Id do cliente para novos casos IRIS (push) |
| `DFIR_IRIS_CLASSIFICATION_ID` | `1` | Id de classificação para novos casos IRIS (push) |
### Push Timesketch (opcional)
URL + usuário + senha são todos obrigatórios para habilitar o push. Exportar para JSONL funciona sem qualquer configuração.
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_TIMESKETCH_URL` | — | URL da instância Timesketch |
| `DFIR_TIMESKETCH_USER` | — | Nome de usuário de autenticação local |
| `DFIR_TIMESKETCH_PASSWORD` | — | Senha de autenticação local |
| `DFIR_TIMESKETCH_TIMELINE` | `DFIR-Companion Forensic Timeline` | Nome da timeline gerenciada |
| `DFIR_TIMESKETCH_CA` | — | Bundle de CA PEM para Timesketch com CA interna |
| `DFIR_TIMESKETCH_INSECURE` | — | `=1` para pular a verificação TLS (apenas laboratório) |
### Exportação Notion (opcional)
Apenas o token habilita. Compartilhe a página/banco de dados de destino com a integração. "Nova página" precisa de um
banco de dados ou página pai (padrão de env ou inserido por exportação); "página existente" atualiza uma página que você cola.
| Variável | Padrão | Significado |
|---|---|---|
| `DFIR_NOTION_TOKEN` | — | Segredo de integração interna (Notion: Configurações → Conexões → desenvolva a sua própria) |
| `DFIR_NOTION_DATABASE_ID` | — | Banco de dados padrão para exportações de "nova página" (o template de investigação) |
| `DFIR_NOTION_PARENT_PAGE_ID` | — | Padrão alternativo: criar a nova página sob esta página pai |
| `DFIR_NOTION_CONTAINER_TITLE` | `🔍 DFIR Companion — Auto-generated` | Título do bloco gerenciado que o Companion possui |
| `DFIR_NOTION_MAX_TIMELINE` | `500` | Máximo de linhas de timeline gravadas no Notion |
| `DFIR_NOTION_CA` | — | Bundle de CA PEM se um proxy usar uma CA interna |
| `DFIR_NOTION_INSECURE` | — | `=1` para pular a verificação TLS (apenas laboratório) |
### Caças ao vivo do Velociraptor + bundles de triagem (opcional)
Defina `DFIR_VELOCIRAPTOR_API_CONFIG` para habilitar. Gere a configuração uma vez com:```
velociraptor --config server.config.yaml config api_client --name dfir --role administrator,api api.config.yaml
| Variável | Padrão | Significado |
|---|---|---|
DFIR_VELOCIRAPTOR_API_CONFIG | — | Caminho para o arquivo de configuração do api_client |
DFIR_VELOCIRAPTOR_BINARY | velociraptor | Caminho do executável (caminho completo do .exe no Windows) |
DFIR_VELOCIRAPTOR_GUI_URL | — | URL base da GUI para deep-linking para hunts lançados |
DFIR_VELOCIRAPTOR_ORG | root | Org para o ?org_id= do deep link (a GUI exige, antes do fragmento #) |
DFIR_VELOCIRAPTOR_TIMEOUT_MS | 60000 | Timeout por consulta (ms) |
DFIR_VELOCIRAPTOR_MAX_ROWS | 1000 | Máximo de linhas retornadas ao dashboard |
DFIR_VELOCIRAPTOR_MAX_OUTPUT | 52428800 | Limite rígido de bytes de saída de consulta interativa (50 MB) |
DFIR_VELOCIRAPTOR_COLLECT_MAX_OUTPUT | 268435456 | Limite maior para coleta de bundle-hunt (linhas + JSON enviado; THOR/Hayabusa são grandes). Um artefato/upload acima disso é ignorado (registrado em log), não fatal — o restante ainda é importado. |
DFIR_VELO_HUNT_WAIT_MIN | 10 | Minutos padrão antes de um hunt de triage bundle auto-coletar (override por execução + por bundle; limitado a 1–1440) |
DFIR_VELOCIRAPTOR_UPLOAD_VQL | — | Avançado: sobrescrever o VQL que lê os relatórios de texto enviados de um hunt (json/jsonl/ndjson/csv/txt/log; sensível à versão; mantenha o placeholder __HUNT_ID__) |
DFIR_VELOCIRAPTOR_FLOW_UPLOAD_VQL | — | Avançado: sobrescrever o VQL que lê os relatórios enviados de um único flow colado externamente (mantenha os placeholders __CLIENT_ID__/__FLOW_ID__) |
DFIR_HUNT_SUGGEST_MAX |
Triage bundles (Settings → Velociraptor tab): Browse server artifacts lista os artefatos CLIENT
coletáveis do servidor; monte + salve bundles nomeados (três vêm embutidos — Best Practice (varredura
quick-wins), Super-Timeline Triage (artefatos brutos do host, roteados apenas para a super-timeline) e Linux
Triage — armazenados globalmente ao lado de cases/ em bundles/). Todo bundle, incluindo os embutidos, é editável in
place — uma edição salva um override; Reset to default o descarta. Execute um como hunt a partir do painel Fleet Collection do dashboard (opcionalmente escopado
por labels de include/exclude + OS, e um piso de importação por minimum-severity). O collection timeout é uma configuração do bundle
(configurada no editor — aumente para artefatos lentos como THOR; o padrão do Velociraptor é 600 s) e é
aplicado automaticamente em cada execução. Cada hunt também carrega uma expiração relativa — por quanto tempo ele continua agendando em
clientes que se registram depois — escolhida entre 1 hour / 1 day / 1 week (padrão 1 hour, vs o próprio
padrão de uma semana do Velociraptor); é um padrão por bundle definido no editor e sobrescrevível por execução. Bundles também podem carregar parâmetros por artefato (passados para o
spec do hunt) para que um artefato pesado emita menos na origem — Best Practice inclui **Hayabusa fixado em RuleLevel=Critical/High/Medium
RuleStatus=Stable+Experimental** para não inundar a importação; ajuste qualquer artefato via o JSON opcional Advanced → parameters do builder,
e descarte linhas ruidosas com filtros de exclusão por artefato (VQL WHERE, ex.: NOT OSPath =~ 'pagefile'). O hunt permanece aberto até a expiração, então
o Companion auto-coleta após DFIR_VELO_HUNT_WAIT_MIN e ingere tanto as linhas de resultado quanto qualquer
relatório JSON enviado (ex.: THOR/Hayabusa via Generic.Scanner.ThorZIP — para esses as linhas não importam, o
JSON enviado importa; ele é auto-detectado e roteado para o importador correto), depois sintetiza — ou clique em Collect
now no cartão do job ativo para puxar antecipadamente. O job em andamento persiste por caso (state/velo-hunt.json) e
sobrevive a um reinício do servidor; os resultados aparecem na timeline/IOCs do dashboard.| Variável | Padrão | Descrição |
|---|---|---|
DFIR_MCP_MODEL | (padrão da CLI) | Modelo usado para chamadas únicas de ferramentas MCP, passado para claude --model. |
DFIR_MCP_AGENT_MODEL | (padrão da CLI) | Modelo para o loop agêntico, passado para claude --model. |
Registrar um servidor é uma decisão de segurança, não apenas configuração — veja Registering an MCP server.
Envie novos/agravados achados, atualizações de playbook e marcos de investigação para webhooks do Slack /
MS Teams ou e-mail SMTP. Não há variável de ambiente de ativação — os canais são criados no
dashboard (⚙ Settings → Notifications) e armazenados ao lado de cases/ em notifications/config.json
(gitignored; contém as URLs de webhook + senhas SMTP). A lista começa vazia (opt-in). Cada canal tem um
limite de severidade e toggles por evento (findings / playbook / milestones). Use o botão Test para
verificar um canal de ponta a ponta.
⚠ OPSEC: as notificações enviam conteúdo do caso (títulos de findings/tarefas) para terceiros. Não ative em um caso sensível a menos que o destino seja confiável.
Slack — crie um Incoming Webhook (sem escopos OAuth manuais; o Slack adiciona incoming-webhook automaticamente):
DFIR Companion) e escolha seu workspace.https://hooks.slack.com/services/T…/B…/…).Um webhook publica em um canal — adicione outro webhook (e outro canal do Companion) para cada canal extra.
A URL é um segredo (qualquer um com ela pode publicar lá), por isso o arquivo de configuração é gitignored e a URL é
redigida nas respostas da API. Escopos de bot-token como chat:write não são necessários — o Companion publica via o
incoming webhook, não a Web API.
MS Teams — adicione um conector Incoming Webhook (ou um fluxo Power Automate "when a webhook request is received")
a um canal e cole sua URL (o Companion envia um MessageCard). E-mail SMTP — forneça ao canal um host/porta,
usuário+senha opcionais, e from/to; STARTTLS oportunista + AUTH LOGIN são usados quando oferecidos. Para um teste
local rápido, aponte para Mailpit (docker run -p 1025:1025 -p 8025:8025 axllent/mailpit).
Telegram — usa um token da Bot API + um ID de chat/canal/grupo:
/newbot e copie o token (123456789:AAF…)./start para seu bot, depois abra https://api.telegram.org/bot<TOKEN>/getUpdates; o chat.id é um inteiro positivo.getUpdates; o chat.id é um inteiro negativo.@mychannel.@getidsbot para obter o ID numérico (geralmente -100…).Já está executando o war-room bot? Deixe o token em branco e preencha apenas
o chat ID — o canal reutiliza DFIR_TELEGRAM_BOT_TOKEN do .env, e o campo mostra (already set). O token
permanece apenas no .env, então rotacioná-lo lá rotaciona este canal também. Digite um token aqui apenas para enviar através de um
bot diferente; ele então sobrescreve o do env para este canal.
Um token digitado aqui é armazenado em notifications/config.json (ao lado de cases/) e nunca é ecoado de volta ao
navegador — o dashboard só sabe se um está definido, e se veio do .env.
| Variável | Padrão | Significado |
|---|---|---|
DFIR_PUBLIC_URL | http://<host>:<port> | URL base pública usada para deep-link de uma notificação de volta ao caso (defina quando acessado via hostname/proxy) |
DFIR_NOTIFY_CA | — | Bundle de CA PEM para um host de webhook auto-hospedado (ex.: Mattermost) |
DFIR_NOTIFY_INSECURE | — | =1 para pular a verificação TLS do host do webhook (apenas laboratório) |
As notificações empurram para fora; este é o caminho de volta para dentro. Execute o caso a partir do canal de incidentes em vez de alternar para o dashboard a cada pergunta:``` /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 é ativada quando você define seu segredo:
**Nenhum túnel é necessário** — o companion abre a conexão de saída:
| Plataforma | Como os comandos chegam | Ativar com |
|---|---|---|
| Slack | **Socket Mode — WebSocket de saída** | `DFIR_SLACK_SOCKET_MODE=on` + `DFIR_SLACK_APP_TOKEN` (`xapp-…`, `connections:write`) |
| Telegram | **Long polling** | `DFIR_TELEGRAM_POLL=on` + `DFIR_TELEGRAM_BOT_TOKEN` |
Ou como webhooks de entrada, que precisam de um endereço público:
| Plataforma | Endpoint | Ativar com |
|---|---|---|
| Slack | `POST /integrations/slack/command` | `DFIR_SLACK_SIGNING_SECRET` (Basic Information → Signing Secret) |
| MS Teams | `POST /integrations/teams/command` | `DFIR_TEAMS_TOKEN` (segredo compartilhado no cabeçalho `Authorization`) |
| Telegram | `POST /integrations/telegram/command` | `DFIR_TELEGRAM_SECRET_TOKEN` (o `secret_token` que você passa para `setWebhook`) |
**O Telegram não precisa de túnel.** Crie o bot com [@BotFather](https://t.me/BotFather), defina duas
variáveis, reinicie e envie uma mensagem para ele:```bash
DFIR_TELEGRAM_POLL=on
DFIR_TELEGRAM_BOT_TOKEN=123456789:AAF...
O companion chama o Telegram e pede novos comandos, portanto nada da máquina é acessível
a partir da internet — a mesma direção de saída que o notificador já utiliza. Um bot não pode fazer as duas coisas:
limpe qualquer webhook existente com .../deleteWebhook primeiro.
Slack Socket Mode é a mesma ideia: ative o Socket Mode na aplicação, crie um token ao nível da aplicação
(xapp-…, scope connections:write), e o companion liga-se ao Slack — sem Request URL.
O modo Webhook alcança este companion a partir da internet através do seu túnel ou reverse proxy — e esse hostname tem de estar em
DFIR_ALLOWED_HOSTS, ou a proteção contra DNS-rebinding rejeita o pedido antes de o bot o ver. O MS Teams não tem opção de saída, por isso precisa sempre disto.
OPSEC — qualquer pessoa que possa publicar no canal pode extrair conteúdo de casos. Casos protegidos por palavra-passe são recusados por completo via chat (uma mensagem de chat não transporta um desbloqueio). Defina
DFIR_*_ACTION_USERSpara manter o gasto de IA, a re-síntese e a re-vinculação em respondedores nomeados; ao fazê-lo, também confina todos os outros ao caso vinculado ao canal.
| Variável | Predefinição | Significado |
|---|---|---|
DFIR_SLACK_ACTION_USERS | (não definido = aberto) | Ids de utilizador do Slack separados por vírgulas autorizados a executar ask/hunt/synthesize/bind |
DFIR_TEAMS_ACTION_USERS | (não definido = aberto) | O mesmo, para o Teams |
DFIR_TELEGRAM_ACTION_USERS | (não definido = aberto) | O mesmo, para o Telegram (ids de utilizador numéricos) |
DFIR_SLACK_RESPONSE_HOSTS | hooks.slack.com | Hosts adicionais para onde um resultado assíncrono pode ser entregue (servidor compatível com Slack auto-alojado) |
DFIR_TEAMS_RESPONSE_HOSTS | *.webhook.office.com, *.logic.azure.com, *.office.com | O mesmo, para o Teams |
DFIR_TELEGRAM_BOT_TOKEN | — | Token do @BotFather, usado para entregar resultados assíncronos |
DFIR_TELEGRAM_API_BASE | https://api.telegram.org | Substituição do URL base da Bot API |
| Variável | Predefinição | Significado |
|---|---|---|
DFIR_HUNT_PLATFORMS | todas | Lista de plataformas permitidas separadas por vírgulas para cartões de hunt-pivot: velociraptor, defender, elastic, splunk, sigma, yara, suricata |
DFIR_CORRELATE_WINDOW_S | 2 | Janela temporal (s) para fusão de eventos de várias fontes no mesmo caminho |
DFIR_PHASE_GAP_S | 300 | Intervalo entre eventos (s) que inicia uma nova fase de ataque |
DFIR_BEACON_MIN_COUNT | 5 | Número mínimo de eventos de ligação a um canal (host → dest:port) antes de ser considerado para deteção de beacon |
DFIR_BEACON_MAX_JITTER_PCT | 20 | Jitter máximo de intervalo (desvio padrão como % da média) para um canal contar como beacon — mais baixo = mais rigoroso |
DFIR_GAP_MIN_MINUTES | 30 | Limite mínimo absoluto para análise de lacunas de log — um silêncio na linha temporal mais curto do que isto nunca é sinalizado |
DFIR_GAP_DENSITY_FACTOR | 4 | Uma lacuna também tem de ser ≥ este valor × o intervalo mediano entre eventos da linha temporal para ser sinalizada (suprime a quietude normal em linhas temporais esparsas; 0 = apenas o limite mínimo) |
DFIR_GAP_ACTIVE_HOURS | (não definido) | Horário de trabalho opcional "8-18" (UTC, suporta wrap-around "22-6") — sinalizar apenas lacunas que se sobreponham a eles; substitui a heurística de densidade quando definido |
DFIR_GAP_MAX_FINDINGS | 5 | Limite de lacunas de silêncio completo que escalam para uma descoberta (o painel/relatório continua a mostrar todas) — impede que um caso de super-linha temporal inunde a lista de descobertas |
DFIR_GAP_HYPOTHESIS_MAX |
Exemplo de .env (configuração OpenRouter de dois níveis):```
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 npm — referência completa da CLI
Todos executados a partir de `companion/`. Os argumentos após `--` são encaminhados para o script.
### `npm run dev`
Inicia o servidor (lê `.env`). Vincula a `127.0.0.1:4773`. Dashboard em `/dashboard`.```
npm run dev
npm run buildVerificação de tipos / compilação com tsc. Sem argumentos.```
npm run build
### `npm test`
Execute a suíte completa do vitest. Sem argumentos.```
npm test
npm run verify:ai -- [caseId] [flags]Teste de fumaça em uma única chamada: envia 3 capturas de tela do meio do caso para o modelo configurado e confirma que a resposta é analisada de acordo com o schema. Exibe descobertas, eventos forenses e pré-visualização do caminho do atacante.
| Arg / flag | Padrão | Efeito |
|---|---|---|
caseId (posicional) | test1 | Caso do qual amostrar capturas de tela. |
--provider NAME | de .env | Substitui DFIR_VISION_PROVIDER para esta execução. |
--model ID | de .env | Substitui DFIR_VISION_MODEL para esta execução. |
--key KEY | de .env | Substitui DFIR_VISION_KEY para esta execução. |
| 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 quantas capturas de tela de um caso foram analisadas vs. ignoradas (duplicatas) vs.
nunca tocadas. Lê apenas `captures.jsonl` e o estado de investigação indexado — sem chamadas de IA.
| Arg | Padrão | Efeito |
| --- | --- | --- |
| `caseId` (posicional) | `test1` | Caso a inspecionar. |```
npm run coverage -- test1
npm run coverage -- mycase
npm run reanalyze -- <caseId> [flags]Executa novamente a análise de IA sobre as capturas de ecrã já recolhidas de um caso, reconstruindo o
estado da investigação. Executa a síntese no final, a menos que --no-synthesis seja passado.
Utiliza a sua quota de API (~1 chamada por --window capturas de ecrã, mais 1 chamada de síntese).
| Arg / flag | Predefinição | Efeito |
|---|---|---|
caseId (posicional) | test1 | Caso a processar. |
--reset | desativado | Esvazia o estado antes de analisar. Caso contrário, faz merge no existente. |
--all | desativado | Inclui também capturas de ecrã duplicadas (mais exaustivo, mais chamadas de API). |
--window N | 4 | Capturas de ecrã por chamada de extração de IA. |
--provider NAME | de .env | Substitui DFIR_VISION_PROVIDER (extração). |
--model ID | de .env | Substitui DFIR_VISION_MODEL (extração). |
--key KEY | de .env | Substitui DFIR_VISION_KEY (extração). |
--base-url URL | de .env | Substitui DFIR_VISION_BASE_URL (extração) — por exemplo, um proxy LiteLLM local. |
--synth-provider NAME | = extração / DFIR_AI_SYNTH_PROVIDER | Fornecedor para a passagem de síntese. |
--synth-model ID | = extração / DFIR_AI_SYNTH_MODEL | Modelo mais forte para síntese (findings / MITRE / caminho do atacante). |
--synth-key KEY | = extração / DFIR_AI_SYNTH_KEY | Chave de API para o fornecedor de síntese. |
--synth-base-url URL | = extração / DFIR_AI_SYNTH_BASE_URL | URL base para o fornecedor de síntese. |
--no-synthesis | desativado | Ignora a passagem final de síntese (apenas linha temporal forense em bruto). |
npm run reanalyze -- test1
npm run reanalyze -- test1 --reset
npm run reanalyze -- test1 --all --reset
npm run reanalyze -- test1 --reset --window 3
npm run reanalyze -- test1 --reset --model openai/gpt-4o
npm run reanalyze -- test1 --reset --provider gemini --model gemini-1.5-pro --key AIza...
npm run reanalyze -- test1 --reset
--model openai/gpt-4o-mini
--synth-model openai/gpt-4o
npm run reanalyze -- test1 --reset
--provider openrouter --model openai/gpt-4o-mini --key sk-or-...
--synth-provider openrouter --synth-model google/gemini-2.5-pro --synth-key sk-or-...
npm run reanalyze -- test1 --reset --no-synthesis
### `npm run synthesize -- <caseId> [flags]`
Uma chamada de IA apenas de texto sobre a linha do tempo forense completa (no escopo) → descobertas, IOCs,
mapeamento MITRE, caminho do atacante, perguntas-chave. Prefere as variáveis de ambiente `DFIR_AI_SYNTH_*`; recorre
ao modelo de extração.
| Arg / flag | Padrão | Efeito |
| --- | --- | --- |
| `caseId` (posicional) | `test1` | Caso a sintetizar. |
| `--provider NAME` | `DFIR_AI_SYNTH_PROVIDER` ?? `DFIR_VISION_PROVIDER` | Substitui o provedor de síntese. |
| `--model ID` | `DFIR_AI_SYNTH_MODEL` ?? `DFIR_VISION_MODEL` | Substitui o modelo de síntese. |
| `--key KEY` | `DFIR_AI_SYNTH_KEY` ?? `DFIR_VISION_KEY` | Substitui a chave de API de síntese. |
| `--base-url URL` | `DFIR_AI_SYNTH_BASE_URL` ?? `DFIR_VISION_BASE_URL` | Substitui a URL base de síntese (por exemplo, um proxy LiteLLM local). |```
# Use whatever .env says
npm run synthesize -- test1
# Re-run conclusions with a stronger model (no re-capture needed)
npm run synthesize -- test1 --model openai/gpt-4o
# Switch provider for this run
npm run synthesize -- test1 --provider gemini --model gemini-1.5-pro --key AIza...
npm run clean-timeline -- <caseId> [--apply]Remove linhas de analista/uso de ferramentas (caçadas do Velociraptor, notebooks, pesquisas, "Response and Monitoring accessed", etc.) da linha do tempo forense. Sem chamadas de IA. Dry-run por padrão.
| Arg / flag | Padrão | Efeito |
|---|---|---|
caseId (posicional) | test1 | Caso a limpar. |
--apply | desativado | Salva de fato. Sem isso, apenas pré-visualiza o que seria removido. |
npm run clean-timeline -- test1
npm run clean-timeline -- test1 --apply
Após a limpeza, execute novamente `npm run synthesize -- <caseId>` para atualizar as conclusões.
## Fluxos de trabalho recomendados```
# Daily live capture (just start the server and browse)
npm run dev
8 |
| Número máximo de hunts de frota sugeridos por IA retornados por geração (requer um provedor de IA, não a API do Velociraptor) |
DFIR_PBHUNT_SUGGEST_MAX | 30 | Número máximo de hunts de playbook sugeridos por IA retornados por geração (um por tarefa relacionada a endpoint; requer um provedor de IA) |
5| Número máximo de lacunas sobre as quais a chamada de IA Hypothesize gaps raciocina por execução (piores primeiro); cada uma continua a receber as suas coleções de shadow-artifact |
DFIR_GAP_HYPOTHESIS_CONTEXT | 8 | Eventos de cada lado de uma lacuna fornecidos ao prompt de hipótese como contexto antes/depois |
DFIR_DEDUP | on | Ignorar a análise de IA de uma captura de ecrã apenas quando é byte-identical à captura anterior (correspondência exata SHA-256 — o ecrã não mudou). Qualquer diferença é analisada; ainda assim armazenada como evidência de qualquer forma. Defina off para analisar todas as capturas de ecrã |
TAGGER_AUTO | true | Event tagger baseado em conteúdo (estilo Timesketch tags.yaml): executar o conjunto de regras automaticamente após cada importação, marcando eventos correspondentes (e, na linha temporal forense, elevando a severidade / unindo MITRE). Defina false para o executar apenas manualmente a partir do dashboard (Super-Timeline → 🏷 Content tagger → Run tagger) |
TAGGER_SCOPE | both | Sobre qual linha temporal o tagger é executado: forensic (apenas linha temporal curada), super (apenas super-linha temporal em bruto, apenas tags — nunca altera severidade/MITRE), ou both. As tags são indexadas por id de evento, por isso filtram em ambas as linhas temporais independentemente |
TAGGER_RULES_FILE | (não definido) | Caminho absoluto para um ficheiro de regras personalizado, substituindo o ficheiro editado no dashboard e o predefinido incluído (companion/data/tags.yaml). Edite as regras na aplicação via Super-Timeline → 🏷 Content tagger → Edit rules |