d6gv/threatwave

GitHub: d6gv/threatwave

一款混合确定性与语义关联的威胁情报知识图谱工具,通过 Neo4j 图谱和按需 AI 将多源 IOC 与安全报告关联并发现隐含关系。

Stars: 0 | Forks: 0

ThreatWeave — threat intelligence knowledge graph

确定性的威胁情报关联。AI 仅在其价值超出成本时使用。

Python 3.11+ FastAPI Neo4j PostgreSQL + pgvector Ruff License: MIT

ThreatWeave 接收来自多个来源的 IOC(IP、哈希、域名、URL)和网络安全报告,对它们进行标准化,并在知识图谱中进行关联。它与普通的 feed 聚合器的区别在于,它能够通过 embedding 的语义相似度,发现精确匹配所遗漏的关系,并根据需要用自然语言对这些关系进行解释。 ## 目录 - [架构原理](#architecture-principle) - [技术栈](#stack) - [项目布局](#project-layout) - [配置](#configuration) - [安装(开发环境)](#install-development) - [运行](#running) - [Web UI(交互式图谱)](#web-ui-interactive-graph) - [本地试运行](#try-it-locally) - [API](#api) - [自动化数据摄入(feed)](#automated-ingestion-feeds) - [摄入文档(CLI)](#ingesting-documents-cli) - [测试](#testing) - [数据与安全](#data--security) - [路线图](#roadmap) ## 架构原理 结构性关联是**确定性的**:一个图、一个 JOIN、一次边遍历。 AI 仅专门用于以下三项工作: 1. 从自由文本中提取 TTP/上下文(在摄入时), 2. 生成 embedding(在摄入时), 3. 编写解释性叙述(按需,在查询时)。 前两项在摄入时对每个数据仅处理一次;第三项仅在明确请求叙述时运行。**绝不**使用 LLM 来关联 IOC 或决定结构关系——这是图谱逻辑,在此处使用 AI 会给必须精确的数据增加成本和幻觉。所有 AI 访问都通过单一的、可替换的 `LLMProvider` 接口(`extract` / `embed` / `narrate`)进行。 **混合提取。** 明显的 IOC(IP、域名、哈希、URL)由确定性的 regex 解析器以零 token 成本提取。LLM *仅* 用于 regex 无法恢复的内容——TTP(映射到 MITRE ATT&CK)、归因的攻击者以及目标行业——因此两者绝不会重复工作。 **语义相似度是补充性的。** Embedding(在摄入时对每个 campaign 计算一次,并缓存在 pgvector 中)使图谱能够将那些读起来相似但不共享任何精确 IOC 的 campaign 关联起来。这通过带分数的 `semantic_similarity` 边*增强*了精确匹配的结构关联——它从不取代结构关联,并且 AI 依然只在摄入时对每个数据触及一次。 **叙述是按需生成的。** 自然语言解释仅在明确请求(`GET /api/narrative`)时生成,绝不会在摄入或常规查询期间生成——因此它们的成本随使用量扩展,而不是随数据量扩展。叙述完全基于已计算的子图(模型只能看到该证据),并且每个响应都会带有一个免责声明,表明它是提示性的并需要分析师验证。 **结构化 feed 不使用 AI。** Feed 连接器(OTX、abuse.ch URLhaus / MalwareBazaar / Feodo Tracker)已经在字段中提供了指示器,因此它们的摄入纯粹是标准化 + 批量的、幂等的 `MERGE`——每个 IOC **零**次 LLM 调用和**零**次 embedding。AI 依然专用于自由文本的 `ingest-doc`。(OTX pulse 的*描述*可以选择通过 `OTX_EMBED_DESCRIPTIONS` 进行 embedding 以用于语义搜索,默认关闭;测试会断言结构化 feed 不会调用 AI provider。)

ThreatWeave pipeline: sources feed ingestion; regex and LLM process in parallel and merge; Neo4j and pgvector store; deterministic correlation serves the FastAPI endpoints; narratives are generated only on demand

## 技术栈 - Python 3.11+, FastAPI - Neo4j(图),PostgreSQL + pgvector(embedding) - React + Vite + TypeScript 前端,带有 Cytoscape.js 图谱视图 - Docker Compose 用于本地基础设施 - 通过 regex/解析器进行确定性 IOC 提取(无 AI) - 结构化 feed 连接器(AlienVault OTX、abuse.ch URLhaus / MalwareBazaar / Feodo Tracker) - 定时、幂等的批量摄入(对 cron 友好的 CLI) - pytest、ruff、完整的类型提示 ## 项目布局 ``` src/threatweave/ ├── config.py # pydantic-settings, loaded from .env ├── models/ # domain models (IOC, Actor, Campaign, TTP, graph value objects) + normalization ├── parsers/ # deterministic regex IOC parser (+ refanging) ├── connectors/ # ingestion sources: OTX, abuse.ch (URLhaus/MalwareBazaar/Feodo), documents + shared retry ├── graph/ # GraphStore port (incl. batch UNWIND+MERGE upserts) + Neo4j and in-memory adapters ├── vector/ # VectorStore port + pgvector and in-memory adapters + factory ├── correlation/ # correlate() (structural + semantic) and similar() ├── ingest.py # OTX payload / structured feed / document -> graph (batched, + cached embeddings) ├── ingest_runner.py # multi-source scheduled ingestion: dedup, per-source isolation, AI-free feeds ├── ingest_state.py # persistent per-source run state (dedup hash + status), shared CLI <-> API ├── llm/ # LLMProvider interface + OpenAI provider, Ollama stub, cost, narrative, factory ├── cli.py # `threatweave` CLI (ingest, ingest-doc, demo) └── api/ # FastAPI app, routes and API-key/rate-limit gating (security.py) frontend/ # React + Vite single-page graph explorer ├── src/api/ # typed API client + error mapping ├── src/graph/ # Cytoscape styling + subgraph merge for expansion ├── src/hooks/ # useGraph (search/expand) and useNarrative (on-demand) └── src/components/ # SearchBar, GraphView, NodeDetailPanel, NarrativePanel, StatusBanner ``` 图谱建模了五种节点类型——`IOC`、`Actor`、`Campaign`、`TTP`、`Sector`——通过确定性的关系(`PART_OF`、`ATTRIBUTED_TO`、`RESOLVES_TO`、`USES`、`TARGETS`)连接,加上一个带权重的 `SEMANTIC_SIMILARITY` 边(携带余弦分数),在查询时根据 campaign 的 embedding 添加。 ## 配置 所有配置均来自环境变量。复制模板并填入值(切勿提交 `.env`): ``` cp .env.example .env ``` 关键变量:`NEO4J_*`、`POSTGRES_*`、`OTX_API_KEY`、`API_*`、`GRAPH_BACKEND`(`neo4j` | `memory`)和 `SEED_SAMPLE`。 **Feed 和定时摄入。** 每个源根据 `.env` 启用:`OTX_ENABLED` 和 `ABUSECH_ENABLED`(三个 abuse.ch feed 共享一个标志)。abuse.ch 将 URLhaus 和 MalwareBazaar 置于单一账户密钥(`ABUSECH_AUTH_KEY`)之后;Feodo Tracker 是公开的。`threatweave ingest --all` 将每个源的运行记录持久化到 `INGEST_STATE_PATH`(`data/ingest_state.json`,被 git 忽略),而 `INGEST_INTERVAL_MINUTES` 是驱动它的 cron/Task Scheduler 作业的推荐执行频率。 `OTX_EMBED_DESCRIPTIONS`(默认为 `false`)使定时的 OTX 摄入保持无 AI 调用;将其设置为 `true` 以便为语义搜索嵌入 pulse 描述。 **API 限制和速率限制。** `/api/*` 路由接受可选的 `X-API-Key` header,仅在设置了 `API_KEY` 时启用(未设置时——即演示模式——API 保持开放,因此它在没有任何密钥的情况下启动)。请将此密钥视为**日常限制,而非机密**:前端将其打包在 bundle 中(`VITE_API_KEY`),因此任何加载该页面的人都可以读取它。对于公共部署,真正的保护是针对每个客户端的速率限制(`API_RATE_LIMIT`,例如 `60/minute`),超过此限制时会返回 `429`。`/health` 始终保持开放。对于文档摄入,设置 LLM provider:`LLM_PROVIDER=openai`、`LLM_API_KEY=`、`LLM_MODEL=gpt-4o-mini`(加上可选的 `LLM_MAX_INPUT_CHARS`、`LLM_MAX_OUTPUT_TOKENS`、`LLM_MAX_RETRIES`)。对于语义相似度,启用向量后端:`VECTOR_BACKEND=pgvector`(或 `memory`),并设置 `LLM_EMBED_MODEL=text-embedding-3-small` 和 `LLM_EMBED_DIM=1536`。叙述使用一个独立的、质量更高的模型,可通过 `LLM_NARRATIVE_MODEL=gpt-5.4-mini` 进行配置。请查看 [.env.example](.env.example) 以获取完整列表。 ## 安装(开发环境) ``` python -m venv .venv # Windows: .venv\Scripts\activate | Unix: source .venv/bin/activate pip install -e ".[dev]" ``` ## 运行 ### 选项 A — 无 Docker,内存演示(最快) 使用 `data/samples/` 中的合成样本作为种子,在进程内图谱上运行 API: ``` GRAPH_BACKEND=memory SEED_SAMPLE=true uvicorn threatweave.api.app:app --reload ``` 然后查询一个关联子图: ``` curl "http://localhost:8000/api/correlate?ioc=malicious.example&depth=2" ``` 你应该会得到一个 JSON 子图,其中包含被查询的指示器、来自同一 OTX pulse 的兄弟 IOC、`Synthetic APT-Test Infrastructure` campaign 节点,以及连接它们的边。 ### 选项 B — 使用 Docker 的全栈(Neo4j + Postgres + API) ``` docker compose up --build ``` 这将启动 Neo4j(浏览器 UI 位于 http://localhost:7474,Bolt 端口为 7687),一个启用了 pgvector 的 Postgres(专为 embedding 阶段保留),以及位于 `http://localhost:8000` 的 API。通过针对运行中的 Neo4j 执行摄入操作,从 OTX 填充图谱(需要有效的 `OTX_API_KEY`)。 ## Web UI(交互式图谱) `frontend/` 应用是一个基于 API 的 React + Vite 单页浏览器。搜索一个 IOC 以构建其关联子图,**单击**一个节点以便在侧边栏中检查它,**双击**以向外扩展其关系。Campaign 节点可以列出其语义邻居;IOC 节点可以请求按需 AI 叙述——当相应的后端被禁用时(例如在无密钥的演示中),两者都能优雅降级。节点颜色编码了实体类型,而 AI 推导出的 `semantic_similarity` 边以虚线绘制并携带其余弦分数,因此它们绝不会与确定性的结构边混淆。 ## 本地试运行 最快的路径——无需数据库、无需 API 密钥——是带有种子数据的内存演示,它还会从同一源提供已构建的前端: ``` # 1. 构建 frontend(若存在,则由 API 从 frontend/dist 提供服务) cd frontend && npm install && npm run build && cd .. # 2. 在已填充数据的内存示例图上启动 API threatweave demo # http://127.0.0.1:8000 (add --reload for dev) ``` 打开 http://127.0.0.1:8000 并搜索 `malicious.example` 或 `203.0.113.10`。 对于带有热重载的前端开发,请并排运行 API 和 Vite 开发服务器(Vite 将 `/api` 代理给 API,因此该应用在开发和生产环境中都是同源的): ``` threatweave demo # terminal 1: API on :8000 cd frontend && npm run dev # terminal 2: Vite on :5173 ``` ## API | 方法 | 路径 | 描述 | |--------|-------------------|--------------------------------------------------------| | GET | `/health` | 存活探针(无需身份验证)。 | | GET | `/api/correlate` | 指示器的关联子图。 | | GET | `/api/expand` | 任意节点 id 周围的邻域子图。 | | GET | `/api/similar` | 实体的语义最近邻。 | | GET | `/api/narrative` | 按需为指示器生成的自然语言解释。 | | GET | `/api/ingest/status` | 每个源的上次摄入结果(时间、计数、错误)。 | `GET /api/correlate?ioc=&depth=<1..4>` —— 指示器类型是根据值(IP、域名、哈希或 URL)推断出来的。如果图谱中不存在该指示器,则返回 `404`。响应是一个 `{ "nodes": [...], "edges": [...] }` 子图。 添加 `&semantic=true`(配置了向量后端时)也可以包含带分数的 `semantic_similarity` 边(可通过 `&k=` 和 `&min_score=` 进行调整)。 `GET /api/expand?id=&depth=<1..4>` —— 返回**任意**节点(IOC、campaign、actor、TTP 或 sector)周围的邻域子图,而不仅仅是原始的指示器值。这支持 Web UI 中的交互式探索:点击选中,展开以向外扩展图谱。它是纯粹的确定性遍历(`GraphStore.neighborhood`),在内存和 Neo4j 后端中表现完全一致——无需 AI。当节点 id 不在图谱中时,响应 `404`。 `GET /api/similar?id=&k=` —— 以 `[{ "id", "label", "score" }]` 的形式返回 `k` 个语义最相似的实体,例如 `id=campaign:`。如果未配置向量后端,则响应 `503`;如果该实体没有存储的 embedding,则响应 `404`。 `GET /api/narrative?ioc=` —— 计算关联子图,然后要求 LLM 对其进行解释,返回 `{ "ioc", "narrative", "model" }`(`model` 字段记录了是哪个模型生成了该文本)。添加 `&semantic=true` 也可以将相似度边纳入考虑。如果指示器不存在,则响应 `404`;如果未配置 LLM provider,则响应 `503`。叙述始终以验证免责声明结束。 `GET /api/ingest/status` —— 以 `{ "sources": [{ "source", "status", "last_run", "new_iocs", "total_iocs", "error", "payload_hash" }] }` 的形式返回每个摄入源的最后一次运行情况。它读取由 `threatweave ingest`(通常是独立的调度进程)写入的持久化状态文件,因此 API 能够在不与其共享内存的情况下展示最新的一次运行。从未运行过的源将被省略。纯粹的易观测性——不访问图谱或 AI。 ## 自动化数据摄入 `threatweave ingest` 从结构化 feed 中提取 IOC 并将其写入图谱——高效且幂等,**完全不使用 AI**。指示器在每个后端被批量处理为单个 `UNWIND` + `MERGE`(而不是每个 IOC 处理一次事务),并且确定性的节点 使得重复摄入可重复:没有任何内容会被重复处理,并且来自两个 feed 的同一指示器会解析为一个节点(自动的跨源关联)。基于源的 payload 哈希值会跳过自上次运行以来内容未更改的 feed。 ``` threatweave ingest --all # every enabled source threatweave ingest --source urlhaus --source feodo # a specific subset ``` 源:`otx`、`urlhaus`、`malwarebazaar`、`feodo`。每个连接器都会处理网络错误,在触发速率限制(HTTP 429)时进行退避,并且——对于 abuse.ch——使用 `ABUSECH_AUTH_KEY` 进行身份验证。每次运行都会被记录下来(可在 `/api/ingest/status` 查询)。 **对 cron 友好。** 该命令运行一次即退出,因此任何调度程序都可以驱动其执行频率(`INGEST_INTERVAL_MINUTES` 记录了建议的间隔): ``` # Linux/macOS cron — 每小时 0 * * * * cd /path/to/threatweave && /path/to/.venv/bin/threatweave ingest --all # Windows Task Scheduler — 每小时 schtasks /create /tn ThreatWeaveIngest /tr "threatweave ingest --all" /sc hourly ``` ## 摄入文档(CLI) `threatweave ingest-doc` 摄入非结构化的威胁报告——博客文章、CERT 公告或社交媒体帖子——来源可以是 URL、文件或内联文本。它运行混合提取(regex IOC + LLM TTP/actor/sector)并将结果写入图谱,构建 `Campaign`、`Actor`、`TTP` 和 `Sector` 节点及其边。需要配置 LLM provider(`LLM_PROVIDER=openai`、`LLM_API_KEY`)。 ``` threatweave ingest-doc --url https://example.com/threat-report threatweave ingest-doc --file data/samples/threat_report.txt threatweave ingest-doc --text "APT-Sample phishing campaign targeting finance ..." ``` 每次调用都会打印出摘要(campaign id、IOC/TTP 计数、actor、sector,以及记录的预估 token 成本)。之后,提取出的实体即可通过 `/api/correlate` 进行查询。 ## 测试 完整测试套件完全离线运行——不需要 Neo4j、pgvector、Docker、网络或 API 密钥。关联和相似度计算针对内存存储运行,每个 feed 连接器(OTX 和三个 abuse.ch feed)针对模拟传输运行,并且 LLM provider 被完全模拟(固定的提取和确定性的 embedding),因此在测试提取、embedding 和文档摄入时无需进行任何真实的 API 调用。防护测试断言批量 upsert 保持幂等(重复摄入永远不会产生重复数据),未更改的 feed 会被跳过,并且结构化 feed 的摄入对 AI provider 发起**零**次调用: ``` pytest # run tests ruff check . # lint ``` ## 数据与安全 - 代码库中无密钥——所有配置均通过环境变量进行。 - `.env` 被 git 忽略;仅提交 `.env.example`(仅包含名称,不包含值)。 - 真实的情报数据保留在代码库之外;`data/samples/` 仅包含合成或公开数据。 ## 路线图 - [x] **第 1 阶段 — 基础图谱**:项目骨架、确定性 IOC 解析、AlienVault OTX 摄入、带有内存测试后端的 Neo4j 图谱模型、确定性关联以及 FastAPI 查询 endpoint。不使用 AI。`LLMProvider` 接口和 pgvector 基础设施已定义但尚未实现。 - [x] **第 2 阶段 — LLM 提取**:混合文档摄入(`threatweave ingest-doc`)——通过可替换的 `OpenAIProvider`(保留了 Ollama stub),将 regex IOC 与 LLM 提取的 TTP(MITRE ATT&CK)、actor 和目标行业结合,并带有 token 成本记录和结构化输出验证。提取操作会插入 `Campaign`/`Actor`/`TTP`/`Sector` 节点及其边。 - [x] **第 3 阶段 — 语义相似度**:每个 campaign 的 embedding(`embed`),在摄入时计算一次并缓存在 `VectorStore`(pgvector,带有内存测试后端)中。添加了 `similar(entity, k)`、`correlate()` 中带分数的 `semantic_similarity` 边,以及一个 `GET /api/similar` endpoint——将不共享精确 IOC 的 campaign 关联起来。 - [x] **第 4 阶段 — 按需叙述**:`narrate()` 通过独立的、质量更高的模型(`LLM_NARRATIVE_MODEL`)解释关联的子图,该模型完全基于子图证据,并带有验证免责声明。在 `GET /api/narrative` 中暴露——仅在请求时生成,因此成本随使用量扩展,而不是随数据量扩展。 - [x] **第 5 阶段 — 前端与加固**:一个带有 Cytoscape.js 图谱视图(通过新的确定性 `GET /api/expand` 进行搜索、点击检查、双击展开)的 React + Vite 单页浏览器,一个带有按需叙述的节点详细信息面板,以及加载/错误状态。提供了日常的 `X-API-Key` 限制以及 API 上的按客户端速率限制,以及一个无密钥的一键演示(`threatweave demo`,内存中种子图谱),该演示也可提供已构建的前端。 - [x] **第 6 阶段 — 自动化 feed 摄入**:共享 OTX 模式的结构化 feed 连接器(abuse.ch URLhaus、MalwareBazaar、Feodo Tracker),带有网络错误处理、速率限制退避和完全模拟的测试。批处理的 `UNWIND` + `MERGE` upsert 使摄入变得快速且幂等;payload 哈希去重跳过未更改的 feed。一个对 cron 友好的 `threatweave ingest --all` 运行每个已启用的源,`GET /api/ingest/status` 报告每个源的上一次运行情况,并且结构化 feed 严格保持不使用 AI(embedding/提取依然是自由文本 `ingest-doc` 的专属领域)。
标签:AV绕过, DLL 劫持, FastAPI, Python, 大语言模型, 威胁情报, 开发者工具, 无后门, 测试用例, 版权保护, 逆向工具