
DFIR 포렌식 컴패니언 서버 + 캡처 확장 프로그램
AI 지원 DFIR 트리아지 — 당신의 머신에서. 조사 스크린샷과 가져온 아티팩트를 포렌식 타임라인, 발견 사항, IOC, 자산↔IoC 그래프, 공유 가능한 보고서로 변환합니다; 사건에 대해 평범한 영어로 질문하고 다른 조사자와 협업하세요.
로컬호스트 디지털 포렌식 / 사고 대응 컴패니언. 브라우저 확장 프로그램이 조사(Velociraptor, EDR/SIEM 대시보드, Security Onion, Splunk4DFIR, VolWeb, VirusTotal 등)의 스크린샷을 증거로 캡처합니다; 로컬 서버가 이를 저장하고, 윈도우 기반 AI 비전 분석을 실행하여 사건별 누적 조사 상태로 만들고, 라이브 대시보드와 내보낼 수 있는 보고서를 제공합니다.
모든 것이 당신의 머신에서 실행됩니다 — 컴패니언은 127.0.0.1에만 바인딩되고, 증거는
디스크에 남으며, AI 제공자는 당신이 선택합니다.
탐지 후 분석 계층. DFIR Companion은 탐지 엔진이 아닙니다 — Velociraptor, Security Onion, Chainsaw, Hayabusa, THOR, Cyber Triage, EDR/SIEM의 판정을 수집하여 하나의 포렌식 타임라인으로 상관시키고, 발견 사항, 공격자 경로, IOC, 보고서를 종합합니다. 가치는 **"그래서 무엇을 해야 하는가"**에 있으며, 경보를 다시 도출하는 것이 아닙니다.
데모 사례: https://dfir-companion-production.up.railway.app/dashboard?caseId=demo
실습 랩: https://killercoda.com/dfir-companion/scenario/killercoda
사용자 매뉴얼: https://hasamba.github.io/DFIR-Companion/manual/
companion/.env)데모 사례: GlobalTech Industries — BEC 및 랜섬웨어 전조, 2026년 5월.
실제 증거를 가져오지 않고도 탐색할 수 있는 완전히 미리 채워진 사례 — 발견 사항, IOC, MITRE 기법, 분석가 태그/댓글, 고객 노출 데이터, 보고서 메타데이터가 모두 미리 시딩되어 있어 모든 대시보드 패널에 표시할 내용이 있습니다.
한 번의 클릭으로 로드 — 대시보드 툴바의 Demo case 버튼을 클릭하세요. 휴대용 Windows EXE에서도 작동합니다 (Node나
npm불필요). 사례가 이미 존재하는 경우 버튼이 덮어쓰기 전에 확인합니다.또는 CLI에서 시딩 (개발 / 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 id그런 다음
http://127.0.0.1:4773/dashboard를 열고 사례에 연결하세요.
AI가 생성한 사례 요약, 분 단위 내러티브, 공격자 경로 기술 — 초기 접근부터 랜섬웨어 배포까지.
심각도 필터, 트리아지 태그, 행별 상세 링크, 가져오기 변경 추적(확장 가능한 diff가 있는 새 이벤트 배너)이 포함된 분석된 이벤트.
범위/심각도 필터링 이전에 가져온 모든 이벤트 — 행을 필터링, 태그, 별표 표시하고 분석된 포렌식 타임라인으로 승격시키세요; 아무것도 제거되지 않으며, 이는 상위 집합 뷰입니다.
자산(Y축)과 시간(X축)별 이벤트를 심각도로 색상화한 시각적 차트 — 시간 축을 드래그하여 포렌식 타임라인을 범위로 필터링하세요.
신뢰도 점수, 분석가 트리아지 태그, MITRE ATT&CK 기법 링크가 포함된 AI 생성 발견 사항; 이전 종합 실행 이후 변경된 사항을 추적합니다.
MITRE ATT&CK 전술별로 분류된 이벤트 — 확인된 킬 체인 단계가 아닌 분류로, AI 없이 결정론적으로 도출됩니다.
종합된 사례에서 자동으로 답변되는 표준 DFIR 질문(답변됨 / 부분적 / 알 수 없음), 각각 증거 포인터 또는 "다음으로 수집할 항목" 지시가 함께 제공됩니다.
발견 사항과 권장 다음 단계에서 자동으로 도출된 실행 가능한 교정 체크리스트; 분석가 상태, 담당자, 마감일을 보존하면서 각 종합 실행 시 재동기화됩니다.
공격을 주도하는 호스트/계정은 무엇인가 — 볼륨이 아닌 신호(심각도 가중 이벤트 + 기법 + 연결 IOC)로 점수가 매겨지며, 제안된 범위 창이 함께 제공됩니다.
프로세스 트리, 측면 이동, 파일 계보가 하나의 인과적 공격 그래프로 엮입니다. 임포터가 채운 필드에서 결정론적으로 도출됩니다 — AI 없음, 비용 없음, 오프라인 실행.
누가 어디에 로그온했는가 — 슈퍼 타임라인 로그온 이벤트에서 연결된 계정과 호스트로, 성공, 실패, 위험(RDP/runas/netonly) 로그온을 구분합니다.
인간 트래픽으로 보기에는 너무 규칙적인 주기적 아웃바운드 채널 — 판정이 아닌 헌팅 단서로, 후보별 간격, 지터, 이벤트 수가 함께 제공됩니다.
지표(IP · 도메인 · 해시 · 파일 · 프로세스 · 계정)가 VirusTotal,
AbuseIPDB, ThreatFox 및 기타 제공자에 대해 강화됨 — 판정 배지, 탐지 점수, NEW 가져오기
하이라이트, 분석가 트리아지 라벨.
피해자 호스트와 계정을 각각에 접촉한 지표에 연결하는 대화형 그래프, 그리고 알려진 침해 호스트와 사용자 목록.
drop/ 폴더에 복사된 파일은 백그라운드에서 가져오고, _processed/ 또는 _failed/로 이동하며, drop-log.txt에 기록; asset=<HOST> 하위 폴더가 호스트를 명명.evtx는 바이트 단위로 보존, 파서 버전과 종료 코드가 보관에 기록, 실패 시 닫힘, 기본적으로 꺼짐모든 임포터는 **결정론적(AI 호출 없음)**이며, 아티팩트 자체의 타임스탬프를 읽고, 교차 소스 상관을 위해 실제 도구 이름으로 이벤트에 태그를 지정합니다. 같은 파일을 타임라인을 중복시키지 않고 다시 가져올 수 있습니다.
object/action/properties, epoch-ms timestamp_ms) — 프로세스/플로우/로그온/레지스트리/모듈/파일/스레드 이벤트 | Info 증거; LOLBin/인코딩된 명령줄 상향 (공인 IP → IOC) |
| Windows Event Log XML | 이벤트 뷰어 "XML로 저장", wevtutil qe /f:xml, Get-WinEvent … ToXml() (Security, Sysmon, System, 모든 채널) | Windows/Sysmon EID별 테이블 |
| Chainsaw | EVTX 헌트 JSON/JSONL (chainsaw hunt --json); 도구 러너를 통해 원시 .evtx에서 직접 실행 가능 | 매칭된 Sigma 규칙 레벨 |
| Hayabusa | json-timeline 또는 csv-timeline | 매칭된 Sigma 규칙 레벨 |
| Velociraptor | JSON 배열, JSONL, 또는 아티팩트 맵 | Sigma/YARA 판정 또는 EID별 |
| THOR (Nextron) | JSON-Lines 스캔 출력 | THOR 경보 레벨 |
| Suricata / Zeek | eve.json, Zeek JSON 로그; 텔레메트리 → IOC만 | 경보 우선순위 / notice 심각도 |
| | 단일 행 경보 로그 | 규칙 (1→High / 2→Medium / 3→Low) |
| | CLI 스캔 출력 (규칙 매치 + 문자열/메타) | 매치당 Info→Medium; 규칙 / 메타에 따라 상향 |
| | Apache/Nginx/Squid 로그 형식 (웹 서버 또는 포워드 프록시 접근 로그); 요청 URL, 캡처 (URL/Referer 내 시크릿 + 스캐너/봇/인젝션 UA가 이벤트 + IOC로 보존됨) | 기본 Info; 접근 거부 (401/403/407) → Low; git smart-HTTP clone/push → T1213 |
| | Built/Teardown/Deny 메시지 | 기본 Info (텔레메트리); 명시적 → Low |
| | RFC 5424 () + RFC 3164 () Linux/Unix 호스트 로그 | 기본 Info (텔레메트리); 인증 실패 또는 crit/alert/emerg PRI → Low |
| | SOC Alerts/Hunt 이벤트 (ECS); 확장 또는 SOC API 내보내기로 푸시됨 | (Suricata/SO 레이블) |
| | Suricata 경보 + YARA 파일 매치 () 및 Sigma 탐지 (); 확장 또는 원시 내보내기로 푸시됨 | Suricata 우선순위 / Sigma 레벨 / YARA 매치 |
| | JSONL / JSON / CSV 타임라인 | Cyber Triage 항목 점수 |
| | UAL, Entra 로그인 + 감사 로그 | BEC 트레이드크래프트 테이블 / Entra riskLevel |
| | System Log 내보내기 | IdP 트레이드크래프트 테이블 (MFA 비활성화, 관리자 권한 부여, API 토큰 발급, 세션 사칭) — 벤더의 운영 등급이 아님 |
| | 관리자 + 로그인 감사 | IdP 트레이드크래프트 테이블 (2SV 비활성화, 역할 부여, OAuth 동의, 메일 모니터 추가) |
| | Chrome/Edge/Brave 기록, 다운로드, 해석 (JSON 또는 CSV) | — (Info 이벤트: 브라우저 아티팩트는 증거이지 판정이 아님) |
| | 통합 로그 (), LSQuarantine 다운로드 이벤트, 속성, launchd plist, 로그인 항목 (클래식 plist, , BTM) | 격리 기록 ↔ 파일 속성 ↔ 브라우저 방문 ↔ 프로세스 시작이 식별자로 조인됨; plist는 구성으로 읽히며 실행으로 읽히지 않음 |
| | LEAPP TSV 내보내기에서 추출한 iOS + Android 아티팩트 | — (Info 이벤트; 타임스탬프 열을 키로 하는 범용 파서) |
| | Records JSON, NDJSON, Athena | API 액션 테이블 (IAM/로깅/S3/시크릿) |
| | Cloud Audit Logs, Azure Activity Log | 액션 테이블 (IAM/로깅/시크릿) |
| | API 서버 감사 로그 ( JSON-lines / EventList) | (verb, resource) 테이블 — pod exec/attach T1609, 시크릿 접근 T1552.007, RBAC 변경 T1098, 권한 있는 pod T1610/T1611, 익명 접근 T1078 |
| | 예약 쿼리 결과 로그 (차분 + ) | Info 텔레메트리; 명령줄 열에 대한 보수적 트레이드크래프트 상향 |
| | CSV (dynamic + l2tcsv) | — (Info 이벤트) |
| | CAPEv2 , Falcon Sandbox 요약 | 샘플 판정 + 행위 시그니처 |
| | Volatility 3 () + Rekall: pslist/pstree, netscan, malfind, cmdline, svcscan; JSON 실행 봉투 (명령, 종료 상태, stderr)가 내보내기 옆에 임포트됨 | malfind 주입 코드 → High (T1055); 목록 → Info/Low; 0행 또는 실패한 실행은 그것이 입증하는 바를 명시함 |
| | 플러그인 테이블 + | 동일한 플러그인 매핑; 메모리 YARA 히트 → Low, 밀집된 다중 규칙 클러스터 → Info; 행 상한 공개됨 |
| | 케이스 / 경보 JSON 내보내기, 관찰 가능 항목 목록 (TheHive 5) | TheHive 심각도 1–4; ATT&CK 태그가 지정된 태그에서 MITRE 도출 |
| | (RFC 2822), 최선 노력 | SPF/DKIM/DMARC 실패 → 발신자 스푸핑 휴리스틱 (T1566 피싱) |
| | / (bash + zsh 확장 기록) | 기본 Info; 트레이드크래프트에 대한 보수적 상향 (리버스 셸, 다운로드 후 실행, 자격 증명 접근, 로그/기록 변조, 측면 SSH) |
| | SSH authorized keys, cron, systemd 유닛, 셸 프로필, 단일 수집에서의 SUID 목록 및 PATH | 전 세계 쓰기 가능 페이로드, root가 사용자 쓰기 가능 파일 실행, setuid 인터프리터; 단지 존재한다는 이유로 등급이 매겨지는 것은 없음 |
| | 원시 / 레코드, 테이블 | 레코드 유형 테이블 (로그인, 계정 관리, sudo, SELinux, 감사 변조) |
| | / | syslog PRIORITY + 트레이드크래프트 상향 (sshd, sudo, useradd) |
| | Falco 경보 JSON, sysdig 이벤트 JSON | Falco 규칙 우선순위; 원시 시스템 콜 → Info 텔레메트리 |
| | / NDJSON, 또는 API 내보내기 () | (≥13 Critical, ≥10 High, ≥7 Medium) |
| | Velociraptor / EDR 내보내기 | — |
| | 방화벽, syslog, VPN; 반복적인 행 → 집계된 패턴 | AI 트리아지됨 |결정론적 트레이드크래프트 등급 산정 — Windows/Sysmon, ECAR 및 메모리 명령줄은 110건 이상의 실제 침해 사례(The DFIR Report, Huntress)에서 수집된 규칙에 따라 등급이 매겨집니다: 고신뢰도 트레이드크래프트 → 해당 ATT&CK 기법과 함께 High (Defender 비활성화, 복구 방해, 자격 증명 덤프, 리버스 터널, Impacket, RMM/C2, 클라우드 유출 …), 이중 용도 → Medium; 순수 탐색은 태그되지만 절대 승격되지 않습니다.
runas /netonly)를 등급화 → Medium$SI/$FN 타임스탬프 불일치를 타임스톰핑 가능성으로 표시 → Mediumrclone/restic/megasync/megacmd 실행Zone.Identifier 마크가 동일 파일의 Prefetch, 프로세스 시작 및 존재 기록과 대조되어 읽히며, 실행이 그 이후로 날짜가 지정된 경우에만 승격됨; 숨겨진 스트림 페이로드는 이름이 아닌 내용으로 등급화됨DFIR_JEV_ENABLED 전까지 꺼져 있음)DFIR_SHODAN_KEY 재사용? 버튼이 새 탭에서 온라인 사용 설명서를 엽니다manual 태그가 붙고 재분석 후에도 유지됨)DFIR_CROSS_CASE=on이 아니면 꺼져 있습니다$0.00을 표시하지 않음)DFIR_MAX_EVENTS) — 기본 가져오기당 2000개 이벤트 안전 상한을 재정의DFIR_LOG_LEVEL 라이브 토글; debug는 AI/캡처/OCR/익명화를 추적choco install dfir-companion; 포터블 빌드를 다운로드 + 검증하고 캡처 확장을 번들로 제공, 데이터는 %LOCALAPPDATA%에 저장docker compose up; 증거는 호스트 볼륨에, 번들 AI 백엔드 없음Companion은 사건 증거를 귀하가 운영하는 MCP 서버 — SIFT 워크스테이션, REMnux 박스, Windows 트리아지 베이스라인 서비스 — 로 향하게 할 수 있어, 도구가 갖춰진 머신에서 증거가 분석됩니다.
이 서버들에 접근하는 경로는 Claude Code뿐입니다. Companion은 MCP 클라이언트가 아닙니다: 서버 URL도, 베어러 토큰도 보유하지 않으며, 자체적으로 npx나 uvx를 시작하지 않습니다. Claude Code는 이미 귀하의 서버로 구성되어 있고 그 자격 증명을 이미 보유하고 있으므로, Claude Code가 대화를 담당하고 Companion이 이를 요청합니다.
이 기능 전체는 다음 경우에만 작동합니다:
claude가 해당 PATH에 없으면 DFIR_AI_CLAUDE_CODE_BIN을 설정하세요.claude mcp add … 또는 해당 구성 파일), claude mcp list에 연결된 것으로 표시되어야 합니다.대체 경로는 없습니다. Claude Code 없이 Docker, AppImage, 또는 포터블 Windows 빌드에서 Companion을 실행하면 MCP 경로는 그 사실을 알려줄 뿐 다른 것은 하지 않습니다.
이 기능에 의존하기 전에 알아둘 만한 두 가지 결과가 있습니다. 모든 MCP 호출은 모델을 거치므로 토큰을 소비하며, 직접 JSON-RPC 요청이었을 때의 비트 단위 결정적 호출이 아닙니다 — 프롬프트가 이를 전송 수단으로 만듭니다 (하나의 도구, 정확한 인수, 축자 출력). 그러나 여전히 중간에 모델이 있습니다. 그리고 서버는 생성된 구성이 아니라 Claude Code 자체 구성에서 오므로, Claude Code는 사용 중인 서버만이 아니라 구성된 모든 서버를 매 실행마다 시작합니다; 허용 목록은 호출될 수 있는 것을 제한할 뿐, 시작되는 것을 제한하지 않습니다.
Settings → Tools에서 Refresh from Claude Code를 눌러 서버 목록을 로드한 다음, 하나를 허용하고 무엇을 할 수 있는지 지정하세요. 입력할 것은 정책뿐입니다 — 서버 이름은 Claude Code 자체에서 오므로, 오타로 인해 조용히 아무것도 일치하지 않는 항목이 생기는 일은 없습니다.
POST /cases/<id>/mcp/<serverId>/run에 { tool, args, targetPath }를 사용합니다. 도구가 증거 경로를 기대하는 곳에 <target>을 넣으세요 — 전달이 실행된 후 분석 호스트 상의 경로로 대체되므로, 작성한 인수가 도구가 받는 인수입니다:```json
{ "tool": "run_command",
"args": { "command": ["vol.py", "-f", "", "pslist"] },
"targetPath": "imports/memory.raw" }
`targetPath`는 케이스 디렉터리 내부에서 해석되며, 그 밖의 경로는 거부됩니다. 브라우저가 보유하고 있지만 서버가 접근할 경로가 없는 샘플의 경우, `POST /cases/<id>/mcp/<serverId>/run-upload`가 대신 `{ filename, dataBase64 }`를 받아 케이스 내부에 바이트를 먼저 스테이징합니다.
두 방식 모두 블로킹하지 않고 **작업 ID와 함께 202**를 반환합니다. 실제 Volatility 실행은 합리적인 요청 타임아웃보다 오래 걸리므로, 실행은 진행률, 취소 버튼, WebSocket `job_changed` 브로드캐스트를 갖춘 백그라운드 작업입니다. 결과는 다른 모든 도구와 동일한 가져오기 체인을 통해 케이스로 흘러들어갑니다 — 타임라인 이벤트, 파인딩 및 IOC, 실행 취소 체크포인트 포함 — 따라서 결과를 읽는 방식은 일반 가져오기와 다르지 않습니다. 구조화된 출력은 일치하는 임포터로 라우팅되고, 구조화되지 않은 산문은 거부되지 않고 일반 로그 경로로 전달됩니다.
자체 실패를 보고하는 도구는 수집되지 않고 작업을 실패시킵니다. 오류 메시지는 진단이지 아티팩트가 아니며, 이를 타임라인에 기록하면 증거처럼 보이게 되기 때문입니다.
### 가져오기 전 미리보기
**기본적으로 켜져 있으며**, 켜두는 것이 좋습니다. MCP 서버는 증거만큼이나 참조 데이터를 쉽게 반환합니다 — SIFT에 어떤 도구가 있는지 물으면 Volatility 테이블과 구조적으로 동일한 JSON 인벤토리를 받게 됩니다: 타임스탬프가 없는 객체 배열입니다. 어떤 탐지기도 이 둘을 구별할 수 없으므로, 임포터는 원래 하도록 만들어진 대로 그 안의 모든 경로를 파일 인디케이터로 추출합니다. 기능 목록 하나가 케이스가 전혀 원하지 않았던 수십 개의 IOC가 됩니다.
미리보기가 켜져 있으면 실행은 출력을 가져오고 멈춥니다. 바이트, 크기, 그리고 *가져올* 종류를 확인하고 선택할 수 있습니다. 승인하면 **이미 가져온 바로 그 바이트**를 수집합니다 — 도구를 다시 실행하지 않으므로 20분짜리 Volatility 실행은 20분이 한 번만 소요되고, 부작용이 있는 도구는 그 부작용을 한 번만 수행합니다. 폐기하면 출력을 버리고 케이스는 그대로 유지됩니다.
API에서 사용하려면 실행 시 `preview: true`를 보낸 다음, `/cases/<id>/mcp/preview/<jobId>`에 대해 `GET`, `POST …/import` 또는 `DELETE`를 사용하세요.
여기서 무엇을 실행할지에 대한 판단을 대체하는 것은 없으며, 미리보기 없이 가져오는 것이 위험하지도 않습니다 — 모든 MCP 가져오기는 실행 취소 체크포인트를 푸시하므로, 노이즈로 판명된 실행은 클릭 한 번으로 롤백할 수 있습니다.
### 서버 사용이 부여하는 것
**기본적으로 서버가 제공하는 모든 것입니다.** 이는 의도적입니다: Claude Code는 이미 구성한 모든 서버의 모든 도구를 호출할 수 있게 해주므로, 여기서 다시 열거하도록 요구하는 것은 일상적인 사용보다 더 엄격했을 것이며 — 같은 서버를 설명하는 두 번째 장소가 되었을 것입니다.
"모든 것"에 무엇이 포함되는지 알 가치가 있습니다. 일부 서버는 세분화된 도구를 노출합니다 — `check_service`, `check_autorun`, 질문당 하나씩. 다른 서버는 넘겨주는 무엇이든 실행하는 단일 **명령 실행기**를 노출합니다: SIFT의 `run_command`는 "curl, wget, dd, fdisk, python3를 포함한 대부분의 SIFT 설치 도구"를 실행할 수 있다고 명시하고, REMnux의 `run_tool`은 전체 셸 파이프라인을 받습니다. Companion에서 이러한 서버를 사용한다는 것은 해당 호스트에서의 명령 실행을 의미합니다 — 분석 박스가 본인 소유이고 증거가 이미 본인 LAN에 있는 격리된 포렌식 네트워크에서는 합리적이며, 다른 곳에서는 합리적이지 않습니다.
그것을 원할 때 좁힐 수 있는 두 개의 **선택적** 목록이 있습니다:
| 설정 | 적용 대상 | 비어 있음의 의미 |
|---|---|---|
| **도구로 제한** | 모든 호출 | 서버가 제공하는 모든 도구 |
| **명령으로 제한** | 명령 인수를 전달하는 호출 | 명령 제한 없음 |
명령은 **basename으로** 매칭되므로 `grep`과 `/usr/bin/grep`은 하나의 규칙입니다. 파이프라인의 모든 단계가 검사되며 첫 번째만이 아닙니다 — `oledump.py s.doc | curl -T - http://elsewhere`는 `oledump.py`와 `curl` 둘 다 허용되어야 합니다. 셸 치환(`$(…)`, 백틱, `${…}`)을 사용하는 명령은 실행될 내용을 미리 알 수 없으므로 즉시 거부됩니다.
**명령 목록이 하지 않는 것.** 이는 *어떤* 바이너리가 실행되는지를 제한할 뿐, 허용된 바이너리가 무엇을 할 수 있는지는 결코 제한하지 않습니다 — `dd`를 허용하면 해당 서버 사용자가 쓸 수 있는 모든 경로에 쓰는 것이 허용되고, `python3`를 허용하면 임의 코드가 허용됩니다. 또한 잘 알려진 매개변수 이름(`command`, `cmd`, `argv`)에 의존하므로, 명령 매개변수를 특이한 이름으로 지정한 서버는 잡히지 않습니다. 이는 자신의 접근을 좁히고자 하는 운영자를 돕기 위해 존재하는 것이지, 애초에 구성하지 말았어야 할 서버를 격리하기 위한 것이 아닙니다.
### 서버로 증거 전달하기
MCP에는 파일 전송 프리미티브가 없고 수 기가바이트 메모리 이미지는 도구 인수 안에 담을 수 없으므로, 파일은 이미 서버가 열 수 있는 어딘가에 있어야 합니다. 이 부분은 Companion의 역할로 남습니다 — Claude Code는 이미지를 분석 박스로 옮길 수 없습니다. 각 서버는 두 경로 중 하나를 선택합니다:
**`remote-path`** (기본값) — 증거가 공유 마운트를 통해 분석 호스트에 이미 보입니다. 로컬 접두사와 원격 접두사를 설정하면 경로가 재작성됩니다(`/srv/cases/…` → `/mnt/dfir/…`). 양쪽에서 동일한 경로에 마운트된 경우 둘 다 비워 두세요. 아무것도 복사되지 않습니다.
**`scp`** — Companion이 파일을 스테이징 디렉터리로 푸시하고, 도구가 실행된 후 스테이징된 복사본이 삭제됩니다. `host`, `remoteDir`, 선택적으로 `user`, `port`, `identityFile`을 구성하세요.
`scp`를 선택하기 전에 알아야 할 네 가지:
- **호스트 키가 이미 신뢰되어 있어야 합니다.** `BatchMode`가 켜져 있고 `StrictHostKeyChecking`이 *비활성화되지 않으므로*, 알 수 없는 호스트는 주소에 응답한 무엇이든 신뢰하는 대신 `Host key verification failed`로 실패합니다. 먼저 수동으로 한 번 연결하거나(또는 키를 `known_hosts`에 추가) 하세요. 이는 의도적입니다: 검증되지 않은 키를 조용히 수용하면 IP를 보유한 누구에게나 증거를 넘겨주게 됩니다.
- **인증은 키 기반만 가능합니다.** `BatchMode`는 ssh가 절대 프롬프트하지 않음을 의미하므로, 비밀번호만 있는 호스트는 작동할 수 없습니다. `identityFile`을 암호 없는 키로 지정하거나, 서버 프로세스가 접근할 수 있는 에이전트에 로드하세요.
- **진행률도 재개도 없습니다.** 16 GB 복사는 완료되거나 실패할 때까지 불투명하며, 연결이 끊기면 처음부터 다시 시작해야 합니다. 전송은 취소 가능하고 도구 호출 타임아웃과 별개인 자체 1시간 타임아웃을 가집니다.
- **호스트, 사용자, 원격 디렉터리는 보수적인 문자 집합으로 제한됩니다** (문자, 숫자, 점, 대시, 밑줄, 디렉터리의 경우 `/`). `user@host`는 따옴표 없이 ssh에 도달하므로, 셸 의미를 가진 것은 전송 시점이 아니라 저장할 때 거부됩니다. 스테이징된 파일 이름은 증거 이름에서 파생되며 동일한 방식으로 정제됩니다.
두 경로 모두 목적지를 명시하는 **보관 연속성 `transferred` 이벤트**를 기록하므로, 케이스 파일은 증거가 이 머신을 떠났다는 것, 언제, 어디로 갔는지를 보여줍니다. 실패한 전송은 아무것도 기록하지 않습니다 — 연속성은 일어나지 않은 복사를 결코 주장하지 않습니다.
### 평이한 영어 MCP 조사
단일 도구 호출로는 실마리를 따라갈 수 없습니다. "이 덤프를 조사하라"는 루프를 원합니다 — pslist를 실행하고, 무언가를 발견하고, malfind로 전환하는 — 그리고 그것이 에이전트 모드가 하는 일입니다: 허용한 서버에 대해 Claude Code가 주도하도록 하고, 보고하는 내용을 병합합니다. 이것이 대시보드의 주요 MCP 워크플로우입니다: 목표를 평이한 영어로 작성하고, 증거를 선택하거나 탐색하고, MCP 앱을 선택하고, **Investigate**를 누르세요. 도구 이름과 JSON 인수는 고급 수동 호출 섹션에서만 사용할 수 있습니다.
`{ prompt, servers?, targetPath?, preview? }`와 함께 `POST /cases/<id>/mcp/agent`, 또는 `{ prompt, servers, filename, dataBase64, preview? }`와 함께 `POST /cases/<id>/mcp/agent-upload`.
**서버를 허용하기 전에 이것을 읽으세요.** 수동 실행에서는 Companion이 각 호출을 제어하므로 모든 호출이 도구 *및* 명령 허용 목록을 통과합니다. 에이전트 모드에서는 그렇지 않습니다: `claude`가 서버와 직접 대화합니다. 도구 허용 목록만 `--allowed-tools`로 살아남습니다. **명령 허용 목록은 강제될 수 없습니다.** 따라서 에이전트가 명령 실행기 도구를 사용하도록 허용하면 자율 루프가 해당 호스트에서 스스로 명령줄을 선택할 수 있는 능력을 부여하게 됩니다.
Companion에서 MCP 서버를 허용하고 활성화하는 것이 이 모드의 권한 경계입니다. 서버의 도구 제한은 여전히 적용됩니다. 명령 제한은 자율 루프를 제약할 수 없으며, 고급 수동 호출에만 적용됩니다.
이 모드가 여전히 보장하는 것: 명시적 도구 제한은 도구별로 전달됩니다. 빈 제한은 의도적으로 해당 서버가 노출하는 모든 도구를 허용합니다. 프로젝트/로컬 설정, `CLAUDE.md` 파일 및 훅은 제외되며, 실행은 턴 제한됩니다. Claude Code의 사용자 설정은 MCP 서버 연결이 거기에 있으므로 활성화된 상태로 유지됩니다.
에이전트의 응답은 병합되기 전에 스키마 검증되고 출처 주장이 제거됩니다 — 그것이 본 모든 것은 신뢰할 수 없는 도구 출력에서 왔기 때문입니다. 케이스 요약을 요청받지 않으므로, 실행은 결론을 재작성하지 않고 파인딩, IOC 및 이벤트를 추가합니다. 미리보기는 여기서도 작동하며 더 중요합니다: 자율 루프는 무엇을 보고할지 스스로 결정합니다.
조사는 40턴으로 제한됩니다. Claude Code가 도구를 사용하면서 그 예산을 소진하면, Companion은 모든 도구를 비활성화한 상태로 동일한 세션을 한 번 재개하고 이미 수집된 증거만으로 보고하도록 요청합니다. 이는 완료된 조사의 최종 JSON이 다음 턴이었을 것이라는 이유만으로 조사를 잃지 않으면서 안전 경계를 보존합니다.
### 자격 증명
여기에 구성할 것은 없습니다. Bearer 토큰, 헤더 및 전송은 모두 Claude Code 자체의 MCP 구성에 있으며, 그것이 유일하게 이들을 보유하는 곳입니다. Companion은 서버 *이름*, 허용 목록 및 전달 블록을 저장합니다 — 스스로 무엇이든 연결할 수 있게 해주는 것은 없습니다.
찾아볼 경우 한 가지 주의사항: `claude mcp list`는 각 서버의 전체 명령줄을 출력하며, `mcp-remote` 항목의 경우 bearer 토큰이 평문으로 포함됩니다. Companion은 그 출력에서 이름과 상태 판정만 파싱하고 나머지는 저장, 로깅 또는 렌더링하지 않습니다 — 하지만 그 명령을 직접 실행할 때는 어디서 실행하는지 주의하세요.
## 저장소 레이아웃```
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 │ └─────────────────────┘ └───────────────────────────────────────┘
**2단계 분석:** 저렴한 비전 모델이 각 스크린샷을 포렌식 타임라인으로 읽어들이고, 더 강력한 모델이 단일 종합 분석 호출(발견 사항, MITRE, 공격자 경로, 질문)을 수행합니다. 두 가지 모두 `.env`를 통해 구성합니다 — `companion/README.md`를 참조하세요.
## 빠른 시작
> **사전 요구 사항:** [Node.js](https://nodejs.org/) **22.19 이상** (`npm`이 함께 제공됨).
> `node --version`으로 확인하세요. 아래의 모든 내용은 `npm`을 사용하므로 다른 런타임은 필요하지 않습니다.
> 인덱싱된 케이스 저장소는 내장 `node:sqlite` 모듈을 사용하므로, 이전 Node 릴리스에서는 케이스를 열 수
> 없습니다. 포터블 빌드에는 호환되는 런타임이 번들로 포함되어 있습니다.
1. **Companion** (서버): ```
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)
확장 프로그램 (캡처):
가장 쉬운 방법: Chrome 웹 스토어에서 직접 설치하세요.
**Firefox 140+**에서는 최신 릴리스에서 dfir-capture-extension-firefox-*.zip을 다운로드하여 압축을 해제하세요.
또는 소스에서 빌드: ``` 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
Firefox에서는 about:debugging#/runtime/this-firefox에서 로드합니다 → 임시 부가 기능 로드…
그리고 manifest.json 파일을 선택합니다 (Chrome은 폴더를 요구하지만 Firefox는 그렇지 않습니다). Firefox는
재시작 시 임시 부가 기능을 제거하므로 매 세션마다 반복해야 합니다 — 아직 AMO 등록이 없어
릴리스 zip은 서명되지 않았으며 영구적으로 설치할 수 없습니다.
임시 로드에서는 아무것도 묻지 않으므로, 무엇을 수집하는지 알아두세요. Firefox는 정상적으로 설치된 서명된 부가 기능에 대해서만 데이터 수집 고지를 표시하며,
about:debugging은 모든 권한을 조용히 부여합니다. 이 확장 프로그램은 브라우징 활동(캡처 시 탭의 URL과 제목이 포함됨)과 웹사이트 콘텐츠(스크린샷, 그리고 Push가 스크래핑하는 행들)를 선언합니다. 이 확장 프로그램은 사용자가 설정한 컴패니언 주소로만 전송하며 다른 곳으로는 보내지 않습니다. 컴패니언이 이후에 전달하는 내용 — 비전 모델이 스크린샷을 읽고, AI 합성이 행들을 읽고, 보강 기능이 평판 서비스를 조회하는 것 — 은 컴패니언 자체의 설정에 따릅니다. 자세한 내용은 extension/PRIVACY.md를 참조하세요.
팝업은 기존 케이스에만 연결됩니다 — 케이스는 대시보드에서 생성합니다.
http://127.0.0.1:4773/dashboard를 열고 + New case를 클릭하여 케이스를 생성합니다(자동으로
연결됩니다). 그런 다음 확장 프로그램 팝업에서 Case 드롭다운으로 해당 케이스를 선택하고
(Refresh cases를 눌러 목록을 갱신하세요) Start를 누릅니다. 증거를 브라우징하면 —
대시보드가 실시간으로 업데이트됩니다.기존 체크아웃을 업데이트하시나요?
git pull후companion/과extension/양쪽 모두에서npm install을 다시 실행하세요 — 새 기능이 의존성을 추가할 수 있습니다(예: 스크린샷 OCR 마스킹이tesseract.js를 추가했습니다). 그런 다음npm run dev를 재시작하세요(서버 코드는 시작 시 한 번만 로드됩니다).
전체 설정, HTTP 엔드포인트, 케이스 폴더 레이아웃, 분석 모델은 **companion/README.md**에 문서화되어 있습니다.
컴패니언 서버 + 대시보드 + 브라우저 부가 기능 전체를 하나의 컨테이너에서 실행합니다.
Ollama나 LiteLLM은 번들로 포함되지 않습니다; AI를 사용하려면 DFIR_AI_*를 OpenAI 호환
엔드포인트(직접 호스팅하는 모델, 원격 제공자, 또는 별도로 실행하는 Ollama/LiteLLM)로 지정하세요. AI를
설정하지 않아도 컨테이너는 전체 캡처와 모든 결정적 임포터를 수행합니다.
사전 요구 사항: Compose 플러그인이 포함된 Docker (
docker compose version).
설계상 로컬호스트 전용: 컨테이너는 내부적으로 0.0.0.0에 바인딩되지만, Compose는
호스트의 127.0.0.1에 포트를 게시합니다 — 따라서 대시보드가 네트워크에 노출되지 않습니다.
또는 빌드하는 대신 GHCR에서 미리 빌드된 이미지를 가져오세요: ``` docker compose pull && docker compose up -d
2. **애드온 로드** (캡처). 컨테이너는 첫 시작 시 미리 빌드된, 압축 해제된 확장 프로그램을
`./addon`에 기록합니다. Chrome/Comet에서 `chrome://extensions`를 열고 **개발자
모드**를 활성화한 후, **압축 해제된 확장 프로그램 로드**를 클릭하고 **`./addon/dist`**를 선택하세요 (패키징된
`dfir-companion-extension.zip`도 그곳에 생성됩니다).
3. `http://127.0.0.1:4773/dashboard`를 열고 **+ 새 케이스**를 클릭한 다음, 확장 프로그램 팝업에서 해당 케이스를 선택하고 **시작**을 누르세요.
**데이터 및 구성:**
- 증거와 케이스 상태는 호스트의 **`./cases`** (마운트된 볼륨)에 유지됩니다 — 재시작과 이미지 재빌드 후에도 보존됩니다.
- [`docker-compose.yml`](https://github.com/hasamba/dfir-companion/blob/master/docker-compose.yml)의 `environment:` 블록을 통해 구성하거나,
`env_file: - .env`의 주석을 해제하여 `.env` 파일을 사용하세요 (`companion/.env.example`을 복사하세요).
- 호스트에서 실행 중인 AI 엔드포인트에 접근하려면 `http://host.docker.internal:<port>/v1`을 사용하세요
(Docker Desktop이 없는 Linux에서는 compose 파일의 `extra_hosts` 줄도 주석 해제하세요).
## Windows (Chocolatey)
[Chocolatey](https://chocolatey.org/)로 포터블 Windows 빌드를 설치하세요 — Node.js가
필요하지 않습니다. 관리자 권한 셸에서:```
choco install dfir-companion
dfir-companion # → http://127.0.0.1:4773/dashboard
choco upgrade dfir-companion은 다음 릴리스를 가져옵니다. choco uninstall dfir-companion은
바이너리와 PATH shim을 제거합니다. 설치 프로그램은 Releases 페이지에
게시된 동일한 portable zip을 다운로드하고 SHA256을 검증합니다.
데이터는 사용자 프로필에 저장되며, 관리자 소유의 설치 디렉터리에는 저장되지 않습니다. 케이스는
%LOCALAPPDATA%\DFIR-Companion\cases에, 설정은 %LOCALAPPDATA%\DFIR-Companion\.env에
저장됩니다(예제에서 시드되며, AI / threat-intel 키를 위해 편집하세요 — 모두 선택 사항). 제거 시에도
해당 폴더는 유지되므로 증거가 절대 삭제되지 않습니다. 방화벽 규칙은 생성되지 않습니다 — 서버는
127.0.0.1에만 바인딩됩니다.
캡처 확장 프로그램은 오프라인 설치를 위해 %LOCALAPPDATA%\DFIR-Companion\extension에
디스크로 번들로 제공됩니다(에어갭 워크스테이션에서 유용) — chrome://extensions → 개발자 모드 →
압축해제된 확장 프로그램 로드 → 해당 폴더를 통해 로드하거나, 게시되면 Chrome 웹 스토어에서
설치하세요. 브라우저에 자동으로 설치되지는 않습니다.
아직 Chocolatey 커뮤니티 저장소에 없나요? 게시되기 전까지는 릴리스에서
dfir-companion.<version>.nupkg를 받아 해당 폴더에서choco install dfir-companion --source .를 실행하세요. 패키징은packaging/chocolatey/에 있습니다.
Releases 페이지에서
dfir-companion-<version>-x86_64.AppImage를 다운로드한 후:```
chmod +x dfir-companion--x86_64.AppImage
./dfir-companion--x86_64.AppImage # → http://127.0.0.1:4773/dashboard
Node가 필요하지 않습니다 — 서버, 대시보드, 이미지 도구를 모두 번들로 포함합니다. **데이터는 실행한 디렉터리에 저장됩니다:** `cases/`(증거 + 상태)와 선택적 `.env`(AI / 위협 인텔리전스 설정)가 AppImage를 실행한 위치 옆에 생성/읽기됩니다. `DFIR_CASES_ROOT`(절대 경로)와 `DFIR_ENV_FILE`(설정 파일의 절대 경로)로 재정의할 수 있습니다.
### 데이터가 저장되는 위치
| 설치 방식 | 케이스 + 상태 | 설정 (`.env`) |
| ---------------------- | ------------------------------------- | ------------------------------------- |
| 소스 / `npm run dev` | `companion/cases/` | `companion/.env` |
| 포터블 Windows EXE | EXE 옆의 `cases/` | EXE 옆의 `.env` |
| Windows (Chocolatey) | `%LOCALAPPDATA%\DFIR-Companion\cases` | `%LOCALAPPDATA%\DFIR-Companion\.env` |
| Linux AppImage | `$PWD/cases` (실행 디렉터리) | `$PWD/.env` (또는 `DFIR_ENV_FILE`) |
| Docker / Compose | 마운트된 `./cases` 볼륨 | `environment:` / `--env-file` |
모든 위치는 `DFIR_CASES_ROOT`(절대 경로)로 재정의할 수 있습니다.
## 환경 변수 (`companion/.env`)
모든 companion 동작은 환경 변수(`companion/.env` 또는 셸)로 설정됩니다. 시작하려면 `companion/.env.example`을 복사하세요 — 모든 변수에 대한 인라인 주석이 포함되어 있습니다.
### 핵심
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_CASES_ROOT` | `./cases` | 케이스 폴더 위치; 상대 경로는 `companion/`을 기준으로 해석됨 |
| `DFIR_PORT` | `4773` | 서버 포트 (확장 프로그램 및 대시보드와 일치해야 함) |
| `DFIR_HOST` | `127.0.0.1` | 바인드 인터페이스. 인증되지 않은 비루프백 바인드는 거부됨; Docker Compose는 호스트 루프백 전용 예외를 문서화함 |
| `DFIR_MAX_BODY_MB` | `256` | 최대 업로드 크기(MB); 대용량 SIEM/EDR 내보내기가 HTTP 413으로 실패하면 값을 높이세요 |
| `DFIR_ALLOWED_ORIGINS` | _(없음)_ | API를 호출할 수 있는 추가 브라우저 오리진, 쉼표로 구분. 캡처 확장 프로그램, 루프백, 그리고 companion 자체가 서비스한 모든 오리진은 항상 신뢰되므로 localhost/LAN/Docker에는 설정이 필요 없음; 그 외 모든 웹 오리진은 거부됨. `Origin`을 보내지 않는 호출자(curl, 스크립트, Velociraptor)는 영향을 받지 않음. 대시보드가 **호스트 이름**에서 서비스될 때 필요 — 리버스 프록시 또는 호스팅 배포 |
| `DFIR_ALLOWED_HOSTS` | _(없음)_ | 이 companion이 응답하는 추가 호스트 이름, 쉼표로 구분. 루프백과 순수 IP 주소는 항상 허용되므로 localhost, Docker, 그리고 `http://192.168.1.50:4773`으로 LAN을 통해 대시보드에 접근하는 경우에는 설정이 필요 없음. 목록에 없는 모든 **이름**은 거부됨 — 이것이 DNS 리바인딩(악성 사이트가 자체 도메인을 사용자 머신으로 지정하는 것)을 막는 방법임. 리버스 프록시가 `DFIR_ALLOWED_ORIGINS`에 넣은 오리진과 다른 `Host`를 전달할 때 설정하세요 |
| `DFIR_ALLOWED_HOST_SUFFIXES` | _(없음)_ | 위와 동일하지만 도메인 접미사로 매칭됨, 예: `.lab.example.com`, 세션마다 새 호스트 이름을 생성하는 플랫폼용. 매칭은 레이블 경계에서 이루어지므로 `.acme.com`은 `evilacme.com`과 절대 매칭되지 않음 |
| `DFIR_LOG_LEVEL` | `info` | 로그 상세 수준 (`debug`/`info`/`warn`/`error`). 콘솔 + `logs/session-<time>.log`(전역) + `cases/<id>/logs/session-<time>.log`(케이스별)에 기록됨. `debug`는 AI 호출, 캡처, OCR, 익명화, 보강을 추적함. 설정 → 로그 상세 수준에서 실시간(재시작 없이) 변경 가능 |
| `DFIR_LOG_DIR` | 케이스 루트 옆의 `logs/` | **전역** 세션 로그용 폴더. 상대 경로는 `companion/`을 기준으로 함. 케이스별 로그는 항상 케이스 폴더에 유지됨 |
### 인증 (선택적 팀 배포)
`DFIR_AUTH_MODE=team`은 OIDC/로컬 로그인, 보안 브라우저 세션, 케이스별 역할, 케이스 범위 서비스 ID를 활성화합니다. 인증 및 ID 공급자 설정은 배포 보안 제어입니다: `.env` 또는 시크릿 저장소에서 구성한 후 재시작하세요. 전체 변수 목록, HTTPS 설정, 최초 관리자 부트스트랩, 역할 매트릭스, 확장 프로그램 토큰, 단일 작성자 프로세스 모델은 [팀 계정 및 케이스 역할 가이드](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/team-authentication.md)를 참조하세요.
### AI — 추출 (분석을 활성화하려면 필수)
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_VISION_PROVIDER` | — | `openai` \| `openrouter` \| `ollama` \| `litellm` \| `gemini` \| `anthropic` \| `claude-code`; 미설정 = 캡처 전용 |
| `DFIR_VISION_MODEL` | — | 모델 ID (예: `gpt-4o-mini`, `gemini-2.5-flash`); 스크린샷 추출을 위해 **비전을 지원해야 함** |
| `DFIR_VISION_KEY` | — | 공급자 API 키; 인증 없는 로컬 프록시 또는 `claude-code`(로그인된 `claude` CLI 구독을 대신 사용)의 경우 비워 두세요 |
| `DFIR_AI_CLAUDE_CODE_BIN` | PATH의 `claude` | `claude-code` 전용: `claude` 바이너리가 PATH에 없을 경우 절대 경로 |
| `DFIR_VISION_BASE_URL` | 공급자 기본값 | 기본 URL 재정의 — 로컬 LiteLLM 프록시 또는 모든 OpenAI 호환 엔드포인트용 |
| `DFIR_AI_TIMEOUT_MS` | `900000` | 요청당 타임아웃(ms); CLI 공급자(claude-code, codex)는 대용량 타임라인에서 몇 분이 필요함 |
| `DFIR_AI_MAX_TOKENS` | `16000` | 최대 완료 토큰; 너무 낮으면 합성이 잘리고, 잔액이 낮을 때 OpenRouter 402를 방지함 |
| `DFIR_AI_SYNTH_MAX_EVENTS` | `600` | 합성에 전송되는 포렌식 이벤트 상한; Critical/High는 항상 발견 사항을 받음 |
| `DFIR_REPORT_SYNTH_COVERAGE` | _(꺼짐)_ | 참으로 설정하면 보고서에 **§3.4 합성 커버리지** 각주를 추가 — "창 내 이벤트 M개 중 N개 고려됨(K개 생략: 예산/필터링)", 토큰 추정치, 안전망 백필이 복구한 고심각도 누락 수. 대시보드 synth-meta 카드는 항상 이 줄을 표시함; 이 플래그는 내보낸 보고서에도 나타날지 여부만 제어함 |
| `DFIR_REPORT_MODEL_PERF` | _(꺼짐)_ | 참으로 설정하면 보고서에 **§3.5 모델 성능** 각주를 추가 — 합성 모델, 발견 사항 수 대 안전망 백필이 추가해야 했던 수, 파싱 재시도, 그리고 (2차 의견이 실행된 경우) `DFIR_AI_SECOND_OPINION_MODEL`이 `DFIR_AI_MODEL`/`DFIR_AI_SYNTH_MODEL`과 일치한 빈도. 대시보드 synth-meta 카드는 항상 이를 표시함; 이 플래그는 내보낸 보고서에도 나타날지 여부만 제어함 |
| `DFIR_AI_CONTEXT_TOKENS` | `128000` | 모델 컨텍스트 창; Claude/Gemini(200k/1M)의 경우 값을 높여 호출당 더 많이 전송 |
| `DFIR_VISION_IMAGE_DETAIL` | `high` | `high` \| `low` \| `auto` (OpenAI/OpenRouter); `high`는 작은 텍스트 OCR을 위해 전체 해상도로 타일링함 |
| `DFIR_AI_AUTO_SYNTHESIZE` | `on` | 캡처 중 재합성: `on` \| `off` |
| `DFIR_AI_AUTO_SYNTHESIZE_MS` | `8000` | 자동 합성이 실행되기 전 디바운스 창(ms) |
| `DFIR_FLUSH_INTERVAL_MS` | `300000` | 남은 캡처 버퍼의 안전망 플러시(ms); `0`은 비활성화 |
| `DFIR_ANONYMIZE` | `on` | AI 호출 전에 피해자 IP/호스트/사용자/경로를 토큰화: `on` \| `off` |
| `DFIR_PRESIDIO_URL` | _(미설정)_ | 선택 사항: 이미 마스킹된 텍스트에서 정규식으로 잡을 수 없는 이름 및 기타 PII를 스캔하는 자체 실행 [Presidio](https://github.com/hasamba/dfir-companion/blob/master/mkdocs-docs/reference/presidio.md) Analyzer 컨테이너의 기본 URL (예: `http://localhost:5002`). 미설정 = 기능 꺼짐. |
| `DFIR_PRESIDIO_MIN_SCORE` | `0.6` | Presidio 발견 사항의 신뢰도 하한(0–1); 비어 있거나 숫자가 아니면 기본값으로 대체되고, 범위를 벗어난 값은 클램프됨 |
| `DFIR_PRESIDIO_TIMEOUT_MS` | `60000` | 하나의 `/analyze` 요청 예산(스캔은 청크로 나뉘며 각 청크가 전체 예산을 받음). 느리거나 공유된 분석기의 경우 값을 높이세요; 비어 있거나 숫자가 아니거나 ≤0이면 기본값으로 대체됨 |
> 위의 스크린샷/비전 변수(`DFIR_VISION_PROVIDER` / `DFIR_VISION_MODEL` / `DFIR_VISION_KEY` / `DFIR_VISION_BASE_URL` / `DFIR_VISION_IMAGE_DETAIL`)는 `DFIR_AI_*` 접두사에서 이름이 변경되었습니다; 레거시 `DFIR_AI_PROVIDER` / `DFIR_AI_MODEL` / `DFIR_AI_KEY` / `DFIR_AI_BASE_URL` / `DFIR_AI_IMAGE_DETAIL` 이름은 여전히 더 이상 사용되지 않는 폴백으로 작동합니다(둘 다 설정된 경우 새 이름이 우선함).
**Claude Code** — `claude` CLI를 통해 로그인된 Claude 구독을 사용하며 API 키가 필요 없습니다; 비전 + 텍스트(스크린샷 추출 *및* 합성)를 처리합니다. 호스트에 `claude` CLI가 설치되어 있고 `claude auth login`이 완료되어 있어야 합니다. 구독 요금 제한을 소모합니다(과도한 추출은 이를 소진할 수 있음); 보고되는 비용은 API 환산 비용이며 실제 지출이 아닙니다. 설정 → AI는 연결 상태(설치되지 않음 / 연결되지 않음 / 연결됨)와 원클릭 연결 작업을 표시합니다.
### AI — 텍스트 모델 (2계층, 선택 사항)
분할은 **비전 대 텍스트**입니다: `DFIR_VISION_MODEL`은 스크린샷을 읽고(멀티모달이어야 함), `DFIR_AI_SYNTH_*` 모델은 **모든 텍스트 작업** — CSV 추출, 로그 분류, 합성, 질문/설명 — 을 수행합니다. 미설정 시 텍스트 작업은 `DFIR_VISION_MODEL`을 재사용합니다.
**Codex** — `DFIR_AI_SYNTH_PROVIDER=codex`로 설정하면(velo / 2차 의견 공급자에도 유효) 로컬 OpenAI **Codex CLI**(`codex exec`)를 통해 텍스트 작업을 실행하며, 주변 codex 인증 — `codex login` 또는 `OPENAI_API_KEY`, **`DFIR_AI_KEY` 불필요** — 을 사용합니다. Codex는 **텍스트 전용**이므로(스크린샷을 읽을 수 없음) 추출을 위해 비전 공급자와 함께 사용하세요; 데이터를 OpenAI로 전송합니다(비로컬). `@openai/codex`가 설치되어 있어야 합니다. 선택적 `DFIR_AI_CODEX_BIN`은 PATH에 없는 `codex`를 가리킵니다. 설정 → AI는 codex 연결 상태(설치되지 않음 / 연결되지 않음 / 연결됨)와 원클릭 연결 작업을 표시합니다.
권장: 스크린샷에는 저렴한 비전 모델, 텍스트에는 강력한 추론 모델. 텍스트 모델에서 아끼지 마세요 — 약한 모델은 로그 분류를 *조용히* 실패하여 잘못된 이벤트 대신 아무 이벤트도 반환하지 않습니다(`npm run eval:real`이 바로 이것을 측정함).
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_AI_SYNTH_PROVIDER` | = `DFIR_VISION_PROVIDER` | 텍스트 작업(CSV/로그/합성)용 공급자 |
| `DFIR_AI_SYNTH_MODEL` | = `DFIR_VISION_MODEL` | 텍스트 모델 ID — CSV/로그 추출 + 합성 (예: `gpt-4o`, `gemini-2.5-pro`, `claude-sonnet-4-6`) |
| `DFIR_AI_SYNTH_KEY` | = `DFIR_VISION_KEY` | 텍스트 모델 API 키 |
| `DFIR_AI_SYNTH_BASE_URL` | = `DFIR_VISION_BASE_URL` | 합성 기본 URL |
### AI — Velociraptor 헌트 모델 (선택 사항)
**오직** Velociraptor VQL 헌트 생성에만 사용되는 전용 모델(*Suggest Velociraptor hunts* / *Fleet Hunts* 기능), 추출/합성/OCR과 별개 — 많은 모델이 VQL을 망칩니다. **설정 → AI**에서도 편집 가능합니다.
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_AI_VELO_PROVIDER` | `openrouter` | VQL 헌트 생성용 공급자 |
| `DFIR_AI_VELO_MODEL` | `anthropic/claude-haiku-4.5` | VQL 헌트 생성용 모델 ID |
| `DFIR_AI_VELO_KEY` | = `DFIR_VISION_KEY` | API 키 (비어 있으면 기본 키를 재사용) |
| `DFIR_AI_VELO_BASE_URL` | = `DFIR_VISION_BASE_URL` | 기본 URL 재정의 |
### AI — 사용자 정의 프롬프트 (선택 사항)
각 프롬프트에는 두 가지 재정의 형식이 있습니다(우선순위 순): `DFIR_AI_<NAME>_PROMPT`(인라인 텍스트, 시작 시 읽음) 및 `DFIR_AI_<NAME>_PROMPT_FILE`(파일 경로, 호출마다 다시 읽음 — 편집하면 즉시 적용됨). `npm run prompts:eject`는 내장 기본값을 시작점으로 기록합니다.
| 프롬프트 이름 | `<NAME>` 토큰 |
|---|---|
| 스크린샷별 추출 | `SYSTEM` |
| CSV 가져오기 분류 | `CSV` |
| 로그 가져오기 분류 | `LOG` |
| 전체적 합성 | `SYNTH` |
| 케이스 Q&A | `ASK` |
| 경영진 요약 | `EXEC` |
| 내러티브 타임라인 | `NARRATIVE` |
| 제안된 플릿 헌트 | `HUNTS` |
| 제안된 플레이북 헌트 | `PBHUNTS` |
| 타임라인 격차 가설 | `GAPHYP` |
| 쿼리 번역기 (NL → 쿼리) | `QUERYXLATE` |
### 위협 인텔리전스 보강 (선택 사항 — 기본적으로 꺼짐)
키를 추가하면 해당 공급자가 활성화됩니다. 모든 외부 공급자는 대시보드에서 케이스별로 옵트인합니다.
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_VT_KEY` | — | VirusTotal API 키 (해시 / IP / 도메인 / URL) |
| `DFIR_HUNTINGCH_KEY` | — | Hunting.ch용 abuse.ch Auth-Key (MalwareBazaar · ThreatFox · URLhaus · YARAify); `DFIR_MB_KEY`로 대체됨 |
| `DFIR_MB_KEY` | — | 레거시 abuse.ch 키 — Hunting.ch를 구동; `DFIR_HUNTINGCH_KEY` 선호 |
| `DFIR_ABUSEIPDB_KEY` | — | AbuseIPDB API 키 (IP 평판) |
| `DFIR_CROWDSTRIKE_CLIENT_ID` | — | CrowdStrike Falcon TI OAuth2 클라이언트 ID |
| `DFIR_CROWDSTRIKE_CLIENT_SECRET` | — | CrowdStrike OAuth2 시크릿 (*Indicators: Read* + *MalQuery: Read* 필요) |
| `DFIR_CROWDSTRIKE_CLOUD` | `us-1` | 테넌트 클라우드: `us-1` \| `us-2` \| `eu-1` \| `gov-us-1` \| `gov-us-2` |
| `DFIR_CROWDSTRIKE_BASE_URL` | 클라우드에서 | 명시적 API 기본 URL (`DFIR_CROWDSTRIKE_CLOUD` 재정의) |
| `DFIR_ROCKYRACCOON_KEY` | — | Windows 프로세스 유행도 / LOLBIN / ATT&CK용 RockyRaccoon 키 |
| `DFIR_MISP_URL` | — | MISP 인스턴스 URL — 보강 및 푸시 모두 URL + 키 필요 |
| `DFIR_MISP_KEY` | — | MISP API 인증 키 |
| `DFIR_MISP_CA` | — | 내부 CA MISP용 PEM CA 번들 (검증은 계속 켜짐) |
| `DFIR_MISP_INSECURE` | — | `=1`이면 TLS 검증 건너뜀 (실험실 전용) |
| `DFIR_MISP_DISTRIBUTION` | `0` | 새 이벤트 배포: `0`=org, `1`=community, `2`=connected, `3`=all |
| `DFIR_MISP_ANALYSIS` | `1` | 새 이벤트 분석 상태: `0`=initial, `1`=ongoing, `2`=complete |
| `DFIR_MISP_TIMELINE_LIMIT` | `5000` | 푸시당 최대 포렌식 타임라인 이벤트; 상한을 넘으면 가장 심각한 것이 유지되고 푸시가 경고함 |
| `DFIR_YETI_URL` | — | YETI 인스턴스 URL — URL + 키 모두 필요 |
| `DFIR_YETI_KEY` | — | YETI API 키 |
| `DFIR_YETI_CA` | — | 내부 CA YETI용 PEM CA 번들 |
| `DFIR_YETI_INSECURE` | — | `=1`이면 TLS 검증 건너뜀 (실험실 전용) |
| `DFIR_OPENCTI_URL` | — | OpenCTI 인스턴스 URL — URL + 키 모두 필요 (hash/ip/domain/url) |
| `DFIR_OPENCTI_KEY` | — | OpenCTI API 토큰 |
| `DFIR_OPENCTI_CA` | — | 내부 CA OpenCTI용 PEM CA 번들 |
| `DFIR_OPENCTI_INSECURE` | — | `=1`이면 TLS 검증 건너뜀 (실험실 전용) |
| `DFIR_OPENCTI_MALICIOUS_SCORE` | `75` | 악성 판정을 위한 `x_opencti_score` 임계값 |
| `DFIR_RDAP_URL` | `https://rdap.org` | WHOIS-over-RDAP 기본 (키 불필요; 소유 RIR로의 IANA 부트스트랩) |
| `DFIR_GEOIP_URL` | `https://ipinfo.io/{ip}/json` | GeoIP URL 템플릿 (키 불필요 HTTPS; `{ip}` 치환됨; 파서는 ip-api.com + ipwho.is도 허용) |
| `DFIR_GEOIP_KEY` | — | 선택적 GeoIP 키 (`{key}` 채움, 그렇지 않으면 `?token=`으로 추가) 유료/자체 호스팅 백엔드용 |
| `DFIR_SHODAN_KEY` | — | Shodan API 키 — Shodan 호스트 조회 IP 보강기도 구동 (고객 노출과 공유) |
| `DFIR_HASHLOOKUP_URL` | `https://hashlookup.circl.lu` | CIRCL hashlookup 기본 (해시 IOC용 키 불필요 알려진 파일 조회); 자체 호스팅 / 에어갭 미러용 재정의 |
| `DFIR_ENRICH_DELAY_MS` | `1500` | 조회 간 스로틀(ms) |
| `DFIR_ENRICH_JITTER_MS` | `0` | 호출 간 대기에 추가되는 ± 무작위 지터(ms); 정렬/병렬 실행이 모두 함께 공급자의 요금 제한 창에 도달하지 않도록 분산시킴 |
| `DFIR_ENRICH_RETRIES` | `2` | 429에 도달한 공급자 호출의 재시도 횟수, 공급자가 `Retry-After`를 보내면 이를 준수한 후 오류로 집계됨 |
| `DFIR_ENRICH_RETRY_BACKOFF_MS` | `1000` | 공급자가 `Retry-After`를 주지 않았을 때 첫 429 재시도 전 기본 백오프(시도마다 두 배, 최대 30초) |
| `DFIR_ENRICH_MAX` | `100` | 보강 배치당 최대 조회 IOC 수 (해시/IP 우선) |
| `DFIR_ENRICH_MAX_BATCHES` | `20` | 하나의 보강 실행이 연쇄할 수 있는 상한 배치 수. IOC가 `DFIR_ENRICH_MAX`보다 많은 케이스는 더 이상 상한에서 멈추지 않음: 실행이 저장된 후 중단된 지점에서 다음 배치를 시작하며, 이 횟수까지 진행됨. `1`은 이전 단일 실행 동작을 복원함. 상한이 여전히 남기는 것은 상태 줄에 보고되며 조용히 삭제되지 않음 |
| `DFIR_ENRICH_HEALTH_TTL_MS` | `60000` | 자체 호스팅 공급자의 up/down 판정 캐시(ms) |
| `DFIR_ENRICH_HEALTH_POLL_MS` | `60000` | 다운된 공급자의 재탐색 간격; `0`은 백그라운드 폴러 비활성화 |
### 고객 노출 (선택 사항)
**피해자 조직 자체의** 도메인/이메일을 유출 데이터베이스와 대조 확인 — 공격자/IOC 도메인은 절대 아님.
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_HIBP_KEY` | — | Have I Been Pwned API 키 |
| `DFIR_HIBP_USER_AGENT` | `DFIR Companion` | HIBP User-Agent 헤더 |
| `DFIR_LEAKCHECK_KEY` | — | LeakCheck Pro API 키 |
| `DFIR_LEAKCHECK_DOMAIN_LIMIT` | `1000` | 도메인 검색당 최대 레코드 수 |
| `DFIR_DEHASHED_KEY` | — | DeHashed v2 API 키 |
| `DFIR_DEHASHED_BASE_URL` | DeHashed 기본값 | DeHashed API 기본 URL 재정의 |
| `DFIR_SHODAN_KEY` | — | Shodan 키 (도메인 → 노출된 호스트 / 포트 / CVE; 이메일 조회 없음) |
| `DFIR_EXPOSURE_DELAY_MS` | `1500` | 공급자 조회 간 스로틀(ms) |
### DFIR-IRIS 푸시 / 가져오기 (선택 사항)
활성화하려면 URL과 키가 모두 필요합니다. 동일한 연결이 **Push to DFIR-IRIS**와 **Import from IRIS**(기존 IRIS 케이스의 자산/IOC/타임라인을 케이스로 가져오기)를 모두 구동합니다.
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_IRIS_URL` | — | IRIS 인스턴스 URL |
| `DFIR_IRIS_KEY` | — | IRIS API 키 |
| `DFIR_IRIS_CA` | — | 내부 CA IRIS용 PEM CA 번들 |
| `DFIR_IRIS_INSECURE` | — | `=1`이면 TLS 검증 건너뜀 (실험실 전용) |
| `DFIR_IRIS_CUSTOMER_ID` | `1` | 새 IRIS 케이스용 고객 ID (푸시) |
| `DFIR_IRIS_CLASSIFICATION_ID` | `1` | 새 IRIS 케이스용 분류 ID (푸시) |
### Timesketch 푸시 (선택 사항)
푸시를 활성화하려면 URL + 사용자 + 비밀번호가 모두 필요합니다. JSONL로 내보내기는 설정 없이 작동합니다.
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_TIMESKETCH_URL` | — | Timesketch 인스턴스 URL |
| `DFIR_TIMESKETCH_USER` | — | 로컬 인증 사용자 이름 |
| `DFIR_TIMESKETCH_PASSWORD` | — | 로컬 인증 비밀번호 |
| `DFIR_TIMESKETCH_TIMELINE` | `DFIR-Companion Forensic Timeline` | 관리되는 타임라인 이름 |
| `DFIR_TIMESKETCH_CA` | — | 내부 CA Timesketch용 PEM CA 번들 |
| `DFIR_TIMESKETCH_INSECURE` | — | `=1`이면 TLS 검증 건너뜀 (실험실 전용) |
### Notion 내보내기 (선택 사항)
토큰만으로 활성화됩니다. 대상 페이지/데이터베이스를 통합과 공유하세요. "새 페이지"는 데이터베이스 또는 상위 페이지가 필요하며(환경 기본값 또는 내보내기마다 입력); "기존 페이지"는 붙여넣은 페이지를 업데이트합니다.
| 변수 | 기본값 | 의미 |
|---|---|---|
| `DFIR_NOTION_TOKEN` | — | 내부 통합 시크릿 (Notion: 설정 → 연결 → 직접 개발) |
| `DFIR_NOTION_DATABASE_ID` | — | "새 페이지" 내보내기용 기본 데이터베이스 (조사 템플릿) |
| `DFIR_NOTION_PARENT_PAGE_ID` | — | 대체 기본값: 이 상위 페이지 아래에 새 페이지 생성 |
| `DFIR_NOTION_CONTAINER_TITLE` | `🔍 DFIR Companion — Auto-generated` | Companion이 소유하는 관리 블록의 제목 |
| `DFIR_NOTION_MAX_TIMELINE` | `500` | Notion에 기록되는 최대 타임라인 행 수 |
| `DFIR_NOTION_CA` | — | 프록시가 내부 CA를 사용하는 경우 PEM CA 번들 |
| `DFIR_NOTION_INSECURE` | — | `=1`이면 TLS 검증 건너뜀 (실험실 전용) |
### Velociraptor 라이브 헌트 + 트리아지 번들 (선택 사항)
`DFIR_VELOCIRAPTOR_API_CONFIG`를 설정하면 활성화됩니다. 다음으로 설정을 한 번 생성하세요:```
velociraptor --config server.config.yaml config api_client --name dfir --role administrator,api api.config.yaml
트리아지 번들 (Settings → Velociraptor 탭): Browse server artifacts는 서버의 수집 가능한
CLIENT 아티팩트를 나열함; 이름이 지정된 번들을 구성 + 저장함 (세 가지가 기본 제공됨 — Best Practice (빠른 성과
일괄 점검), Super-Timeline Triage (원시 호스트 아티팩트, 슈퍼-타임라인으로만 라우팅됨), Linux
Triage — cases/ 옆의 bundles/에 전역으로 저장됨). 기본 제공을 포함한 모든 번들은 제자리에서 편집 가능함 —
편집하면 재정의가 저장됨; Reset to default는 이를 폐기함. 대시보드의 Fleet Collection 패널에서 하나를 헌트로 실행함 (선택적으로
include/exclude 레이블 + OS로 범위 지정, 그리고 최소 심각도 가져오기 하한). 수집 타임아웃은 번들
설정이며 (편집기에서 구성 — THOR 같은 느린 아티팩트의 경우 값을 높임; Velociraptor의 기본값은 600초), 모든 실행에
자동으로 적용됨. 각 헌트는 또한 상대 만료를 가짐 — 나중에 체크인하는 클라이언트에서 얼마나 오래 스케줄링을 유지할지 —
1 hour / 1 day / 1 week 중에서 선택함 (기본 1 hour, Velociraptor 자체의
일주일 기본값과 대비됨); 이는 편집기에서 설정하는 번들별 기본값이며 실행별로 재정의 가능함. 번들은 또한 아티팩트별 매개변수를 가질 수 있어 (헌트의
spec에 전달됨) 무거운 아티팩트가 소스에서 더 적게 내보내도록 함 — Best Practice는 **Hayabusa를 RuleLevel=Critical/High/Medium
RuleStatus=Stable+Experimental로 고정**하여 가져오기를 범람시키지 않도록 함; 빌더의 선택적 Advanced → parameters JSON을 통해
모든 아티팩트를 조정하고, 아티팩트별 제외 필터 (VQL WHERE, 예: NOT OSPath =~ 'pagefile')로 노이즈가 많은 행을 제거함. 헌트는 만료까지 열린 상태로 유지되므로,
Companion이 DFIR_VELO_HUNT_WAIT_MIN 후 자동 수집하고 결과 행 과 업로드된
JSON 보고서 (예: Generic.Scanner.ThorZIP를 통한 THOR/Hayabusa — 이들의 경우 행은 중요하지 않고
업로드된 JSON이 중요함; 자동 감지되어 올바른 임포터로 라우팅됨) 둘 다를 가져온 다음 종합함 — 또는 라이브 작업 카드에서 Collect
now를 클릭하여 조기에 가져올 수 있음. 진행 중인 작업은 케이스별로 지속되며 (state/velo-hunt.json)
서버 재시작 후에도 유지됨; 결과는 대시보드 타임라인/IOC에 나타남.| 변수 | 기본값 | 설명 |
|---|---|---|
DFIR_MCP_MODEL | (CLI 기본값) | 단일 MCP 도구 호출에 사용되는 모델로, claude --model에 전달됨. |
DFIR_MCP_AGENT_MODEL | (CLI 기본값) | 에이전트 루프용 모델로, 에 전달됨. |
서버 등록은 단순한 구성이 아니라 보안 결정임 — Registering an MCP server를 참조.
새로운/에스컬레이션된 파인딩, 플레이북 업데이트, 조사 마일스톤을 Slack /
MS Teams 웹훅 또는 SMTP 이메일로 푸시함. 활성화 환경 변수는 없음 — 채널은
대시보드 (⚙ Settings → Notifications)에서 생성되며 cases/ 옆의 notifications/config.json에 저장됨
(gitignore됨; 웹훅 URL + SMTP 비밀번호를 보관함). 목록은 비어 있는 상태로 시작함 (옵트인). 각 채널에는
심각도 임계값과 이벤트별 토글 (파인딩 / 플레이북 / 마일스톤)이 있음. Test 버튼을 사용하여
채널을 엔드투엔드로 검증함.
⚠ OPSEC: 알림은 케이스 콘텐츠 (파인딩/작업 제목)를 제3자에게 전송함. 대상이 신뢰할 수 있는 경우가 아니면 민감한 케이스에서는 활성화하지 말 것.
Slack — Incoming Webhook 생성 (수동 OAuth 스코프 불필요; Slack이 incoming-webhook을 자동으로 추가함):
DFIR Companion) 워크스페이스를 선택.https://hooks.slack.com/services/T…/B…/…)을 복사.하나의 웹훅은 하나의 채널에 게시함 — 추가 채널마다 다른 웹훅 (그리고 다른 Companion 채널)을 추가할 것.
URL은 비밀임 (이를 가진 사람은 누구나 게시할 수 있음), 그래서 구성 파일이 gitignore되고 URL이
API 응답에서 가려짐. chat:write 같은 봇 토큰 스코프는 필요하지 않음 — Companion은
Web API가 아니라 incoming webhook을 통해 게시함.
MS Teams — 채널에 Incoming Webhook 커넥터 (또는 Power Automate "when a webhook request is received" 플로우)를
추가하고 그 URL을 붙여넣음 (Companion이 MessageCard를 전송함). SMTP 이메일 — 채널에 호스트/포트,
선택적 사용자 이름+비밀번호, from/to를 지정함; 제공되는 경우 기회적 STARTTLS + AUTH LOGIN이 사용됨. 빠른
로컬 테스트를 위해서는 Mailpit을 가리키게 함 (docker run -p 1025:1025 -p 8025:8025 axllent/mailpit).
Telegram — Bot API 토큰 + 채팅/채널/그룹 ID를 사용함:
/newbot을 실행한 뒤 토큰 (123456789:AAF…)을 복사./start를 보낸 뒤 https://api.telegram.org/bot<TOKEN>/getUpdates를 엶; chat.id는 양의 정수임.getUpdates를 엶; chat.id는 음의 정수임.@mychannel.@getidsbot으로 전달하여 숫자 ID를 얻음 (보통 -100…).war-room bot을 이미 실행 중인가? 토큰을 비워 두고
채팅 ID만 입력할 것 — 채널이 .env의 DFIR_TELEGRAM_BOT_TOKEN을 재사용하며, 필드에는 *(already set)*이 표시됨. 토큰은
.env에만 남으므로, 거기서 회전하면 이 채널도 함께 회전함. 다른 봇을 통해 보내려는 경우에만 여기에 토큰을 입력할 것; 그러면 이 채널에 대해
환경 변수 토큰을 재정의함.
여기에 입력한 토큰은 notifications/config.json (cases/ 옆)에 저장되며 브라우저로 절대 다시
에코되지 않음 — 대시보드는 설정 여부와 .env에서 왔는지만 알 수 있음.
알림은 밖으로 푸시함; 이것은 안으로 들어오는 방법임. 모든 질문마다 대시보드로 전환하는 대신 인시던트 채널에서 케이스를 운영함:``` /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
각 플랫폼은 해당 시크릿을 설정하면 활성화됩니다:
**터널이 필요하지 않습니다** — 컴패니언이 아웃바운드로 연결을 엽니다:
| 플랫폼 | 명령이 도착하는 방식 | 활성화 방법 |
|---|---|---|
| Slack | **Socket Mode — 아웃바운드 WebSocket** | `DFIR_SLACK_SOCKET_MODE=on` + `DFIR_SLACK_APP_TOKEN` (`xapp-…`, `connections:write`) |
| Telegram | **롱 폴링** | `DFIR_TELEGRAM_POLL=on` + `DFIR_TELEGRAM_BOT_TOKEN` |
또는 공인 주소가 필요한 인바운드 웹훅으로도 가능합니다:
| 플랫폼 | 엔드포인트 | 활성화 방법 |
|---|---|---|
| Slack | `POST /integrations/slack/command` | `DFIR_SLACK_SIGNING_SECRET` (Basic Information → Signing Secret) |
| MS Teams | `POST /integrations/teams/command` | `DFIR_TEAMS_TOKEN` (`Authorization` 헤더의 공유 시크릿) |
| Telegram | `POST /integrations/telegram/command` | `DFIR_TELEGRAM_SECRET_TOKEN` (`setWebhook`에 전달하는 `secret_token`) |
**Telegram은 터널이 필요하지 않습니다.** [@BotFather](https://t.me/BotFather)로 봇을 생성하고, 두 개의
변수를 설정한 뒤 재시작하고 메시지를 보내세요:```bash
DFIR_TELEGRAM_POLL=on
DFIR_TELEGRAM_BOT_TOKEN=123456789:AAF...
The companion calls Telegram and asks for new commands, so nothing about the machine is reachable
from the internet — the same outbound direction the notifier already uses. A bot can't do both:
clear any existing webhook with .../deleteWebhook first.
Slack Socket Mode is the same idea: enable Socket Mode on the app, mint an app-level token
(xapp-…, scope connections:write), and the companion dials out to Slack — no Request URL.
Webhook mode reaches this companion from the internet via your tunnel or reverse proxy — and that hostname must be in
DFIR_ALLOWED_HOSTS, or the DNS-rebinding guard turns the request away before the bot sees it. MS Teams has no outbound option, so it always needs this.
OPSEC — anyone who can post in the channel can pull case content. Password-protected cases are refused over chat entirely (a chat message carries no unlock). Set
DFIR_*_ACTION_USERSto keep AI spend, re-synthesis and re-binding to named responders; doing so also confines everyone else to the channel's bound case.
Example .env (two-tier OpenRouter setup):```
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
## npm 스크립트 — 전체 CLI 레퍼런스
모두 `companion/`에서 실행됩니다. `--` 뒤의 인수는 스크립트로 전달됩니다.
### `npm run dev`
서버를 시작합니다 (`.env`를 읽음). `127.0.0.1:4773`에 바인딩합니다. 대시보드는 `/dashboard`에 있습니다.```
npm run dev
npm run buildtsc로 타입 검사/컴파일합니다. 인수는 없습니다.```
npm run build
### `npm test`
전체 vitest 스위트를 실행합니다. 인수는 없습니다.```
npm test
npm run verify:ai -- [caseId] [flags]원콜 스모크 테스트: 케이스 중간에서 스크린샷 3장을 구성된 모델로 전송하고 응답이 스키마에 맞게 파싱되는지 확인합니다. 발견 사항, 포렌식 이벤트, 공격자 경로 미리보기를 출력합니다.
### `npm run coverage -- [caseId]`
케이스의 스크린샷 중 분석된 개수, 건너뛴 개수(중복), 그리고 전혀
처리되지 않은 개수를 보고합니다. `captures.jsonl`과 인덱싱된 조사 상태만 읽으며 — AI 호출은 없습니다.
| 인자 | 기본값 | 효과 |
| --- | --- | --- |
| `caseId` (위치 인자) | `test1` | 검사할 케이스. |```
npm run coverage -- test1
npm run coverage -- mycase
npm run reanalyze -- <caseId> [flags]이미 캡처된 케이스의 스크린샷에 대해 AI 분석을 다시 실행하여 조사 상태를 재구성합니다. --no-synthesis를 전달하지 않는 한 마지막에 종합(synthesis)을 실행합니다. API 할당량을 사용합니다(--window 스크린샷당 약 1회 호출, 추가로 종합 호출 1회).
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]`
전체 (범위 내) 포렌식 타임라인에 대한 텍스트 전용 AI 호출 한 번 → findings, IOCs,
MITRE 매핑, 공격자 경로, 핵심 질문. `DFIR_AI_SYNTH_*` 환경 변수를 우선 사용하며,
없으면 추출 모델로 폴백합니다.
| 인자 / 플래그 | 기본값 | 효과 |
| --- | --- | --- |
| `caseId` (위치 인자) | `test1` | 종합 분석할 케이스. |
| `--provider NAME` | `DFIR_AI_SYNTH_PROVIDER` ?? `DFIR_VISION_PROVIDER` | 종합 분석 제공자를 재정의합니다. |
| `--model ID` | `DFIR_AI_SYNTH_MODEL` ?? `DFIR_VISION_MODEL` | 종합 분석 모델을 재정의합니다. |
| `--key KEY` | `DFIR_AI_SYNTH_KEY` ?? `DFIR_VISION_KEY` | 종합 분석 API 키를 재정의합니다. |
| `--base-url URL` | `DFIR_AI_SYNTH_BASE_URL` ?? `DFIR_VISION_BASE_URL` | 종합 분석 base URL(예: 로컬 LiteLLM 프록시)을 재정의합니다. |```
# Use whatever .env says
npm run synthesize -- test1
# Re-run conclusions with a stronger model (no re-capture needed)
DFIR_DEDUP=off로 비활성화)DFIR_OCR_SEARCH=off로 비활성화; npm run ocr-index로 백필)127.0.0.1; 인식되지 않는 호스트명 거부, DNS 리바인딩 공격 차단(DFIR_ALLOWED_HOSTS)alert_fastyara -s -mscorethreat_level%ASA-#-######:<PRI>1 …Mmm dd …event.severity_label/api/events/api/sigma-alertslog show --style jsoncom.apple.quarantine.sfl2audit.k8s.iocolumnssnapshotpsortreport.json-r jsonmemory_payload.jsonyarascan_results.jsonl.eml.msg.bash_history.zsh_historyHISTTIMEFORMAT#epochaudit.logausearchaureportjournalctl -o json-o json-pretty-jalerts.jsonGET /security/eventsrule.levelcmd.exenltest, Get-AD*, ntdsutil … ifm 및 유사한 것들이 해당 기법과 함께 4104/4103 레코드에서 읽혀짐ssl/x509, Suricata tls)는 관계당 및 인증서당 한 행이 됨; DNS 응답은 TTL 내에서 동일 클라이언트의 이후 연결과 조인됨; 웹 요청 체인은 두 레코드가 모두 보유한 식별자를 통해서만 조인됨DFIR_SYNTH_ADVERSARY_HINTStags.yaml) — 규칙 엔진이 이벤트에 태그를 달고, 심각도를 높이고, MITRE 기법을 통합-enc, [Convert]::FromBase64String) 자동 디코딩; 숨겨진 IOC 추출; [Decoded] 블록 표시process_creation 규칙은 Sysmon / 4688 기록도 헌트POST /cases/:id/push를 통해 경보 푸시(SIEM 웹훅, Velociraptor 모니터, 스크립트)DFIR_FORENSIC_MIN_SEVERITY + 사례별 재정의로 구성 가능, 승격은 게이트를 우회, IOC는 여전히 모든 이벤트에서 추출됨DetectRaptor.Windows.Detection.MFT)별로 표시/숨김, 포렌식 및 슈퍼 타임라인 모두에서j/k가 포렌식 타임라인에서 포커스된 행 하이라이트를 이동, f는 별표, i는 수동 IOC 양식 미리 채우기, p는 인용된 발견 사항 고정, n은 댓글 열기, ?는 치트 시트 표시; 설정 → 일반에서 토글 가능, 기본 켜짐PUT /cases/:id/correlation-profile/dfir findings, /dfir iocs malicious, /dfir ask …; 채널을 사건에 바인딩하고 AI 예산을 사용할 수 있는 사람을 허용 목록에 추가 (#235)/mobile); 오프라인 앱 셸/cases/:id/present): 큰 카드, 키보드 탐색, 자동 진행, 심각도 필터, 보고서 템플릿 브랜딩; 자체 포함 오프라인 HTML 덱 내보내기 (#177)npm run seed-demo로 GlobalTech 시나리오 시드reanalyze, synthesize, coverage, verify:ai, clean-timeline| 변수 | 기본값 | 의미 |
|---|
DFIR_VELOCIRAPTOR_API_CONFIG | — | api_client 구성 파일 경로 |
DFIR_VELOCIRAPTOR_BINARY | velociraptor | 실행 파일 경로 (Windows에서는 전체 .exe 경로) |
DFIR_VELOCIRAPTOR_GUI_URL | — | 실행된 헌트로 딥링크하기 위한 GUI 기본 URL |
DFIR_VELOCIRAPTOR_ORG | root | 딥링크의 ?org_id=에 사용할 조직 (GUI가 # 프래그먼트 앞에 이를 요구함) |
DFIR_VELOCIRAPTOR_TIMEOUT_MS | 60000 | 쿼리당 타임아웃 (ms) |
DFIR_VELOCIRAPTOR_MAX_ROWS | 1000 | 대시보드로 반환되는 최대 행 수 |
DFIR_VELOCIRAPTOR_MAX_OUTPUT | 52428800 | 대화형 쿼리 출력 바이트에 대한 하드 상한 (50 MB) |
DFIR_VELOCIRAPTOR_COLLECT_MAX_OUTPUT | 268435456 | 번들-헌트 수집을 위한 더 큰 상한 (행 + 업로드된 JSON; THOR/Hayabusa는 용량이 큼). 이를 초과하는 아티팩트/업로드는 건너뛰며(로그 기록), 치명적이지 않음 — 나머지는 계속 가져옴. |
DFIR_VELO_HUNT_WAIT_MIN | 10 | 트리아지 번들 헌트가 자동 수집되기까지의 기본 분 (실행별 + 번들별 재정의 가능; 1–1440으로 제한) |
DFIR_VELOCIRAPTOR_UPLOAD_VQL | — | 고급: 헌트의 업로드된 텍스트 보고서(json/jsonl/ndjson/csv/txt/log; 버전에 민감; __HUNT_ID__ 플레이스홀더 유지)를 읽는 VQL 재정의 |
DFIR_VELOCIRAPTOR_FLOW_UPLOAD_VQL | — | 고급: 외부에서 붙여넣은 단일 플로우의 업로드된 보고서를 읽는 VQL 재정의 (__CLIENT_ID__/__FLOW_ID__ 플레이스홀더 유지) |
DFIR_HUNT_SUGGEST_MAX | 8 | 생성당 반환되는 AI 제안 플릿 헌트의 최대 개수 (AI 제공자 필요, Velociraptor API 아님) |
DFIR_PBHUNT_SUGGEST_MAX | 30 | 생성당 반환되는 AI 제안 플레이북 헌트의 최대 개수 (엔드포인트 관련 작업당 하나; AI 제공자 필요) |
claude --model| 변수 | 기본값 | 의미 |
|---|
DFIR_PUBLIC_URL | http://<host>:<port> | 알림을 케이스로 다시 딥링크하는 데 사용되는 공개 기본 URL (호스트명/프록시를 통해 접근할 때 설정) |
DFIR_NOTIFY_CA | — | 자체 호스팅 웹훅 호스트 (예: Mattermost)용 PEM CA 번들 |
DFIR_NOTIFY_INSECURE | — | 웹훅 호스트의 TLS 검증을 건너뛰려면 =1 (실습 전용) |
| Variable | Default | Meaning |
|---|
DFIR_SLACK_ACTION_USERS | (unset = open) | Comma-separated Slack user ids allowed to run ask/hunt/synthesize/bind |
DFIR_TEAMS_ACTION_USERS | (unset = open) | Same, for Teams |
DFIR_TELEGRAM_ACTION_USERS | (unset = open) | Same, for Telegram (numeric user ids) |
DFIR_SLACK_RESPONSE_HOSTS | hooks.slack.com | Extra hosts an async result may be delivered to (self-hosted Slack-compatible server) |
DFIR_TEAMS_RESPONSE_HOSTS | *.webhook.office.com, *.logic.azure.com, *.office.com | Same, for Teams |
DFIR_TELEGRAM_BOT_TOKEN | — | @BotFather token, used to deliver async results |
DFIR_TELEGRAM_API_BASE | https://api.telegram.org | Bot API base URL override |
| Variable | Default | Meaning |
|---|
DFIR_HUNT_PLATFORMS | all | Comma-separated platform allowlist for hunt-pivot cards: velociraptor, defender, elastic, splunk, sigma, yara, suricata |
DFIR_CORRELATE_WINDOW_S | 2 | Time window (s) for same-path cross-source event merge |
DFIR_PHASE_GAP_S | 300 | Gap between events (s) that starts a new attack phase |
DFIR_BEACON_MIN_COUNT | 5 | Minimum connection events to a (host → dest:port) channel before it's considered for beacon detection |
DFIR_BEACON_MAX_JITTER_PCT | 20 | Max interval jitter (stddev as % of mean) for a channel to count as a beacon — lower = stricter |
DFIR_GAP_MIN_MINUTES | 30 | Hard floor for log gap analysis — a timeline silence shorter than this is never flagged |
DFIR_GAP_DENSITY_FACTOR | 4 | A gap must also be ≥ this × the timeline's median inter-event interval to flag (suppresses normal quiet in sparse timelines; 0 = floor only) |
DFIR_GAP_ACTIVE_HOURS | (unset) | Optional working hours "8-18" (UTC, supports wrap-around "22-6") — flag only gaps overlapping them; supersedes the density heuristic when set |
DFIR_GAP_MAX_FINDINGS | 5 | Cap on complete-silence gaps that escalate to a finding (panel/report still show all) — stops a super-timeline case flooding the findings list |
DFIR_GAP_HYPOTHESIS_MAX | 5 | Max gaps the Hypothesize gaps AI call reasons about per run (worst-first); each still gets its shadow-artifact collections |
DFIR_GAP_HYPOTHESIS_CONTEXT | 8 | Events on each side of a gap fed to the hypothesis prompt as before/after context |
DFIR_DEDUP | on | Skip AI analysis of a screenshot only when it's byte-identical to the previous capture (SHA-256 exact match — the screen didn't change). Any difference is analyzed; still stored as evidence either way. Set off to analyze every screenshot |
TAGGER_AUTO | true | Content-based event tagger (Timesketch-style tags.yaml): run the ruleset automatically after every import, tagging matching events (and, on the forensic timeline, raising severity / unioning MITRE). Set false to only run it manually from the dashboard (Super-Timeline → 🏷 Content tagger → Run tagger) |
TAGGER_SCOPE | both | Which timeline the tagger runs over: forensic (curated timeline only), super (raw super-timeline only, tags only — never mutates severity/MITRE), or both. Tags are keyed by event id, so they filter in both timelines regardless |
TAGGER_RULES_FILE | (unset) | Absolute path to a custom rule file, overriding the dashboard-edited file and the bundled default (companion/data/tags.yaml). Edit rules in-app via Super-Timeline → 🏷 Content tagger → Edit rules |
| 인수 / 플래그 | 기본값 | 효과 |
|---|
caseId (위치 인수) | test1 | 스크린샷을 샘플링할 케이스. |
--provider NAME | .env에서 | 이 실행에 한해 DFIR_VISION_PROVIDER를 재정의합니다. |
--model ID | .env에서 | 이 실행에 한해 DFIR_VISION_MODEL을 재정의합니다. |
--key KEY | .env에서 | 이 실행에 한해 DFIR_VISION_KEY를 재정의합니다. |
| npm run verify:ai | ||
| npm run verify:ai -- mycase | ||
| npm run verify:ai -- mycase --provider openrouter --model openai/gpt-4o --key sk-or-... |
| 인수 / 플래그 | 기본값 | 효과 |
|---|
caseId (위치 인수) | test1 | 처리할 케이스. |
--reset | 꺼짐 | 분석 전에 상태를 비웁니다. 그렇지 않으면 기존 상태에 병합됩니다. |
--all | 꺼짐 | 중복 스크린샷도 포함합니다(가장 철저하지만 API 호출이 더 많음). |
--window N | 4 | AI 추출 호출당 스크린샷 수. |
--provider NAME | .env에서 | DFIR_VISION_PROVIDER(추출)를 재정의합니다. |
--model ID | .env에서 | DFIR_VISION_MODEL(추출)을 재정의합니다. |
--key KEY | .env에서 | DFIR_VISION_KEY(추출)를 재정의합니다. |
--base-url URL | .env에서 | DFIR_VISION_BASE_URL(추출)을 재정의합니다 — 예: 로컬 LiteLLM 프록시. |
--synth-provider NAME | = 추출 / DFIR_AI_SYNTH_PROVIDER | 종합 단계의 제공자. |
--synth-model ID | = 추출 / DFIR_AI_SYNTH_MODEL | 종합(발견 사항 / MITRE / 공격자 경로)을 위한 더 강력한 모델. |
--synth-key KEY | = 추출 / DFIR_AI_SYNTH_KEY | 종합 제공자의 API 키. |
--synth-base-url URL | = 추출 / DFIR_AI_SYNTH_BASE_URL | 종합 제공자의 기본 URL. |
--no-synthesis | 꺼짐 | 최종 종합 단계를 건너뜁니다(원시 포렌식 타임라인만). |