Serveur compagnon de forensique DFIR + extension de capture
Triage DFIR assisté par IA — sur votre machine. Transforme les captures d'écran d'investigation et les artefacts importés en une chronologie forensique, des constats, des IOC, un graphe actif↔IOC et des rapports partageables ; posez vos questions sur le cas en langage naturel et collaborez avec d'autres enquêteurs.
Un compagnon local de forensique numérique / réponse à incident. Une extension de navigateur capture des captures d'écran de votre investigation (Velociraptor, tableaux de bord EDR/SIEM, Security Onion, Splunk4DFIR, VolWeb, VirusTotal, etc.) comme preuves ; un serveur local les stocke, exécute une analyse visuelle IA par fenêtres vers un état d'investigation cumulatif par cas, et sert un tableau de bord en direct ainsi que des rapports exportables.
Tout s'exécute sur votre machine — le compagnon n'écoute que sur 127.0.0.1, les preuves
restent sur le disque, et le fournisseur d'IA est le vôtre.
Couche d'analyse post-détection. DFIR Companion n'est PAS un moteur de détection — il ingère les verdicts de Velociraptor, Security Onion, Chainsaw, Hayabusa, THOR, Cyber Triage, EDR/SIEM, les corrèle en une seule chronologie forensique, et synthétise les constats, le chemin de l'attaquant, les IOC et les rapports. La valeur réside dans le « et alors ? », pas dans la re-dérivation des alertes.
Cas de démonstration : https://dfir-companion-production.up.railway.app/dashboard?caseId=demo
Lab pratique : https://killercoda.com/dfir-companion/scenario/killercoda
Manuel utilisateur : https://hasamba.github.io/DFIR-Companion/manual/
companion/.env)Cas de démonstration : GlobalTech Industries — Précurseur de BEC & Ransomware, mai 2026.
Un cas entièrement pré-rempli que vous pouvez explorer sans importer de vraies preuves — constats, IOC, techniques MITRE, étiquettes/commentaires d'analyste, données d'exposition client et métadonnées de rapport sont tous pré-initialisés afin que chaque panneau du tableau de bord ait quelque chose à montrer.
Chargez-le en un clic — cliquez sur le bouton Cas de démonstration dans la barre d'outils du tableau de bord. Il fonctionne aussi avec l'EXE Windows portable (ni Node ni
npmrequis). Le bouton demande confirmation avant d'écraser si le cas existe déjà.Ou initialisez depuis la CLI (dev / Docker) :
cd companion && npm run seed-demo # creates case id "demo" npm run seed-demo -- --force # overwrite an existing demo case npm run seed-demo -- --case-id globaltech # use a custom idPuis ouvrez
http://127.0.0.1:4773/dashboardet connectez-vous au cas.
Résumé de cas généré par IA, narration minute par minute et rédaction du chemin de l'attaquant — de l'accès initial au déploiement du ransomware.

Événements analysés avec filtres de sévérité, étiquettes de triage, liens de détail par ligne et suivi des changements d'import (bannière de nouveaux événements avec diff dépliable).

Chaque événement jamais importé, avant filtrage par périmètre/sévérité — filtrez, étiquetez, marquez d'une étoile et promouvez des lignes vers la chronologie forensique analysée ; rien n'est supprimé, c'est une vue super-ensemble.

Graphique visuel des événements par actif (axe Y) et par temps (axe X), coloré par sévérité — faites glisser l'axe temporel pour filtrer la chronologie forensique sur une plage.

Constats générés par IA avec scores de confiance, étiquettes de triage d'analyste et liens vers les techniques MITRE ATT&CK ; suit ce qui a changé depuis l'exécution de synthèse précédente.

Événements regroupés par tactique MITRE ATT&CK — une catégorisation, pas une étape de kill-chain confirmée, dérivée de manière déterministe sans IA.

Questions DFIR standard auxquelles le cas synthétisé répond automatiquement (répondu / partiel / inconnu), chacune avec un pointeur de preuve ou une directive « collecter ceci ensuite ».

Liste de contrôle de remédiation actionnable dérivée automatiquement des constats et des prochaines étapes recommandées ; re-synchronisée à chaque exécution de synthèse tout en préservant le statut d'analyste, l'assigné et les échéances.

Quels hôtes/comptes portent l'attaque, notés par signal (événements pondérés par sévérité + techniques + IOC connectifs) plutôt que par volume, avec une fenêtre de périmètre suggérée.

Arbres de processus, mouvement latéral et lignage de fichiers assemblés en un seul graphe d'attaque causal. Dérivé de manière déterministe à partir des champs peuplés par les importateurs — pas d'IA, pas de coût, fonctionne hors ligne.

Qui s'est connecté où — comptes et hôtes liés à partir des événements de connexion de la super-chronologie, distinguant les connexions réussies, échouées et risquées (RDP/runas/netonly).

Canaux sortants périodiques trop réguliers pour être du trafic humain — une piste de chasse, pas un verdict, avec intervalle, gigue et nombre d'événements par candidat.

Indicateurs (IP · domaines · hachages · fichiers · processus · comptes) enrichis auprès de VirusTotal,
AbuseIPDB, ThreatFox et d'autres fournisseurs — badges de verdict, scores de détection, surlignages d'import
NEW et étiquettes de triage d'analyste.

Graphe interactif liant les hôtes et comptes victimes aux indicateurs qui ont touché chacun, plus une liste des hôtes et utilisateurs compromis connus.

drop/ d'un cas sont importés en arrière-plan, déplacés vers _processed/ ou _failed/, et consignés dans drop-log.txt ; un sous-dossier asset=<HOST> nomme l'hôte.evtx brut conservé octet pour octet, version du parseur et code de sortie dans la chaîne de custody, fail-closed, désactivé par défautDFIR_DEDUP=off)DFIR_OCR_SEARCH=off pour désactiver ; npm run ocr-index pour remplir)127.0.0.1 avec CORS + Private-Network-Access pour l'extension ; refuse les noms d'hôtes non reconnus, fermant les attaques de DNS-rebinding (DFIR_ALLOWED_HOSTS)Tous les importateurs sont déterministes (aucun appel IA), lisent les horodatages propres à l'artefact et étiquettent les événements avec le vrai nom de l'outil pour la corrélation multi-sources. Le même fichier peut être ré-importé sans dupliquer la chronologie.
object/action/properties, timestamp_ms en epoch-ms) — événements process/flow/logon/registry/module/file/thread | Preuve Info ; majoration LOLBin/ligne de commande encodée (IP publiques → IOCs) |
| Windows Event Log XML | Event Viewer « Save As XML », wevtutil qe /f:xml, Get-WinEvent … ToXml() (Security, Sysmon, System, tout canal) | Table Windows/Sysmon par EID |
| Chainsaw | JSON/JSONL de hunt EVTX (chainsaw hunt --json) ; exécutable directement sur des .evtx bruts via le lanceur d'outils | Niveau de règle Sigma correspondant |
| Hayabusa | json-timeline ou csv-timeline | Niveau de règle Sigma correspondant |
| Velociraptor | Tableau JSON, JSONL, ou map d'artefacts | Verdict Sigma/YARA ou par EID |
| THOR (Nextron) | Sortie de scan JSON-Lines | Niveau d'alerte THOR |
| Suricata / Zeek | eve.json, journaux JSON Zeek ; télémétrie → IOCs uniquement | Priorité d'alerte / sévérité de notice |
| Snort / Suricata IDS (fast) | Journal d'alerte monoligne alert_fast | Priority de la règle (1→High / 2→Medium / 3→Low) |
| YARA | Sortie de scan CLI yara -s -m (correspondances de règles + strings/meta) | Info→Medium par correspondance ; majoration sur les métadonnées score/threat_level de la règle |
| Journal d'accès web/proxy | Format de log combined Apache/Nginx/Squid (journal d'accès serveur web ou proxy direct) ; URL de requête, HTTP Referer et User-Agent capturés (secrets dans l'URL/Referer + UAs de scanner/bot/injection conservés comme événements + IOCs) | Info par défaut ; accès refusé (401/403/407) → Low ; clone/push git smart-HTTP → T1213 |
| Syslog firewall Cisco ASA | Messages Built/Teardown/Deny %ASA-#-######: | Info par défaut (télémétrie) ; Deny explicite → Low |
| Syslog (brut) | RFC 5424 (<PRI>1 …) + RFC 3164 (Mmm dd …) journaux d'hôtes Linux/Unix | Info par défaut (télémétrie) ; échec d'authentification ou PRI crit/alert/emerg → Low |
| Security Onion | Événements SOC Alerts/Hunt (ECS) ; poussés par l'extension ou une exportation API SOC | event.severity_label (label Suricata/SO) |
| SO-CRATES | Alertes Suricata + correspondances de fichiers YARA (/api/events) et détections Sigma (/api/sigma-alerts) ; poussés par l'extension ou une exportation brute | Priorité Suricata / niveau Sigma / correspondance YARA |
| Cyber Triage | Chronologie JSONL / JSON / CSV | Score d'élément Cyber Triage |
| M365 / Entra ID | UAL, journaux de connexion + audit Entra | Table de tradecraft BEC / riskLevel Entra |
| Okta | Export System Log | Table de tradecraft IdP (MFA désactivée, octroi admin, jeton API émis, session usurpée) — pas la note opérationnelle du fournisseur |
| Google Workspace | Audit Admin + login | Table de tradecraft IdP (2SV désactivée, rôle accordé, OAuth consenti, moniteur de messagerie ajouté) |
| Hindsight (navigateur) | Historique, téléchargements, interprétations Chrome/Edge/Brave (JSON ou CSV) | — (Événements Info : les artefacts de navigateur sont des preuves, pas des verdicts) |
| macOS | Journal unifié (log show --style json), événements de téléchargement LSQuarantine, attributs com.apple.quarantine, plists launchd, éléments de connexion (plist classique, .sfl2, BTM) | Enregistrement de quarantaine ↔ attribut de fichier ↔ visite navigateur ↔ démarrage de processus joints par identifiant ; un plist se lit comme une configuration, jamais comme une exécution |
| iLEAPP / ALEAPP | Artefacts d'extraction iOS + Android depuis les exports TSV LEAPP | — (Événements Info ; parseur générique indexé sur la colonne timestamp) |
| AWS CloudTrail | Records JSON, NDJSON, Athena | Table d'actions API (IAM/logging/S3/secrets) |
| GCP / Azure | Cloud Audit Logs, Azure Activity Log | Table d'actions (IAM/logging/secrets) |
| Kubernetes audit | Journal d'audit du serveur API (audit.k8s.io JSON-lines / EventList) | Table (verbe, ressource) — pod exec/attach T1609, accès secret T1552.007, changement RBAC T1098, pod privilégié T1610/T1611, accès anonyme T1078 |
| osquery | Journal de résultats de requêtes planifiées (columns différentiel + snapshot) | Télémétrie Info ; majoration conservatrice de tradecraft sur une colonne de ligne de commande |
| Plaso | CSV psort (dynamique + l2tcsv) | — (Événements Info) |
| Rapports de sandbox | CAPEv2 report.json, résumé Falcon Sandbox | Verdict d'échantillon + signatures comportementales |
| Forensique mémoire | Volatility 3 (-r json) + Rekall : pslist/pstree, netscan, malfind, cmdline, svcscan ; une enveloppe JSON d'exécution (commande, statut de sortie, stderr) s'importe à côté de l'export | code injecté malfind → High (T1055) ; listings → Info/Low ; une exécution à zéro ligne ou échouée dit ce qu'elle établit |
| Intact (VolWeb allégé) | Tables de plugins memory_payload.json + yarascan_results.jsonl | Même mapping de plugins ; correspondances YARA mémoire → Low, un cluster dense multi-règles → Info ; plafonds de lignes divulgués |
| TheHive | Export JSON de case/alerte, liste d'observables (TheHive 5) | Sévérité TheHive 1–4 ; MITRE depuis les tags taggés ATT&CK |
| Email | .eml (RFC 2822), .msg au mieux | Échec SPF/DKIM/DMARC → heuristiques d'usurpation d'expéditeur (T1566 Phishing) |
| Historique shell | .bash_history / .zsh_history (HISTTIMEFORMAT bash #epoch + historique étendu zsh) | Info par défaut ; majoration conservatrice sur tradecraft (reverse shell, download-and-exec, accès aux identifiants, altération de logs/historique, SSH latéral) |
| Persistance Linux | Clés autorisées SSH, cron, unités systemd, profils shell, listings SUID et PATH depuis une seule collecte | Charges utiles modifiables par tous, root exécutant des fichiers modifiables par l'utilisateur, interpréteurs setuid ; rien n'est noté pour sa simple existence |
| Linux auditd | Enregistrements bruts audit.log / ausearch, tables aureport | Table par type d'enregistrement (connexions, gestion de comptes, sudo, SELinux, altération d'audit) |
| systemd journald | journalctl -o json / -o json-pretty | PRIORITY syslog + majorations de tradecraft (sshd, sudo, useradd) |
| sysdig / Falco | JSON d'alerte Falco, JSON d'événement sysdig -j | Priorité de règle Falco ; syscalls bruts → télémétrie Info |
| Wazuh | alerts.json / NDJSON, ou export API (GET /security/events) | rule.level (≥13 Critical, ≥10 High, ≥7 Medium) |
| CSV | Exports Velociraptor / EDR | — |
| Journaux génériques | Firewall, syslog, VPN ; lignes répétitives → motifs comptés | Trié par IA |Notation déterministe du tradecraft — Les lignes de commande Windows/Sysmon, ECAR et mémoire sont notées selon des règles issues de plus de 110 intrusions réelles (The DFIR Report, Huntress) : tradecraft à haute confiance → High avec sa technique ATT&CK (désactivation de Defender, inhibition de la récupération, dump d'identifiants, tunnels inverses, Impacket, RMM/C2, exfiltration cloud …), double usage → Medium ; la découverte pure est taggée mais jamais escaladée.
runas /netonly) → Medium$SI/$FN comme probable timestomping → Mediumrclone/restic/megasync/megacmd dans PrefetchZone.Identifier est lue par rapport à Prefetch, aux démarrages de processus et aux enregistrements de présence du même fichier et n'est relevée que lorsque l'exécution est datée après elle ; une charge utile en flux caché est notée par son contenu, pas par son nomcmd.exe renommé, un outil déposé)nltest, Get-AD*, ntdsutil … ifm et similaires sont extraits des enregistrements 4104/4103 avec leurs techniquesssl/x509, Suricata tls) devient une ligne par relation et par certificat ; les réponses DNS sont jointes aux connexions ultérieures du même client dans le TTL ; les chaînes de requêtes web ne se joignent que par des identifiants que les deux enregistrements portentDFIR_JEV_ENABLED)DFIR_SYNTH_ADVERSARY_HINTS)tags.yaml de style Timesketch) — un moteur de règles étiquette les événements, élève la sévérité et unit les techniques MITRE-enc, [Convert]::FromBase64String) ; extrait les IOC cachés ; affiche les blocs [Decoded]process_creation chassent aussi l'historique Sysmon / 4688POST /cases/:id/push (webhook SIEM, moniteur Velociraptor, scripts)DFIR_FORENSIC_MIN_SEVERITY + un override par affaire, la promotion contourne la porte, et les IOC sont toujours extraits de chaque événementDetectRaptor.Windows.Detection.MFT), sur les chronologies forensique et superj/k déplace un surlignage de ligne focalisée sur la Forensic Timeline, f étoile, i préremplit le formulaire manuel d'IOC, p épingle le constat cité, n ouvre un commentaire, ? affiche une antisèche ; activable dans Settings → General, activé par défautPUT /cases/:id/correlation-profileDFIR_SHODAN_KEY? à côté de l'engrenage des paramètres ouvre le manuel d'utilisation en ligne dans un nouvel ongletmanual, survivent à la ré-analyse)DFIR_CROSS_CASE=on/dfir findings, /dfir iocs malicious, /dfir ask … depuis le canal d'incident ; lier un canal à une affaire, allowlist de qui peut dépenser du budget IA (#235)/mobile) pour les constatations/chronologie/IOC avec verdicts ; app-shell hors ligne/cases/:id/present) pour les briefings de passation et les présentations à la direction : grandes cartes, navigation au clavier, avance automatique, filtre de gravité, image de marque du modèle de rapport ; export d'un diaporama HTML hors ligne autonome (#177)$0.00 fabriqué quand un fournisseur ne les rapporte pas)DFIR_MAX_EVENTS) — remplace le plafond de sécurité par défaut de 2000 événements par importDFIR_LOG_LEVEL ; debug trace l'IA/captures/OCR/anonymisationchoco install dfir-companion ; télécharge + vérifie la build portable + intègre l'extension de capture, données dans %LOCALAPPDATA%docker compose up ; preuves sur volume hôte, aucun backend IA intégrénpm run seed-demo pour initialiser le scénario GlobalTechreanalyze, synthesize, coverage, verify:ai, clean-timelineLe Companion peut pointer les preuves d'une affaire vers des serveurs MCP que vous exécutez — un poste de travail SIFT, une machine REMnux, un service de référence de triage Windows — afin que les preuves soient analysées sur une machine qui dispose de l'outillage.
Il ne les atteint que via Claude Code. Le Companion n'est pas un client MCP : il ne détient aucune URL de serveur,
aucun jeton bearer, et ne lance aucun npx ou uvx de son propre chef. Claude Code est déjà configuré avec
vos serveurs et détient déjà leurs identifiants, c'est donc lui qui parle et le Companion le lui demande.
Toute cette fonctionnalité ne fonctionne que si :
DFIR_AI_CLAUDE_CODE_BIN si claude n'est pas dans son PATH.claude mcp add …, ou son fichier de config), et
claude mcp list les montre connectés.Il n'y a pas de repli. Si vous exécutez le Companion dans Docker, depuis l'AppImage, ou depuis la build portable Windows sans Claude Code à côté, les routes MCP vous le diront et rien d'autre.
Deux conséquences à connaître avant de vous y fier. Chaque appel MCP passe par un modèle, donc il consomme des tokens et n'est pas l'appel déterministe bit-à-bit qu'une requête JSON-RPC directe serait — le prompt en fait un transport (un outil, arguments exacts, sortie verbatim) mais un modèle est toujours au milieu. Et comme les serveurs proviennent de la configuration propre de Claude Code plutôt que d'une configuration générée, Claude Code démarre chaque serveur avec lequel il est configuré à chaque exécution, pas seulement celui qui est utilisé ; l'allowlist borne ce qui peut être appelé, pas ce qui est lancé.
Dans Settings → Tools, appuyez sur Refresh from Claude Code pour charger sa liste de serveurs, puis autorisez-en un et indiquez ce qu'il peut faire. Il n'y a rien à taper à part la politique — les noms de serveurs viennent de Claude Code lui-même, donc une faute de frappe ne peut pas vous laisser avec une entrée qui ne correspond silencieusement à rien.
POST /cases/<id>/mcp/<serverId>/run avec { tool, args, targetPath }. Placez <target> partout où l'outil
attend le chemin des preuves — il est remplacé par le chemin sur l'hôte d'analyse après l'exécution de la livraison,
donc l'argument que vous écrivez est l'argument que l'outil reçoit :```json
{ "tool": "run_command",
"args": { "command": ["vol.py", "-f", "", "pslist"] },
"targetPath": "imports/memory.raw" }
`targetPath` est résolu à l'intérieur du répertoire du cas ; tout ce qui se trouve en dehors est refusé. Pour un échantillon que le navigateur détient et pour lequel le serveur n'a aucun chemin, `POST /cases/<id>/mcp/<serverId>/run-upload` prend à la place `{ filename, dataBase64 }` et place d'abord les octets à l'intérieur du cas.
Les deux renvoient **202 avec un identifiant de tâche** plutôt que de bloquer. Une véritable exécution de Volatility dépasse tout délai d'attente raisonnable, donc l'exécution est une tâche en arrière-plan avec progression, un bouton d'annulation et une diffusion WebSocket `job_changed`. Le résultat entre dans le cas par la même chaîne d'importation que tous les autres outils — événements de chronologie, constats et IOC, avec un point de contrôle d'annulation — de sorte que rien dans la lecture du résultat ne diffère d'une importation ordinaire. La sortie structurée est acheminée vers l'importateur correspondant ; la prose non structurée passe par le chemin de journal générique plutôt que d'être rejetée.
Un outil qui signale son propre échec fait échouer la tâche au lieu d'être ingéré : un message d'erreur est un diagnostic, pas un artefact, et le classer dans la chronologie le ferait passer pour une preuve.
### Aperçu avant importation
**Activé par défaut**, et il vaut la peine de le laisser activé. Un serveur MCP renverra des données de référence aussi volontiers que des preuves — demandez à SIFT quels outils il possède et vous obtenez un inventaire JSON structurellement identique à une table Volatility : un tableau d'objets sans horodatages. Aucun détecteur ne peut les distinguer, donc les importateurs font ce pour quoi ils sont conçus et extraient chaque chemin qu'il contient comme indicateur de fichier. Une seule liste de capacités représente quelques dizaines d'IOC que le cas n'a jamais voulus.
Avec l'aperçu activé, l'exécution récupère la sortie et s'arrête. Vous voyez les octets, la taille et le type sous lequel il *importerait*, et vous choisissez. Approuver ingère **exactement les octets déjà récupérés** — cela ne relance jamais l'outil, donc une exécution Volatility de vingt minutes coûte vingt minutes une seule fois, et un outil avec des effets de bord les exécute une seule fois. Rejeter jette la sortie et le cas reste intact.
Envoyez `preview: true` lors de l'exécution pour l'utiliser depuis l'API, puis `GET`, `POST …/import` ou `DELETE` sur `/cases/<id>/mcp/preview/<jobId>`.
Rien ici ne remplace le jugement sur ce qu'il faut exécuter, et importer sans aperçu n'est pas dangereux — chaque importation MCP pousse un point de contrôle d'annulation, donc une exécution qui s'avère être du bruit est à un clic d'être annulée.
### Ce que l'utilisation d'un serveur accorde
**Par défaut, tout ce que le serveur offre.** C'est délibéré : Claude Code vous permet déjà d'appeler n'importe quel outil sur n'importe quel serveur que vous avez configuré, donc exiger que vous les réénumériez ici aurait été plus strict que votre propre usage quotidien — et un second endroit pour décrire le même serveur.
Il vaut la peine de savoir ce que « tout » inclut. Certains serveurs exposent des outils fins — `check_service`, `check_autorun`, un par question. D'autres exposent un seul **exécuteur de commandes** qui exécute tout ce que vous lui donnez : le `run_command` de SIFT déclare pouvoir exécuter « la plupart des outils installés sur SIFT … y compris curl, wget, dd, fdisk et python3 », et le `run_tool` de REMnux prend un pipeline shell entier. Utiliser un tel serveur depuis le Companion signifie l'exécution de commandes sur cet hôte — raisonnable sur un réseau forensique isolé, où les machines d'analyse sont les vôtres et où les preuves sont déjà sur votre LAN, et déraisonnable partout ailleurs.
Deux listes **optionnelles** le restreignent quand vous le souhaitez :
| Paramètre | S'applique à | Vide signifie |
|---|---|---|
| **Restreindre aux outils** | chaque appel | tous les outils que le serveur offre |
| **Restreindre aux commandes** | les appels portant un argument de commande | aucune restriction de commande |
Les commandes sont mises en correspondance **par nom de base**, donc `grep` et `/usr/bin/grep` sont une seule règle. Chaque étape d'un pipeline est vérifiée, pas seulement la première — `oledump.py s.doc | curl -T - http://elsewhere` nécessite que `oledump.py` et `curl` soient autorisés. Une commande utilisant une substitution shell (`$(…)`, accents graves, `${…}`) est refusée d'emblée, car ce qu'elle exécuterait ne peut être connu à l'avance.
**Ce que la liste de commandes ne fait pas.** Elle borne *quels* binaires s'exécutent, jamais ce qu'un binaire autorisé peut faire — autoriser `dd` autorise l'écriture vers tout chemin accessible en écriture à l'utilisateur de ce serveur ; autoriser `python3` autorise du code arbitraire. Elle se base aussi sur des noms de paramètres bien connus (`command`, `cmd`, `argv`), donc un serveur nommant son paramètre de commande de manière inhabituelle n'est pas intercepté. Elle existe pour aider un opérateur qui veut restreindre son propre accès, non pour contenir un serveur qu'il n'aurait pas dû configurer en premier lieu.
### Acheminer les preuves vers le serveur
MCP n'a pas de primitive de transfert de fichiers et une image mémoire de plusieurs gigaoctets ne peut pas voyager dans un argument d'outil, donc le fichier doit déjà se trouver quelque part où le serveur peut l'ouvrir. Cette partie reste le travail du Companion — Claude Code ne peut pas déplacer une image vers une machine d'analyse. Chaque serveur choisit l'une de deux routes :
**`remote-path`** (par défaut) — les preuves sont déjà visibles par l'hôte d'analyse via un montage partagé. Définissez un préfixe local et un préfixe distant et le chemin est réécrit (`/srv/cases/…` → `/mnt/dfir/…`) ; laissez les deux vides lorsque le montage est au même chemin des deux côtés. Rien n'est copié.
**`scp`** — le Companion pousse le fichier vers un répertoire de staging, l'outil s'exécute, et la copie mise en staging est supprimée ensuite. Configurez `host`, `remoteDir`, éventuellement `user`, `port` et `identityFile`.
Quatre choses à savoir avant de choisir `scp` :
- **La clé d'hôte doit déjà être approuvée.** `BatchMode` est activé et `StrictHostKeyChecking` n'est *pas* désactivé, donc un hôte inconnu échoue avec `Host key verification failed` plutôt que de faire confiance à ce qui a répondu à l'adresse. Connectez-vous d'abord une fois à la main (ou ajoutez la clé à `known_hosts`). C'est délibéré : accepter silencieusement une clé non vérifiée remettrait les preuves à quiconque détient l'IP.
- **L'authentification est uniquement par clé.** `BatchMode` signifie que ssh ne demande jamais, donc un hôte avec mot de passe uniquement ne peut pas fonctionner. Pointez `identityFile` vers une clé sans phrase de passe, ou chargez-la dans un agent accessible au processus serveur.
- **Il n'y a ni progression ni reprise.** Une copie de 16 Go est opaque jusqu'à ce qu'elle se termine ou échoue, et une connexion interrompue signifie recommencer. Le transfert est annulable et dispose de son propre délai d'attente d'une heure, distinct du délai d'attente des appels d'outils.
- **L'hôte, l'utilisateur et le répertoire distant sont restreints à un jeu de caractères conservateur** (lettres, chiffres, point, tiret, underscore, et `/` pour le répertoire). `user@host` parvient à ssh sans guillemets, donc tout ce qui a une signification shell est refusé au moment de l'enregistrement plutôt qu'au moment du transfert. Le nom de fichier mis en staging est dérivé du nom de la preuve et assaini de la même manière.
L'une ou l'autre route enregistre un **événement `transferred` de chaîne de possession** nommant la destination, de sorte qu'un dossier de cas montre que les preuves ont quitté cette machine, quand, et vers où. Un transfert qui échoue n'enregistre rien — la chaîne ne revendique jamais une copie qui n'a pas eu lieu.
### Enquêtes MCP en langage naturel
Un seul appel d'outil ne peut pas suivre un fil. « Enquêter sur ce dump » veut une boucle — exécuter pslist, remarquer quelque chose, pivoter vers malfind — et c'est ce que fait le mode agentique : il laisse Claude Code piloter contre le serveur que vous avez autorisé, puis fusionne ce qu'il rapporte. C'est le flux de travail MCP principal dans le tableau de bord : écrivez l'objectif en langage naturel, sélectionnez ou naviguez vers les preuves, choisissez l'application MCP, et appuyez sur **Investigate**. Les noms d'outils et les arguments JSON ne sont disponibles que dans la section avancée d'appel manuel.
`POST /cases/<id>/mcp/agent` avec `{ prompt, servers?, targetPath?, preview? }`, ou `POST /cases/<id>/mcp/agent-upload` avec `{ prompt, servers, filename, dataBase64, preview? }`.
**Lisez ceci avant d'autoriser un serveur.** Dans une exécution manuelle, le Companion contrôle chaque appel, donc chaque appel passe les listes d'autorisation d'outils *et* de commandes. En mode agentique, ce n'est pas le cas : `claude` parle directement aux serveurs. Seule la liste d'autorisation d'outils survit, sous la forme `--allowed-tools`. **La liste d'autorisation de commandes ne peut pas être appliquée.** Laisser un agent utiliser un outil exécuteur de commandes accorde donc à une boucle autonome la capacité de choisir ses propres lignes de commande sur cet hôte.
Autoriser et activer un serveur MCP dans Companion est la frontière de permission pour ce mode. La restriction d'outils du serveur s'applique toujours. Une restriction de commandes ne peut pas contraindre la boucle autonome ; elle s'applique uniquement aux appels manuels avancés.
Ce que le mode garantit encore : une restriction d'outils explicite est transmise outil par outil ; une restriction vide autorise délibérément tous les outils que ce serveur expose. Les paramètres de projet/locaux, les fichiers `CLAUDE.md` et les hooks sont exclus, et l'exécution est limitée en nombre de tours. Les paramètres utilisateur de Claude Code restent activés car c'est là que résident ses connexions aux serveurs MCP.
La réponse de l'agent est validée par schéma et dépouillée de toute revendication de provenance avant d'être fusionnée — tout ce qu'il a vu provenait de la sortie d'outils, qui n'est pas fiable. Il ne lui est jamais demandé de résumé de cas, donc une exécution ajoute des constats, des IOC et des événements sans réécrire vos conclusions. L'aperçu fonctionne ici aussi, et compte davantage : une boucle autonome décide elle-même de ce qu'elle rapporte.
L'enquête est plafonnée à 40 tours. Si Claude Code consomme ce budget en utilisant des outils, Companion reprend la même session une fois avec tous les outils désactivés et lui demande de ne rapporter qu'à partir des preuves déjà collectées. Cela préserve la frontière de sécurité sans perdre une enquête terminée simplement parce que son JSON final aurait été le tour suivant.
### Identifiants
Il n'y en a aucun à configurer ici. Les jetons Bearer, les en-têtes et les transports résident tous dans la propre configuration MCP de Claude Code, qui est le seul endroit qui les détient. Le Companion stocke un *nom* de serveur, une liste d'autorisation et un bloc de livraison — rien qui lui permettrait de se connecter à quoi que ce soit par lui-même.
Une mise en garde si vous allez chercher : `claude mcp list` affiche la ligne de commande complète de chaque serveur, laquelle, pour une entrée `mcp-remote`, inclut le jeton Bearer en clair. Le Companion n'extrait que le nom et le verdict de santé de cette sortie et ne stocke, journalise ni n'affiche jamais le reste — mais faites attention à l'endroit où vous exécutez cette commande vous-même.
## Organisation du dépôt```
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 │ └─────────────────────┘ └───────────────────────────────────────┘
**Analyse en deux phases :** un modèle de vision léger lit chaque capture d'écran dans la
chronologie forensique ; un modèle plus puissant effectue l'unique appel de synthèse holistique (constats, MITRE,
chemin de l'attaquant, questions). Configurez les deux via `.env` — voir `companion/README.md`.
## Démarrage rapide
> **Prérequis :** [Node.js](https://nodejs.org/) **22.19 ou version ultérieure** (qui est livré avec `npm`).
> Vérifiez avec `node --version`. Tout ce qui suit utilise `npm`, donc aucun autre runtime n'est nécessaire.
> Le stockage de cas indexé utilise le module intégré `node:sqlite`, donc les versions plus anciennes de Node ne peuvent pas ouvrir
> les cas. La version portable embarque un runtime compatible.
1. **Companion** (le serveur) : ```
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)
Extension (capture) :
Le plus simple : installez directement depuis le
Chrome Web Store.
Sur Firefox 140+, téléchargez dfir-capture-extension-firefox-*.zip depuis la
dernière version et décompressez-le.
Ou compilez depuis les sources : ``` 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
Sur Firefox, chargez-le depuis about:debugging#/runtime/this-firefox → Load Temporary Add-on…
et sélectionnez le fichier manifest.json (Chrome demande le dossier ; Firefox non). Firefox
supprime les modules temporaires au redémarrage, donc répétez cette opération à chaque session — il n'y a pas encore de fiche AMO,
donc le zip de release n'est pas signé et ne peut pas être installé de façon permanente.
Ce qu'il collecte, puisqu'un chargement temporaire ne demande jamais. Firefox n'affiche sa notice de collecte de données que pour un module signé installé normalement ;
about:debuggingaccorde tout silencieusement. L'extension déclare l'activité de navigation (une capture contient l'URL et le titre de l'onglet) et le contenu du site web (la capture d'écran, et les lignes qu'un Push extrait). L'extension l'envoie à l'adresse compagnon que vous configurez et nulle part ailleurs ; ce que ce compagnon transmet ensuite — un modèle de vision lit les captures d'écran, une synthèse IA lit les lignes, l'enrichissement interroge des services de réputation — relève de la configuration propre du compagnon. Voir extension/PRIVACY.md.
Le popup ne fait que s'attacher à un dossier existant — vous créez les dossiers dans le tableau de bord.
http://127.0.0.1:4773/dashboard, cliquez sur + New case pour créer votre dossier (il
se connecte automatiquement). Puis, dans le popup de l'extension, choisissez ce dossier dans la liste déroulante Case
(Refresh cases s'il n'apparaît pas encore) et cliquez sur Start. Naviguez dans vos preuves —
le tableau de bord se met à jour en direct.Vous mettez à jour une copie existante ? Après
git pull, relanceznpm installdans les deuxcompanion/etextension/— de nouvelles fonctionnalités peuvent ajouter des dépendances (par ex. la rédaction OCR des captures d'écran a ajoutétesseract.js). Puis redémarreznpm run dev(le code serveur se charge une fois au démarrage).
La configuration complète, les points de terminaison HTTP, l'arborescence des dossiers de cas et le modèle d'analyse sont documentés dans companion/README.md.
Exécutez l'ensemble — serveur compagnon + tableau de bord + l'extension de navigateur — dans un seul conteneur.
Aucun Ollama ni LiteLLM n'est inclus ; pour l'IA, vous pointez DFIR_AI_* vers n'importe quel point de terminaison
compatible OpenAI (un modèle que vous hébergez, un fournisseur distant, ou un Ollama/LiteLLM que vous exécutez séparément). Avec l'IA
laissée non configurée, le conteneur effectue toujours la capture complète et tous les importateurs déterministes.
Prérequis : Docker avec le plugin Compose (
docker compose version).
Localhost uniquement par conception : le conteneur se lie à 0.0.0.0 en interne, mais Compose publie le
port sur 127.0.0.1 sur votre hôte — ainsi le tableau de bord n'est jamais exposé sur votre réseau.
Ou récupérez l'image préconstruite depuis GHCR au lieu de la construire : ``` docker compose pull && docker compose up -d
2. **Charger l'extension** (capture). Le conteneur écrit l'extension pré-construite et décompressée dans
`./addon` au premier démarrage. Dans Chrome/Comet, ouvrez `chrome://extensions`, activez le **mode
développeur**, cliquez sur **Charger l'extension non empaquetée**, et sélectionnez **`./addon/dist`** (un fichier
`dfir-companion-extension.zip` empaqueté y est également déposé).
3. Ouvrez `http://127.0.0.1:4773/dashboard`, cliquez sur **+ New case**, puis sélectionnez ce cas dans la
fenêtre contextuelle de l'extension et cliquez sur **Start**.
**Données et configuration :**
- Les preuves et l'état des cas persistent dans **`./cases`** sur l'hôte (volume monté) — ils survivent aux
redémarrages et aux reconstructions d'images.
- Configurez via le bloc `environment:` dans [`docker-compose.yml`](https://github.com/hasamba/dfir-companion/blob/master/docker-compose.yml), ou
décommentez `env_file: - .env` pour utiliser un fichier `.env` (copiez `companion/.env.example`).
- Pour atteindre un point de terminaison IA exécuté sur l'hôte, utilisez `http://host.docker.internal:<port>/v1`
(sur Linux sans Docker Desktop, décommentez également la ligne `extra_hosts` dans le fichier compose).
## Windows (Chocolatey)
Installez la version portable Windows avec [Chocolatey](https://chocolatey.org/) — Node.js
non requis. Dans un shell élevé :```
choco install dfir-companion
dfir-companion # → http://127.0.0.1:4773/dashboard
choco upgrade dfir-companion récupère la version suivante ; choco uninstall dfir-companion
supprime le binaire et le shim PATH. L'installateur télécharge le même zip portable publié sur
la page Releases et vérifie son
SHA256.
Vos données résident dans votre profil utilisateur, et non dans le répertoire d'installation
appartenant à l'administrateur : les dossiers dans
%LOCALAPPDATA%\DFIR-Companion\cases et la configuration dans %LOCALAPPDATA%\DFIR-Companion\.env
(initialisée à partir de l'exemple ; modifiez-la pour les clés AI / threat-intel — toutes
optionnelles). La désinstallation conserve ce dossier afin que les preuves ne soient jamais
supprimées. Aucune règle de pare-feu n'est créée — le serveur se lie uniquement à 127.0.0.1.
L'extension de capture est fournie sur le disque à l'emplacement %LOCALAPPDATA%\DFIR-Companion\extension pour
une installation hors ligne (pratique sur les postes de travail isolés) — chargez-la via chrome://extensions →
Mode développeur → Load unpacked → ce dossier, ou installez-la depuis le Chrome Web Store une fois
publiée. Elle n'est pas installée automatiquement dans le navigateur.
Pas encore sur le dépôt communautaire Chocolatey ? Tant qu'elle n'y est pas publiée, récupérez le
dfir-companion.<version>.nupkgdepuis la release et exécutezchoco install dfir-companion --source .depuis son dossier. Le packaging se trouve danspackaging/chocolatey/.
Téléchargez dfir-companion-<version>-x86_64.AppImage depuis la
page Releases, puis :```
chmod +x dfir-companion--x86_64.AppImage
./dfir-companion--x86_64.AppImage # → http://127.0.0.1:4773/dashboard
Aucun Node requis — il embarque le serveur, le tableau de bord et l'outillage d'images. **Vos données résident dans le
répertoire depuis lequel vous le lancez :** `cases/` (preuves + état) et un `.env` facultatif (config IA / threat-intel)
sont créés/lus à côté de l'endroit où vous lancez l'AppImage. Remplacez avec `DFIR_CASES_ROOT`
(chemin absolu) et `DFIR_ENV_FILE` (chemin absolu vers un fichier de config).
### Où résident les données
| Installation | Cas + état | Config (`.env`) |
| ---------------------- | ------------------------------------- | ------------------------------------- |
| Source / `npm run dev` | `companion/cases/` | `companion/.env` |
| EXE Windows portable | `cases/` à côté de l'EXE | `.env` à côté de l'EXE |
| Windows (Chocolatey) | `%LOCALAPPDATA%\DFIR-Companion\cases` | `%LOCALAPPDATA%\DFIR-Companion\.env` |
| AppImage Linux | `$PWD/cases` (répertoire de lancement) | `$PWD/.env` (ou `DFIR_ENV_FILE`) |
| Docker / Compose | volume `./cases` monté | `environment:` / `--env-file` |
Tous les emplacements sont remplaçables avec `DFIR_CASES_ROOT` (chemin absolu).
## Variables d'environnement (`companion/.env`)
Tout le comportement du companion est configuré via des variables d'environnement (`companion/.env` ou le shell). Copiez `companion/.env.example` pour commencer — il contient des commentaires en ligne pour chaque variable.
### Cœur
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_CASES_ROOT` | `./cases` | Emplacement du dossier de cas ; les chemins relatifs sont résolus par rapport à `companion/` |
| `DFIR_PORT` | `4773` | Port du serveur (doit correspondre à l'extension et au tableau de bord) |
| `DFIR_HOST` | `127.0.0.1` | Interface d'écoute. Une écoute non-loopback non authentifiée est refusée ; Docker Compose documente son exception limitée au loopback de l'hôte |
| `DFIR_MAX_BODY_MB` | `256` | Taille maximale d'upload en Mo ; augmentez si de gros exports SIEM/EDR échouent avec HTTP 413 |
| `DFIR_ALLOWED_ORIGINS` | _(aucun)_ | Origines navigateur supplémentaires autorisées à appeler l'API, séparées par des virgules. L'extension de capture, le loopback et toute origine servie par le companion lui-même sont toujours de confiance, donc localhost/LAN/Docker ne nécessitent aucun réglage ; toute autre origine web est refusée. Les appelants n'envoyant pas d'`Origin` (curl, scripts, Velociraptor) ne sont pas affectés. Nécessaire lorsque le tableau de bord est servi depuis un **nom d'hôte** — un reverse proxy ou un déploiement hébergé |
| `DFIR_ALLOWED_HOSTS` | _(aucun)_ | Noms d'hôtes supplémentaires auxquels ce companion répond, séparés par des virgules. Le loopback et les adresses IP nues sont toujours acceptés, donc localhost, Docker et l'accès au tableau de bord sur le LAN à `http://192.168.1.50:4773` ne nécessitent aucun réglage. Tout **nom** non listé est refusé — c'est ce qui bloque le DNS rebinding (un site hostile pointant son propre domaine vers votre machine). Définissez ceci lorsqu'un reverse proxy transmet un `Host` différent de l'origine que vous avez mise dans `DFIR_ALLOWED_ORIGINS` |
| `DFIR_ALLOWED_HOST_SUFFIXES` | _(aucun)_ | Identique à ci-dessus mais mis en correspondance sur un suffixe de domaine, par ex. `.lab.example.com`, pour les plateformes qui génèrent un nouveau nom d'hôte par session. La correspondance se fait sur une frontière de label, donc `.acme.com` ne correspond jamais à `evilacme.com` |
| `DFIR_LOG_LEVEL` | `info` | Verbosité des logs (`debug`/`info`/`warn`/`error`). Diffusé vers la console + `logs/session-<time>.log` (global) + `cases/<id>/logs/session-<time>.log` (par cas). `debug` trace les appels IA, les captures, l'OCR, l'anonymisation, l'enrichissement. Modifiable à chaud (sans redémarrage) via Settings → Log verbosity |
| `DFIR_LOG_DIR` | `logs/` à côté de la racine des cas | Dossier pour le log de session **global**. Les chemins relatifs sont ancrés à `companion/`. Les logs par cas restent toujours dans le dossier du cas |
### Authentification (déploiement d'équipe facultatif)
`DFIR_AUTH_MODE=team` active la connexion OIDC/locale, les sessions navigateur sécurisées, les rôles par cas et
les identités de service limitées au cas. Les paramètres d'authentification et de fournisseur d'identité sont des contrôles
de sécurité du déploiement : configurez-les dans `.env` ou un coffre de secrets, puis redémarrez. Consultez le
[guide Team Accounts and Case Roles](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/team-authentication.md) pour la
liste complète des variables, la configuration HTTPS, l'amorçage du premier admin, la matrice des rôles, le token d'extension et
le modèle de processus à écriture unique.
### IA — extraction (requis pour activer l'analyse)
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_VISION_PROVIDER` | — | `openai` \| `openrouter` \| `ollama` \| `litellm` \| `gemini` \| `anthropic` \| `claude-code` ; non défini = capture uniquement |
| `DFIR_VISION_MODEL` | — | Id du modèle (par ex. `gpt-4o-mini`, `gemini-2.5-flash`) ; **doit prendre en charge la vision** pour l'extraction de captures d'écran |
| `DFIR_VISION_KEY` | — | Clé API du fournisseur ; laissez vide pour un proxy local sans auth ou pour `claude-code` (utilise votre abonnement CLI `claude` connecté à la place) |
| `DFIR_AI_CLAUDE_CODE_BIN` | `claude` dans le PATH | `claude-code` uniquement : chemin absolu vers le binaire `claude` s'il n'est pas dans le PATH |
| `DFIR_VISION_BASE_URL` | défaut du fournisseur | Remplace l'URL de base — pour un proxy LiteLLM local ou tout endpoint compatible OpenAI |
| `DFIR_AI_TIMEOUT_MS` | `900000` | Timeout par requête (ms) ; les fournisseurs CLI (claude-code, codex) ont besoin de minutes sur une grande timeline |
| `DFIR_AI_MAX_TOKENS` | `16000` | Tokens de complétion max ; trop bas tronque la synthèse, évite le 402 OpenRouter en cas de solde faible |
| `DFIR_AI_SYNTH_MAX_EVENTS` | `600` | Plafond d'événements forensiques envoyés à la synthèse ; les Critical/High obtiennent toujours une conclusion quoi qu'il arrive |
| `DFIR_REPORT_SYNTH_COVERAGE` | _(désactivé)_ | Définissez une valeur vraie pour ajouter une note de bas de page **§3.4 Synthesis coverage** au rapport — « considered N of M in-window events (K omitted: budget/filtered) », l'estimation de tokens, et combien d'omissions de haute sévérité le filet de sécurité a récupérées. La carte synth-meta du tableau de bord affiche toujours cette ligne ; ce flag contrôle uniquement si elle apparaît aussi dans le rapport exporté |
| `DFIR_REPORT_MODEL_PERF` | _(désactivé)_ | Définissez une valeur vraie pour ajouter une note de bas de page **§3.5 Model performance** au rapport — le modèle de synthèse, le nombre de conclusions par rapport à combien le filet de sécurité a dû en ajouter, les tentatives de parsing, et (quand un second avis a été exécuté) la fréquence à laquelle `DFIR_AI_SECOND_OPINION_MODEL` était d'accord avec `DFIR_AI_MODEL`/`DFIR_AI_SYNTH_MODEL`. La carte synth-meta du tableau de bord affiche toujours ceci ; ce flag contrôle uniquement si elle apparaît aussi dans le rapport exporté |
| `DFIR_AI_CONTEXT_TOKENS` | `128000` | Fenêtre de contexte du modèle ; augmentez pour Claude/Gemini (200k/1M) afin d'envoyer plus par appel |
| `DFIR_VISION_IMAGE_DETAIL` | `high` | `high` \| `low` \| `auto` (OpenAI/OpenRouter) ; `high` découpe en tuiles à pleine résolution pour l'OCR de petits textes |
| `DFIR_AI_AUTO_SYNTHESIZE` | `on` | Re-synthétiser pendant la capture : `on` \| `off` |
| `DFIR_AI_AUTO_SYNTHESIZE_MS` | `8000` | Fenêtre de debounce avant le déclenchement de l'auto-synthèse (ms) |
| `DFIR_FLUSH_INTERVAL_MS` | `300000` | Vidage de sécurité des buffers de capture restants (ms) ; `0` désactive |
| `DFIR_ANONYMIZE` | `on` | Tokenise les IP/hôtes/utilisateurs/chemins des victimes avant les appels IA : `on` \| `off` |
| `DFIR_PRESIDIO_URL` | _(non défini)_ | Facultatif : URL de base d'un conteneur [Presidio](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/presidio.md) Analyzer auto-hébergé (par ex. `http://localhost:5002`) qui scanne le texte déjà masqué pour les noms et autres PII que les regex ne peuvent pas attraper. Non défini = fonctionnalité désactivée. |
| `DFIR_PRESIDIO_MIN_SCORE` | `0.6` | Seuil de confiance (0–1) pour les détections Presidio ; vide/non numérique revient au défaut, les valeurs hors plage sont bornées |
| `DFIR_PRESIDIO_TIMEOUT_MS` | `60000` | Budget pour une requête `/analyze` (les scans sont découpés en chunks ; chaque chunk reçoit le budget complet). Augmentez-le pour un analyseur lent ou partagé ; vide/non numérique/≤0 revient au défaut |
> Les variables de capture d'écran/vision ci-dessus (`DFIR_VISION_PROVIDER` / `DFIR_VISION_MODEL` / `DFIR_VISION_KEY` / `DFIR_VISION_BASE_URL` / `DFIR_VISION_IMAGE_DETAIL`) ont été renommées depuis le préfixe `DFIR_AI_*` ; les anciens noms `DFIR_AI_PROVIDER` / `DFIR_AI_MODEL` / `DFIR_AI_KEY` / `DFIR_AI_BASE_URL` / `DFIR_AI_IMAGE_DETAIL` fonctionnent toujours comme fallback déprécié (le nouveau nom l'emporte quand les deux sont définis).
**Claude Code** — utilise votre abonnement Claude connecté via le CLI `claude`, sans clé API ; gère
la vision + le texte (extraction de captures d'écran *et* synthèse). Nécessite le CLI `claude` installé et
`claude auth login` effectué sur l'hôte. Consomme les limites de débit de votre abonnement (une extraction intensive
peut les épuiser) ; le coût rapporté est équivalent-API, pas de votre poche. Settings → AI affiche un
statut de connexion (not installed / not connected / connected) avec une action Connect en un clic.
### IA — modèle texte (deux niveaux, facultatif)
La séparation est **vision vs texte** : `DFIR_VISION_MODEL` lit les captures d'écran (doit être multimodal) ; le modèle `DFIR_AI_SYNTH_*` fait **tout le travail textuel** — extraction CSV, triage de logs, synthèse, ask/explain. S'il n'est pas défini, le travail textuel réutilise `DFIR_VISION_MODEL`.
**Codex** — définissez `DFIR_AI_SYNTH_PROVIDER=codex` (également valide pour les fournisseurs velo / second-opinion)
pour exécuter le travail textuel via le **Codex CLI** OpenAI local (`codex exec`), en utilisant votre auth codex
ambiante — `codex login` ou `OPENAI_API_KEY`, **pas de `DFIR_AI_KEY`**. Codex est **texte uniquement** (il ne peut pas
lire les captures d'écran), donc associez-le à un fournisseur de vision pour l'extraction ; il envoie les données à OpenAI
(non local). Nécessite `@openai/codex` installé. `DFIR_AI_CODEX_BIN` facultatif pointe vers un
`codex` hors PATH. Settings → AI affiche un statut de connexion codex (not installed / not connected /
connected) avec une action Connect en un clic.
Recommandé : modèle de vision bon marché pour les captures d'écran, modèle de raisonnement fort pour le texte. N'économisez pas sur le modèle texte — un modèle faible échoue au triage de logs *silencieusement*, ne retournant aucun événement plutôt que de mauvais (`npm run eval:real` mesure exactement cela).
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_AI_SYNTH_PROVIDER` | = `DFIR_VISION_PROVIDER` | Fournisseur pour le travail textuel (CSV/log/synthèse) |
| `DFIR_AI_SYNTH_MODEL` | = `DFIR_VISION_MODEL` | Id du modèle texte — extraction CSV/log + synthèse (par ex. `gpt-4o`, `gemini-2.5-pro`, `claude-sonnet-4-6`) |
| `DFIR_AI_SYNTH_KEY` | = `DFIR_VISION_KEY` | Clé API du modèle texte |
| `DFIR_AI_SYNTH_BASE_URL` | = `DFIR_VISION_BASE_URL` | URL de base de la synthèse |
### IA — modèle de chasse Velociraptor (facultatif)
Un modèle dédié utilisé **uniquement** pour générer les chasses VQL Velociraptor (les fonctionnalités *Suggest Velociraptor hunts* / *Fleet Hunts*), séparé de l'extraction/synthèse/OCR — beaucoup de modèles ratent le VQL. Également modifiable dans **Settings → AI**.
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_AI_VELO_PROVIDER` | `openrouter` | Fournisseur pour la génération de chasses VQL |
| `DFIR_AI_VELO_MODEL` | `anthropic/claude-haiku-4.5` | Id du modèle pour la génération de chasses VQL |
| `DFIR_AI_VELO_KEY` | = `DFIR_VISION_KEY` | Clé API (réutilise la clé principale si vide) |
| `DFIR_AI_VELO_BASE_URL` | = `DFIR_VISION_BASE_URL` | Remplacement de l'URL de base |
### IA — prompts personnalisés (facultatif)
Chaque prompt a deux formes de remplacement (ordre de priorité) : `DFIR_AI_<NAME>_PROMPT` (texte en ligne, lu au démarrage) et `DFIR_AI_<NAME>_PROMPT_FILE` (chemin vers un fichier, relu à chaque appel — modifiez-le et il s'applique immédiatement). `npm run prompts:eject` écrit les défauts intégrés comme point de départ.
| Nom du prompt | Token `<NAME>` |
|---|---|
| Extraction par capture d'écran | `SYSTEM` |
| Triage d'import CSV | `CSV` |
| Triage d'import de logs | `LOG` |
| Synthèse holistique | `SYNTH` |
| Q&R sur le cas | `ASK` |
| Résumé exécutif | `EXEC` |
| Timeline narrative | `NARRATIVE` |
| Chasses de flotte suggérées | `HUNTS` |
| Chasses de playbook suggérées | `PBHUNTS` |
| Hypothèses de lacunes de timeline | `GAPHYP` |
| Query Translator (NL → requête) | `QUERYXLATE` |
### Enrichissement threat-intel (facultatif — désactivé par défaut)
Ajoutez une clé pour activer ce fournisseur. Tous les fournisseurs externes sont opt-in par cas depuis le tableau de bord.
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_VT_KEY` | — | Clé API VirusTotal (hash / IP / domaine / URL) |
| `DFIR_HUNTINGCH_KEY` | — | Auth-Key abuse.ch pour Hunting.ch (MalwareBazaar · ThreatFox · URLhaus · YARAify) ; revient à `DFIR_MB_KEY` |
| `DFIR_MB_KEY` | — | Clé abuse.ch héritée — alimente Hunting.ch ; préférez `DFIR_HUNTINGCH_KEY` |
| `DFIR_ABUSEIPDB_KEY` | — | Clé API AbuseIPDB (réputation IP) |
| `DFIR_CROWDSTRIKE_CLIENT_ID` | — | ID client OAuth2 CrowdStrike Falcon TI |
| `DFIR_CROWDSTRIKE_CLIENT_SECRET` | — | Secret OAuth2 CrowdStrike (nécessite *Indicators: Read* + *MalQuery: Read*) |
| `DFIR_CROWDSTRIKE_CLOUD` | `us-1` | Cloud du tenant : `us-1` \| `us-2` \| `eu-1` \| `gov-us-1` \| `gov-us-2` |
| `DFIR_CROWDSTRIKE_BASE_URL` | depuis le cloud | URL de base explicite de l'API (remplace `DFIR_CROWDSTRIKE_CLOUD`) |
| `DFIR_ROCKYRACCOON_KEY` | — | Clé RockyRaccoon pour la prévalence des processus Windows / LOLBIN / ATT&CK |
| `DFIR_MISP_URL` | — | URL de l'instance MISP — URL + clé requises pour l'enrichissement et le push |
| `DFIR_MISP_KEY` | — | Clé d'auth de l'API MISP |
| `DFIR_MISP_CA` | — | Bundle CA PEM pour un MISP à CA interne (la vérification reste activée) |
| `DFIR_MISP_INSECURE` | — | `=1` pour ignorer la vérification TLS (labo uniquement) |
| `DFIR_MISP_DISTRIBUTION` | `0` | Distribution des nouveaux événements : `0`=org, `1`=community, `2`=connected, `3`=all |
| `DFIR_MISP_ANALYSIS` | `1` | État d'analyse des nouveaux événements : `0`=initial, `1`=ongoing, `2`=complete |
| `DFIR_MISP_TIMELINE_LIMIT` | `5000` | Nombre max d'événements de timeline forensique par push ; au-delà du plafond les plus sévères sont conservés et le push avertit |
| `DFIR_YETI_URL` | — | URL de l'instance YETI — URL + clé requises |
| `DFIR_YETI_KEY` | — | Clé API YETI |
| `DFIR_YETI_CA` | — | Bundle CA PEM pour un YETI à CA interne |
| `DFIR_YETI_INSECURE` | — | `=1` pour ignorer la vérification TLS (labo uniquement) |
| `DFIR_OPENCTI_URL` | — | URL de l'instance OpenCTI — URL + clé requises (hash/ip/domain/url) |
| `DFIR_OPENCTI_KEY` | — | Token API OpenCTI |
| `DFIR_OPENCTI_CA` | — | Bundle CA PEM pour un OpenCTI à CA interne |
| `DFIR_OPENCTI_INSECURE` | — | `=1` pour ignorer la vérification TLS (labo uniquement) |
| `DFIR_OPENCTI_MALICIOUS_SCORE` | `75` | Seuil `x_opencti_score` pour un verdict malveillant |
| `DFIR_RDAP_URL` | `https://rdap.org` | Base WHOIS-over-RDAP (sans clé ; bootstrap IANA vers le RIR propriétaire) |
| `DFIR_GEOIP_URL` | `https://ipinfo.io/{ip}/json` | Template d'URL GeoIP (HTTPS sans clé ; `{ip}` substitué ; le parseur tolère aussi ip-api.com + ipwho.is) |
| `DFIR_GEOIP_KEY` | — | Clé GeoIP facultative (remplit `{key}`, sinon ajoutée comme `?token=`) pour un backend payant/auto-hébergé |
| `DFIR_SHODAN_KEY` | — | Clé API Shodan — alimente aussi l'enrichisseur IP de lookup d'hôte Shodan (partagé avec l'exposition client) |
| `DFIR_HASHLOOKUP_URL` | `https://hashlookup.circl.lu` | Base CIRCL hashlookup (lookup de fichiers connus sans clé pour les IOC de hash) ; remplacez pour un miroir auto-hébergé / air-gapped |
| `DFIR_ENRICH_DELAY_MS` | `1500` | Throttle entre les lookups (ms) |
| `DFIR_ENRICH_JITTER_MS` | `0` | Jitter aléatoire ± ajouté à l'attente inter-appels (ms) ; étale les exécutions alignées/parallèles pour qu'elles ne frappent pas toutes la fenêtre de rate-limit d'un fournisseur en même temps |
| `DFIR_ENRICH_RETRIES` | `2` | Tentatives de retry pour un appel fournisseur qui rencontre un 429, en honorant `Retry-After` quand le fournisseur en envoie un, avant d'être compté comme erreur |
| `DFIR_ENRICH_RETRY_BACKOFF_MS` | `1000` | Backoff de base avant le premier retry 429 (double à chaque tentative, plafonné à 30s) quand le fournisseur n'a pas donné de `Retry-After` |
| `DFIR_ENRICH_MAX` | `100` | Nombre max d'IOC interrogés par lot d'enrichissement (hashes/IPs en premier) |
| `DFIR_ENRICH_MAX_BATCHES` | `20` | Combien de lots plafonnés un déclenchement d'enrichissement peut enchaîner. Un cas avec plus d'IOC que `DFIR_ENRICH_MAX` ne s'arrête plus au plafond : l'exécution sauvegarde, puis démarre le lot suivant là où elle s'est arrêtée, jusqu'à ce nombre. `1` restaure l'ancien comportement à exécution unique. Ce que le plafond laisse encore est rapporté dans la ligne de statut, pas silencieusement abandonné |
| `DFIR_ENRICH_HEALTH_TTL_MS` | `60000` | Cache du verdict up/down pour les fournisseurs auto-hébergés (ms) |
| `DFIR_ENRICH_HEALTH_POLL_MS` | `60000` | Intervalle de re-sonde pour les fournisseurs down ; `0` désactive le poller en arrière-plan |
### Exposition client (facultatif)
Vérifie les domaines/emails **de l'organisation victime elle-même** contre les bases de fuites — jamais les domaines adverses/IOC.
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_HIBP_KEY` | — | Clé API Have I Been Pwned |
| `DFIR_HIBP_USER_AGENT` | `DFIR Companion` | En-tête User-Agent HIBP |
| `DFIR_LEAKCHECK_KEY` | — | Clé API LeakCheck Pro |
| `DFIR_LEAKCHECK_DOMAIN_LIMIT` | `1000` | Nombre max d'enregistrements par recherche de domaine |
| `DFIR_DEHASHED_KEY` | — | Clé API DeHashed v2 |
| `DFIR_DEHASHED_BASE_URL` | défaut DeHashed | Remplace l'URL de base de l'API DeHashed |
| `DFIR_SHODAN_KEY` | — | Clé Shodan (domaine → hôtes exposés / ports / CVE ; pas de lookup d'email) |
| `DFIR_EXPOSURE_DELAY_MS` | `1500` | Throttle entre les lookups fournisseur (ms) |
### Push / import DFIR-IRIS (facultatif)
L'URL et la clé sont toutes deux requises pour activer. La même connexion alimente **Push to DFIR-IRIS** et
**Import from IRIS** (extraire les actifs/IOC/timeline d'un cas IRIS existant dans un cas).
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_IRIS_URL` | — | URL de l'instance IRIS |
| `DFIR_IRIS_KEY` | — | Clé API IRIS |
| `DFIR_IRIS_CA` | — | Bundle CA PEM pour un IRIS à CA interne |
| `DFIR_IRIS_INSECURE` | — | `=1` pour ignorer la vérification TLS (labo uniquement) |
| `DFIR_IRIS_CUSTOMER_ID` | `1` | Id client pour les nouveaux cas IRIS (push) |
| `DFIR_IRIS_CLASSIFICATION_ID` | `1` | Id de classification pour les nouveaux cas IRIS (push) |
### Push Timesketch (facultatif)
URL + utilisateur + mot de passe tous requis pour activer le push. L'export vers JSONL fonctionne sans aucune config.
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_TIMESKETCH_URL` | — | URL de l'instance Timesketch |
| `DFIR_TIMESKETCH_USER` | — | Nom d'utilisateur auth locale |
| `DFIR_TIMESKETCH_PASSWORD` | — | Mot de passe auth locale |
| `DFIR_TIMESKETCH_TIMELINE` | `DFIR-Companion Forensic Timeline` | Nom de la timeline gérée |
| `DFIR_TIMESKETCH_CA` | — | Bundle CA PEM pour un Timesketch à CA interne |
| `DFIR_TIMESKETCH_INSECURE` | — | `=1` pour ignorer la vérification TLS (labo uniquement) |
### Export Notion (facultatif)
Le token seul l'active. Partagez la page/base de données cible avec l'intégration. « New page » nécessite une
base de données ou une page parente (défaut env ou saisi par export) ; « existing page » met à jour une page que vous collez.
| Variable | Défaut | Signification |
|---|---|---|
| `DFIR_NOTION_TOKEN` | — | Secret d'intégration interne (Notion : Settings → Connections → develop your own) |
| `DFIR_NOTION_DATABASE_ID` | — | Base de données par défaut pour les exports « new page » (le template d'investigation) |
| `DFIR_NOTION_PARENT_PAGE_ID` | — | Défaut alternatif : créer la nouvelle page sous cette page parente |
| `DFIR_NOTION_CONTAINER_TITLE` | `🔍 DFIR Companion — Auto-generated` | Titre du bloc géré que le Companion possède |
| `DFIR_NOTION_MAX_TIMELINE` | `500` | Nombre max de lignes de timeline écrites dans Notion |
| `DFIR_NOTION_CA` | — | Bundle CA PEM si un proxy utilise une CA interne |
| `DFIR_NOTION_INSECURE` | — | `=1` pour ignorer la vérification TLS (labo uniquement) |
### Chasses live Velociraptor + bundles de triage (facultatif)
Définissez `DFIR_VELOCIRAPTOR_API_CONFIG` pour activer. Générez la config une fois avec :```
velociraptor --config server.config.yaml config api_client --name dfir --role administrator,api api.config.yaml
| Variable | Défaut | Signification |
|---|---|---|
DFIR_VELOCIRAPTOR_API_CONFIG | — | Chemin vers le fichier de configuration api_client |
DFIR_VELOCIRAPTOR_BINARY | velociraptor | Chemin de l'exécutable (chemin complet du .exe sous Windows) |
DFIR_VELOCIRAPTOR_GUI_URL | — | URL de base de l'interface graphique pour les liens profonds vers les hunts lancés |
DFIR_VELOCIRAPTOR_ORG | root | Organisation pour le ?org_id= du lien profond (l'interface graphique l'exige, avant le fragment #) |
DFIR_VELOCIRAPTOR_TIMEOUT_MS | 60000 | Délai d'expiration par requête (ms) |
DFIR_VELOCIRAPTOR_MAX_ROWS | 1000 | Nombre maximal de lignes renvoyées au tableau de bord |
DFIR_VELOCIRAPTOR_MAX_OUTPUT | 52428800 | Plafond strict sur les octets de sortie des requêtes interactives (50 Mo) |
DFIR_VELOCIRAPTOR_COLLECT_MAX_OUTPUT | 268435456 | Plafond plus élevé pour la collecte par bundle-hunt (lignes + JSON téléversé ; THOR/Hayabusa sont volumineux). Un artefact/téléversement dépassant ce seuil est ignoré (journalisé), sans être fatal — le reste est quand même importé. |
DFIR_VELO_HUNT_WAIT_MIN | 10 | Nombre de minutes par défaut avant qu'un hunt de bundle de triage ne collecte automatiquement (surcharge par exécution + par bundle ; borné entre 1 et 1440) |
DFIR_VELOCIRAPTOR_UPLOAD_VQL | — | Avancé : remplacer le VQL qui lit les rapports texte téléversés d'un hunt (json/jsonl/ndjson/csv/txt/log ; sensible à la version ; conserver le placeholder __HUNT_ID__) |
DFIR_VELOCIRAPTOR_FLOW_UPLOAD_VQL | — | Avancé : remplacer le VQL qui lit les rapports téléversés d'un flow unique collé depuis l'extérieur (conserver les placeholders /) |
Bundles de triage (onglet Settings → Velociraptor) : Browse server artifacts liste les artefacts CLIENT collectables du serveur ; assemblez + enregistrez des bundles nommés (trois sont fournis en standard — Best Practice (balayage des gains rapides), Super-Timeline Triage (artefacts bruts de l'hôte, routés uniquement vers la super-timeline) et Linux Triage — stockés globalement à côté de cases/ dans bundles/). Chaque bundle, y compris ceux fournis en standard, est modifiable sur place — une modification enregistre une surcharge ; Reset to default la supprime. Lancez-en un en tant que hunt depuis le panneau Fleet Collection du tableau de bord (éventuellement délimité par des labels include/exclude + OS, et un seuil d'import minimum-severity). Le délai de collecte est un paramètre du bundle (configuré dans l'éditeur — augmentez-le pour les artefacts lents comme THOR ; la valeur par défaut de Velociraptor est 600 s) et est appliqué automatiquement à chaque exécution. Chaque hunt porte aussi une expiration relative — combien de temps il continue à planifier sur les clients qui se connectent plus tard — choisie parmi 1 heure / 1 jour / 1 semaine (par défaut 1 heure, contre la valeur par défaut d'une semaine de Velociraptor lui-même) ; c'est une valeur par défaut par bundle définie dans l'éditeur et surchargeable par exécution. Les bundles peuvent aussi porter des paramètres par artefact (transmis au spec du hunt) afin qu'un artefact lourd émette moins à la source — Best Practice fournit Hayabusa épinglé à RuleLevel=Critical/High/Medium + RuleStatus=Stable+Experimental pour qu'il n'inonde pas l'import ; ajustez n'importe quel artefact via le JSON optionnel Advanced → parameters du constructeur, et éliminez les lignes bruyantes avec des filtres d'exclusion par artefact (VQL WHERE, par ex. NOT OSPath =~ 'pagefile'). Le hunt reste ouvert jusqu'à expiration, donc le Companion collecte automatiquement après DFIR_VELO_HUNT_WAIT_MIN et ingère à la fois les lignes de résultat et tout rapport JSON téléversé (par ex. THOR/Hayabusa via Generic.Scanner.ThorZIP — pour ceux-là les lignes importent peu, c'est le JSON téléversé qui compte ; il est auto-détecté et routé vers le bon importateur), puis synthétise — ou cliquez sur Collect now sur la carte du job en cours pour tirer plus tôt. Le job en cours persiste par affaire (state/velo-hunt.json) et survit à un redémarrage du serveur ; les résultats apparaissent sur la timeline/IOCs du tableau de bord.
| Variable | Défaut | Description |
|---|---|---|
DFIR_MCP_MODEL | (défaut CLI) | Modèle utilisé pour les appels d'outil MCP uniques, transmis à claude --model. |
DFIR_MCP_AGENT_MODEL | (défaut CLI) | Modèle pour la boucle agentique, transmis à claude --model. |
Enregistrer un serveur est une décision de sécurité, pas seulement de la configuration — voir Enregistrer un serveur MCP.
Poussez les constats nouveaux/ escaladés, les mises à jour de playbook et les jalons d'investigation vers des webhooks Slack / MS Teams ou par email SMTP. Il n'y a aucune variable d'environnement d'activation — les canaux sont créés dans le tableau de bord (⚙ Settings → Notifications) et stockés à côté de cases/ dans notifications/config.json (gitignoré ; il contient les URLs de webhook + les mots de passe SMTP). La liste démarre vide (opt-in). Chaque canal a un seuil de sévérité et des bascules par événement (constats / playbook / jalons). Utilisez le bouton Test pour vérifier un canal de bout en bout.
⚠ OPSEC : les notifications envoient du contenu de l'affaire (titres de constats/tâches) à un tiers. Ne les activez pas sur une affaire sensible à moins que la destination ne soit de confiance.
Slack — créez un Incoming Webhook (aucun scope OAuth manuel ; Slack ajoute incoming-webhook automatiquement) :
DFIR Companion) et choisissez votre espace de travail.https://hooks.slack.com/services/T…/B…/…).Un webhook publie vers un seul canal — ajoutez un autre webhook (et un autre canal Companion) pour chaque canal supplémentaire. L'URL est un secret (quiconque la possède peut y publier), c'est pourquoi le fichier de configuration est gitignoré et l'URL est masquée dans les réponses de l'API. Les scopes de bot-token comme chat:write ne sont pas nécessaires — le Companion publie via le webhook entrant, pas via la Web API.
MS Teams — ajoutez un connecteur Incoming Webhook (ou un flux Power Automate « when a webhook request is received ») à un canal et collez son URL (le Companion envoie une MessageCard). Email SMTP — donnez au canal un hôte/port, un nom d'utilisateur + mot de passe facultatifs, et from/to ; STARTTLS opportuniste + AUTH LOGIN sont utilisés lorsqu'ils sont proposés. Pour un test local rapide, pointez-le vers Mailpit (docker run -p 1025:1025 -p 8025:8025 axllent/mailpit).
Telegram — utilise un token Bot API + un ID de chat/canal/groupe :
/newbot, et copiez le token (123456789:AAF…)./start à votre bot, puis ouvrez https://api.telegram.org/bot<TOKEN>/getUpdates ; le chat.id est un entier positif.getUpdates ; le chat.id est un entier négatif.@mychannel.@getidsbot pour obtenir l'ID numérique (généralement -100…).Vous faites déjà tourner le bot de war-room ? Laissez le token vide et renseignez uniquement le chat ID — le canal réutilise DFIR_TELEGRAM_BOT_TOKEN depuis .env, et le champ affiche (already set). Le token reste uniquement dans .env, donc le faire tourner là-bas fait aussi tourner ce canal. Ne saisissez un token ici que pour envoyer via un autre bot ; il remplace alors celui de l'env pour ce canal.
Un token saisi ici est stocké dans notifications/config.json (à côté de cases/) et n'est jamais renvoyé au navigateur — le tableau de bord apprend seulement si un token est défini, et s'il provient de .env.
| Variable | Défaut | Signification |
|---|---|---|
DFIR_PUBLIC_URL | http://<host>:<port> | URL de base publique utilisée pour créer un lien profond d'une notification vers l'affaire (à définir lorsqu'on y accède via un nom d'hôte/proxy) |
DFIR_NOTIFY_CA | — | Bundle de CA PEM pour un hôte de webhook auto-hébergé (par ex. Mattermost) |
DFIR_NOTIFY_INSECURE | — | =1 pour ignorer la vérification TLS pour l'hôte du webhook (labo uniquement) |
Les notifications poussent vers l'extérieur ; ceci est le chemin retour. Pilotez l'affaire depuis le canal d'incident au lieu de basculer vers le tableau de bord pour chaque question :``` /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
Chaque plateforme s'active lorsque vous définissez son secret :
**Aucun tunnel nécessaire** — le compagnon ouvre la connexion en sortie :
| Plateforme | Comment les commandes arrivent | Activer avec |
|---|---|---|
| Slack | **Socket Mode — WebSocket sortant** | `DFIR_SLACK_SOCKET_MODE=on` + `DFIR_SLACK_APP_TOKEN` (`xapp-…`, `connections:write`) |
| Telegram | **Long polling** | `DFIR_TELEGRAM_POLL=on` + `DFIR_TELEGRAM_BOT_TOKEN` |
Ou en tant que webhooks entrants, qui nécessitent une adresse publique :
| Plateforme | Point de terminaison | Activer avec |
|---|---|---|
| Slack | `POST /integrations/slack/command` | `DFIR_SLACK_SIGNING_SECRET` (Basic Information → Signing Secret) |
| MS Teams | `POST /integrations/teams/command` | `DFIR_TEAMS_TOKEN` (secret partagé dans l'en-tête `Authorization`) |
| Telegram | `POST /integrations/telegram/command` | `DFIR_TELEGRAM_SECRET_TOKEN` (le `secret_token` que vous passez à `setWebhook`) |
**Telegram n'a besoin d'aucun tunnel.** Créez le bot avec [@BotFather](https://t.me/BotFather), définissez deux
variables, redémarrez, et envoyez-lui un message :```bash
DFIR_TELEGRAM_POLL=on
DFIR_TELEGRAM_BOT_TOKEN=123456789:AAF...
Le compagnon appelle Telegram et demande de nouvelles commandes, donc rien de la machine n'est accessible
depuis internet — la même direction sortante que celle déjà utilisée par le notificateur. Un bot ne peut pas faire les deux :
effacez d'abord tout webhook existant avec .../deleteWebhook.
Slack Socket Mode repose sur la même idée : activez Socket Mode sur l'application, générez un token au niveau de l'application
(xapp-…, scope connections:write), et le compagnon se connecte en sortie vers Slack — pas de Request URL.
Le mode webhook atteint ce compagnon depuis internet via votre tunnel ou reverse proxy — et ce nom d'hôte doit figurer dans
DFIR_ALLOWED_HOSTS, sinon la protection contre le DNS-rebinding rejette la requête avant que le bot ne la voie. MS Teams n'a pas d'option sortante, il a donc toujours besoin de cela.
OPSEC — toute personne pouvant publier dans le canal peut extraire le contenu d'un cas. Les cas protégés par mot de passe sont entièrement refusés via le chat (un message de chat ne porte aucun déverrouillage). Définissez
DFIR_*_ACTION_USERSpour réserver la dépense IA, la re-synthèse et le re-binding à des intervenants nommés ; cela confine également tous les autres au cas lié au canal.
| Variable | Défaut | Signification |
|---|---|---|
DFIR_SLACK_ACTION_USERS | (non défini = ouvert) | Identifiants utilisateur Slack séparés par des virgules autorisés à exécuter ask/hunt/synthesize/bind |
DFIR_TEAMS_ACTION_USERS | (non défini = ouvert) | Idem, pour Teams |
DFIR_TELEGRAM_ACTION_USERS | (non défini = ouvert) | Idem, pour Telegram (identifiants utilisateur numériques) |
DFIR_SLACK_RESPONSE_HOSTS | hooks.slack.com | Hôtes supplémentaires auxquels un résultat asynchrone peut être livré (serveur compatible Slack auto-hébergé) |
DFIR_TEAMS_RESPONSE_HOSTS | *.webhook.office.com, *.logic.azure.com, *.office.com | Idem, pour Teams |
DFIR_TELEGRAM_BOT_TOKEN | — | Token @BotFather, utilisé pour livrer les résultats asynchrones |
DFIR_TELEGRAM_API_BASE | https://api.telegram.org | Remplacement de l'URL de base de l'API Bot |
| Variable | Défaut | Signification |
|---|---|---|
DFIR_HUNT_PLATFORMS | tous | Liste d'autorisation de plateformes séparées par des virgules pour les cartes de pivot de chasse : velociraptor, defender, elastic, splunk, sigma, yara, suricata |
DFIR_CORRELATE_WINDOW_S | 2 | Fenêtre temporelle (s) pour la fusion d'événements multi-sources sur un même chemin |
DFIR_PHASE_GAP_S | 300 | Écart entre événements (s) qui démarre une nouvelle phase d'attaque |
DFIR_BEACON_MIN_COUNT | 5 | Nombre minimum d'événements de connexion vers un canal (hôte → dest:port) avant qu'il ne soit considéré pour la détection de beacon |
DFIR_BEACON_MAX_JITTER_PCT | 20 | Jitter d'intervalle maximal (écart-type en % de la moyenne) pour qu'un canal soit compté comme beacon — plus bas = plus strict |
DFIR_GAP_MIN_MINUTES | 30 | Seuil plancher strict pour l'analyse des lacunes de logs — un silence de timeline plus court que cela n'est jamais signalé |
DFIR_GAP_DENSITY_FACTOR | 4 | Une lacune doit aussi être ≥ ce × l'intervalle médian entre événements de la timeline pour être signalée (supprime le calme normal dans les timelines clairsemées ; 0 = plancher uniquement) |
DFIR_GAP_ACTIVE_HOURS | (non défini) | Heures de travail optionnelles "8-18" (UTC, prend en charge le chevauchement "22-6") — ne signaler que les lacunes qui les chevauchent ; remplace l'heuristique de densité lorsqu'il est défini |
DFIR_GAP_MAX_FINDINGS | 5 | Plafond des lacunes de silence complet qui deviennent une conclusion (le panneau/rapport les affiche toujours toutes) — empêche un cas de super-timeline d'inonder la liste des conclusions |
DFIR_GAP_HYPOTHESIS_MAX |
Exemple de .env (configuration OpenRouter à deux niveaux) :```
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 — référence CLI complète
Tous s'exécutent depuis `companion/`. Les arguments après `--` sont transmis au script.
### `npm run dev`
Démarre le serveur (lit `.env`). Se lie à `127.0.0.1:4773`. Tableau de bord à `/dashboard`.```
npm run dev
npm run buildVérification de type / compilation avec tsc. Aucun argument.```
npm run build
### `npm test`
Exécute la suite vitest complète. Aucun argument.```
npm test
npm run verify:ai -- [caseId] [flags]Test de fumée en un seul appel : envoie 3 captures d'écran du milieu du cas au modèle configuré et confirme que la réponse est analysée conformément au schéma. Affiche les constatations, les événements forensiques et un aperçu du chemin d'attaque.
| Arg / flag | Défaut | Effet |
|---|---|---|
caseId (positionnel) | test1 | Cas à partir duquel échantillonner les captures d'écran. |
--provider NAME | depuis .env | Remplace DFIR_VISION_PROVIDER pour cette exécution. |
--model ID | depuis .env | Remplace DFIR_VISION_MODEL pour cette exécution. |
--key KEY | depuis .env | Remplace DFIR_VISION_KEY pour cette exécution. |
| 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]`
__CLIENT_ID____FLOW_ID__DFIR_HUNT_SUGGEST_MAX | 8 | Nombre maximal de hunts de flotte suggérés par IA renvoyés par génération (nécessite un fournisseur d'IA, pas l'API Velociraptor) |
DFIR_PBHUNT_SUGGEST_MAX | 30 | Nombre maximal de hunts de playbook suggérés par IA renvoyés par génération (un par tâche liée à un endpoint ; nécessite un fournisseur d'IA) |
5 |
| Nombre maximal de lacunes sur lesquelles l'appel IA Hypothesize gaps raisonne par exécution (les pires en premier) ; chacune obtient toujours ses collections d'artefacts fantômes |
DFIR_GAP_HYPOTHESIS_CONTEXT | 8 | Événements de chaque côté d'une lacune fournis au prompt d'hypothèse comme contexte avant/après |
DFIR_DEDUP | on | Ignorer l'analyse IA d'une capture d'écran uniquement lorsqu'elle est identique octet pour octet à la capture précédente (correspondance exacte SHA-256 — l'écran n'a pas changé). Toute différence est analysée ; stockée comme preuve dans les deux cas. Définissez off pour analyser chaque capture d'écran |
TAGGER_AUTO | true | Étiqueteur d'événements basé sur le contenu (style Timesketch tags.yaml) : exécute automatiquement le jeu de règles après chaque import, en étiquetant les événements correspondants (et, sur la timeline forensique, en élevant la sévérité / en unissant MITRE). Définissez false pour ne l'exécuter manuellement que depuis le tableau de bord (Super-Timeline → 🏷 Content tagger → Run tagger) |
TAGGER_SCOPE | both | Sur quelle timeline l'étiqueteur s'exécute : forensic (timeline curée uniquement), super (super-timeline brute uniquement, étiquettes seulement — ne modifie jamais la sévérité/MITRE), ou both. Les étiquettes sont indexées par identifiant d'événement, elles filtrent donc dans les deux timelines quoi qu'il arrive |
TAGGER_RULES_FILE | (non défini) | Chemin absolu vers un fichier de règles personnalisé, remplaçant le fichier édité dans le tableau de bord et le fichier par défaut fourni (companion/data/tags.yaml). Modifiez les règles dans l'application via Super-Timeline → 🏷 Content tagger → Edit rules |