Mario0111/alert-triage-rag
GitHub: Mario0111/alert-triage-rag
该项目是一个面向 SOC/MDR 场景的检索增强型告警分诊助手,通过检索 MITRE ATT&CK 技术与内部 runbook 为安全分析师生成带引用的结构化研判结论。
Stars: 0 | Forks: 0
# alert-triage-rag
一个用于 SOC/MDR 告警的检索增强分诊助手。分析师用自然语言描述告警;系统会检索相关的 **MITRE ATT&CK** 技术和内部 **runbook** 步骤,然后生成一个结构化且有依据的分诊结论(JSON),并带有指向原始材料的**引用**。
刻意构建为*不包含*编排框架(没有 LangChain / LlamaIndex)
## 架构
两个阶段:
1. **数据摄取** (`triage/ingest.py`) — 加载语料库 → 分块 → 本地嵌入 → 持久化到 Chroma。在语料库更改时运行一次或随时运行。
2. **查询** (`triage/query.py`) — 嵌入告警文本 → 检索 top-k 分块 → 构建基于事实的 prompt → 调用 Claude → 根据 schema 验证 JSON。
```
ATT&CK STIX bundle (auto-downloaded) ─┐
├─ ingest ──► Chroma (per-user data dir) ──► query ──► triage verdict (JSON)
packaged runbooks (triage/corpus/) ───┘ ▲
alert description
```
**语料库**
- MITRE ATT&CK Enterprise (STIX/JSON),**按技术字段**分块 — 描述和检测作为单独的分块进行嵌入(如果超出嵌入模型的 512-token 窗口,则会进一步拆分),所有分块都标记有相同的 `attack_id`。
- 手写的 runbook(`triage/corpus/runbooks/*.md`,打包在项目内),使用相同的基于 token 预算的拆分器进行拆分,并标记有 runbook 文件名。
- 检索将同一文档的同级分块(通过 `attack_id` / 文件名)合并回一个完整、可引用的单元,因此结论引用的是整个技术或 runbook,而不是一个片段。
- runbook 始终包含在来源中:如果没有 runbook 进入相似度 top-k,则会附加最接近的一个 — 标记为 `backfilled`,以便模型判断其相关性,而不是假设其相关性。
**技术栈:** Python 3.11+ · `sentence-transformers` (`bge-small-en-v1.5`,本地) · `chromadb` · `anthropic` (Claude) · `pydantic` v2 · `fastapi` + `uvicorn` · `PySide6`(可选的 `[desktop]` 额外依赖)。
可安装的 CLI + HTTP 服务 + 原生桌面应用程序。
**陈旧性防护:** 数据摄取会为存储打上指纹印记(应用程序版本、嵌入模型、分块参数、语料库身份)。查询和 serve 服务会拒绝指纹与当前运行代码不再匹配的存储 — “重新运行 `triage ingest`” — 这样应用程序升级就不会在不知情的情况下提供由旧代码构建的索引所产生的结论。
## 安装
有三种方式,具体取决于您的需求:
| | 需要 | 得到 |
|---|---|---|
| **Windows 安装程序** | 无需准备 | 开始菜单应用 + `triage` 命令,自带 Python 和嵌入模型 |
| **pipx** | Python 3.11+ | CLI 和 API,像任何其他 CLI 工具一样进行管理 |
| **Docker** | Docker | 仅 API,在容器中运行 |
### Windows 安装程序(无前置条件)
从 [Releases 页面](https://github.com/Mario0111/alert-triage-rag/releases) 下载 `alert-triage-rag--setup.exe` 并运行。它按用户安装(无管理员提示,无 UAC)到 `%LOCALAPPDATA%\Programs\Alert Triage RAG`,并且**不需要 Python 和 Docker** — 它在有效载荷中捆绑了 CPython 和 `bge-small-en-v1.5` 嵌入模型。
它是未签名的,因此 SmartScreen 会在首次运行时显示“Windows 保护了你的电脑”:**更多信息 → 仍要运行**。
在 *选择组件* 页面上选择三个层级之一:
| 层级 | 安装大小 | 它是什么 |
|---|---|---|
| **桌面应用程序** *(推荐)* | 2.0 GB | 原生窗口 + 完整的本地 pipeline。启动它会开启自己的后端 — 无需 Docker,无需终端。 |
| **仅命令行** | 1.4 GB | 相同的 pipeline,无 GUI。将 `triage` 添加到您的 PATH(可选)。 |
| **瘦客户端** | 0.7 GB | 仅包含 GUI 和 `triage` 命令,通过 `--api-url` / `TRIAGE_API_URL` 指向远程或容器化的 API。`ingest`/`query`/`serve` 需要 pipeline 并会提示您。 |
容量大小是实际测量值,而非估算值。本地 pipeline(torch、transformers、chromadb、onnxruntime 和嵌入模型)占用了其中的 1.3 GB,Qt GUI 占用 0.6 GB,因此去掉 GUI 可节省约 31%,去掉 pipeline 可节省约 66%。只有瘦客户端可以避免下载机器学习技术栈。
在安装了包含 pipeline 的层级后:
```
# 1. 用于查询阶段的 API key(生成在 Claude 上运行)
setx ANTHROPIC_API_KEY "sk-ant-..." # then open a new terminal / re-login
# 2. 构建 vector store:开始菜单 -> "Build the triage store (run this first)"
# (或在安装程序最后一页勾选复选框)。下载约 51 MB 的
# ATT&CK bundle 并在本地嵌入语料库 - 需要几分钟,且仅需一次。
# 3. 开始菜单 -> "Alert Triage RAG"
```
从 **设置 → 应用** 或开始菜单条目卸载。卸载程序会删除程序,清理其 PATH 条目,并*询问*是否删除数据目录(`%LOCALAPPDATA%\alert-triage-rag`,约 700 MB)— 如果您打算重新安装,请选否,因为重建它意味着重新下载并重新嵌入整个语料库。
自行构建安装程序的说明记录在 [packaging/README.md](packaging/README.md) 中。
### pipx(任何平台,需要 Python)
推荐的安装程序是 [pipx](https://pipx.pypa.io/):它为应用程序创建一个专用的虚拟环境,在那里安装它,并仅将 `triage` 命令放在您的 PATH 上。您将获得一个独立的安装(此项目锁定的依赖项永远不会与您机器上的任何其他内容发生冲突),而无需自己激活 venv — 这是终端用户 CLI 应用程序的正确工具,在这种情况下,将 `pip install` 放入共享环境中是同时破坏两个项目的经典方式。
```
# 每台机器一次
pip install --user pipx
pipx ensurepath # then open a new terminal
# 安装应用程序(从克隆版本,或直接从 GitHub)
pipx install git+https://github.com/Mario0111/alert-triage-rag
# 或者,从本地 checkout:
pipx install .
```
(不使用 pipx 的等效方法:创建一个新的 venv,激活它,`pip install .` — 这正是 pipx 自动执行的操作。)
## 快速开始(纯净机器)
```
# 1. 用于查询阶段的 API key(生成在 Claude 上运行)
export ANTHROPIC_API_KEY=sk-ant-... # Windows (PowerShell): $env:ANTHROPIC_API_KEY="..."
# 2. 构建 vector store。首次运行会下载 MITRE ATT&CK Enterprise
# bundle(约 51 MB,固定为 v19.1)和 bge-small-en-v1.5 embedding
# model(约 130 MB),然后在本地嵌入语料库 — 预计需要几分钟。
triage ingest
# 3. Triage 一个 alert
triage query "Multiple failed logons followed by a successful logon from a new country, then a PowerShell download cradle on the host."
```
输出是一个结构化的分诊结论(JSON),其中带有指向所使用的 ATT&CK 技术和 runbook 步骤的引用。
## 使用方法
**`triage ingest`** — 构建向量存储。缺失时将 ATT&CK 包获取到数据目录中(`--refresh-attack` 重新下载它,例如在升级锁定的版本之后)。每次运行都会从头开始重建集合(它会首先删除任何现有集合),因此在语料库或分块更改后重新摄取始终是安全的,不会留下过时的文档。
```
triage ingest
# 选项:--attack-file --refresh-attack --runbooks-dir --db-dir
# --collection --embed-model --batch-size
```
**`triage query`** — 对单个告警进行分诊。
```
triage query "Scheduled task created remotely via schtasks from a workstation to a domain controller."
# 选项:--top-k --db-dir --collection --gen-model --rewrite-model --no-rewrite
```
**`triage serve`** — 运行分诊 HTTP API。这是单一的集成面:桌面应用程序(`triage desktop`)是此端点的瘦客户端,由与 `triage query` 相同的 pipeline 提供支持,并且计划中的 SIEM webhook([路线图](#roadmap))旨在成为另一个集成面。嵌入模型和 Chroma 集合在启动时加载一次;缺失或陈旧的存储会因 `triage ingest` 补救措施而中止启动,而不是提供服务错误。默认绑定 `127.0.0.1` — 请谨慎使用 `--host 0.0.0.0` 对外暴露。
```
triage serve # http://127.0.0.1:8000, interactive docs at /docs
# 选项:--host --port --db-dir --collection --embed-model --gen-model
# --rewrite-model --no-rewrite
```
```
# POST 一个 alert,返回 {"verdict": {...}, "retrieved": [...]}:
curl -s http://127.0.0.1:8000/triage \
-H "Content-Type: application/json" \
-d '{"alert": "Multiple failed logons followed by a successful logon from a new country."}'
# Windows (PowerShell):
# Invoke-RestMethod http://127.0.0.1:8000/triage -Method Post -ContentType "application/json" `
# -Body '{"alert": "..."}'
```
响应是一个**信封**:`verdict` 是完全符合 CLI 打印输出的 `schema.py` 契约,而 `retrieved` 列出了检索所呈现的源文档 — 全文、源类型以及 `backfilled` 标记 — 以便客户端(UI 的引用面板)可以展示结论本身无法携带的证据。
响应:`200` 表示信封;如果请求正文未通过验证,则为 `422`;如果上游模型没有生成任何服务可以担保的内容(API 失败、拒绝或未通过事实依据验证的结论),则为 `502`。
**`triage desktop`** — 启动**原生桌面应用程序**(Qt/PySide6):一个真正的应用程序窗口,就像其他所有客户端一样是一个瘦客户端(它通过 HTTP POST 到 `/triage`,并且不导入任何 pipeline 代码)。PySide6 是一个可选的额外依赖,所以先用 `pip install "alert-triage-rag[desktop]"` 安装它(普通的安装会跳过它)。使用 `--api-url`、`TRIAGE_API_URL` 环境变量或窗口中的可编辑端点字段选择 API(默认为 `http://127.0.0.1:8000`)。该应用程序**不是** Docker 镜像的一部分(GUI 在无头容器中毫无用处)。
**它会为您启动后端。** 在启动时,应用程序会检查 `/health`;如果没有响应*且* URL 是本地的,它会启动一个后端并等待它,同时显示进度。它启动的内容取决于应用程序本身是如何运行的:
| 运行方式 | 启动的后端 | 原因 |
|---|---|---|
| 源码 / venv (`triage desktop`) | `python -m triage.serve` | 该解释器已经安装了 pipeline |
| 打包的 `.exe` | Docker 容器 | 捆绑的解释器故意不包含 pipeline(见下文) |
它**只**关闭由它启动的后端 — 附加到您自己的 `triage serve`,当您关闭窗口时,应用程序会使其保持运行。传递 `--no-autostart` 以禁用此功能,并仅附加到已经在运行的内容上。
```
triage serve # in one terminal (or docker compose up)
triage desktop # opens the native window
# 选项:--api-url
```
### 将桌面应用程序构建为独立的 .exe
PyInstaller 将应用程序、CPython 解释器和 Qt 库捆绑到一个可双击的可执行文件中,因此目标机器完全不需要 Python。构建配置位于 `packaging/alert-triage-desktop.spec` 中(已签入,因此构建是可重现的,而不是一条仅凭记忆的命令行):
```
pip install -e ".[desktop,exe]"
python -m PyInstaller packaging/alert-triage-desktop.spec
# -> dist/alert-triage-desktop.exe (~47 MB)
```
注意:
- **它仍然是一个瘦客户端。** 该规范明确*排除*了 torch、chromadb、anthropic、fastapi 及其相关依赖 — 这种排除是对 GUI 永远不会触及 pipeline 的一种断言,这就是为什么构建产物约为 47 MB 而不是数 GB 的原因。
- **因此,它需要安装并运行 Docker**,因为那是它启动的后端(它无法启动 `triage serve` — 按照设计,捆绑的解释器中没有 pipeline)。它运行的容器使用与 `docker compose` 相同的镜像和相同的命名卷,因此它会重用您摄取的存储。如果 Docker 没有运行,应用程序会在窗口中提示,而不是静默失败。
- **单文件,启动较慢。** 所有内容都打包在单个 .exe 文件中,每次启动时都会将其解压缩到临时目录中 — 预计在窗口出现之前会有一两秒的延迟。如果您希望有一个能瞬间启动的文件夹,请将规范切换为 onedir 布局。
- **二进制文件未签名。** 它在本地运行良好,但 Windows SmartScreen(或防病毒软件)可能会在其他用户首次运行时发出警告 — 代码签名可以消除此警告,但这需要证书。
## 使用 Docker 运行
该容器是无需 Python 的路径:镜像内置于其中,提供了锁定的依赖项、仅支持 CPU 的 torch 和 `bge-small-en-v1.5` 嵌入模型,因此冷启动根本不需要下载。Chroma 存储位于命名卷上,因此它可以在容器和镜像升级后保留下来。
```
export ANTHROPIC_API_KEY=sk-ant-... # PowerShell: $env:ANTHROPIC_API_KEY="..."
# 1. 构建 store(一次性容器;将 ATT&CK bundle 下载到
# volume 中并嵌入语料库 — 需要几分钟)
docker compose run --rm api ingest
# 2. 提供 API 服务
docker compose up
```
`GET /health` 和 `POST /triage` 随后将在 `http://127.0.0.1:8000` 上响应,就像 `triage serve` 一样 — 容器运行的是相同的 CLI。Compose 仅提供 API:用户界面是原生桌面应用程序,运行在您的机器上(GUI 无法从无头容器中显示)。启动容器,然后运行 `triage desktop` 并将其指向 `http://127.0.0.1:8000`。
注意:
- **API 密钥仅通过环境传递。** `docker-compose.yml` 指定了变量名,但从不保存值,因此密钥永远不会出现在镜像、文件或 git 中。
- **数据摄取是刻意的一次性操作,不会在启动时自动执行。** 升级到新镜像版本后,API 将*拒绝启动*,并指出指纹不匹配 — 该存储是由不同的代码构建的。重新运行第 1 步,然后再次运行 `docker compose up`。这种拒绝是陈旧性防护在设计上起作用的方式,因此容器不会默默地自动修复它。
- 可以直接使用已发布的镜像,而不是自行构建:`docker pull ghcr.io/mario0111/alert-triage-rag:latest`。
- `docker compose down` 会停止服务并保留存储;`docker compose down -v` 还会删除卷(之后需要进行完整的重新摄取)。
## 数据位置
该应用程序从不假定存在代码库检出。可变数据(Chroma 存储、下载的 ATT&CK 包)位于用户级数据目录中(通过 `platformdirs 实现);runbook 在包内以只读形式提供。
| 平台 | 数据目录 |
|---|---|
| Windows | `%LOCALAPPDATA%\alert-triage-rag` |
| Linux | `~/.local/share/alert-triage-rag` |
| macOS | `~/Library/Application Support/alert-triage-rag` |
使用 `TRIAGE_DATA_DIR` 环境变量覆盖根目录,或使用 `--db-dir` / `--attack-file` 覆盖单个位置(标志优先于环境变量,环境变量优先于默认值)。
## 开发环境设置
从代码库检出开始,以**可编辑**模式安装,以便代码编辑无需重新安装即可生效,并将数据目录指向代码库以重现经典的目录结构(`./chroma_db`,`corpus/attack/`):
```
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
export TRIAGE_DATA_DIR=$PWD # Windows (PowerShell): $env:TRIAGE_DATA_DIR=$PWD
triage ingest # or: python -m triage.ingest
```
## 演示
_演示截图即将推出。_
## 路线图
检索 pipeline、打包的应用程序和发布路径已完成并于 **v0.5.0** 发布 — 可安装的 CLI、HTTP 服务、原生桌面应用程序、Docker 镜像、Windows 安装程序、CI 和带标签的版本。有两部分已经设计好,但特意尚未构建:
- **SIEM 集成 — Wazuh + 面向互联网的 SSH 蜜罐。** 预期的最终状态是,真实的攻击者的蜜罐会话产生有依据的结论,而无需任何人工输入。它因硬件问题而搁置:它需要一个与分诊 API 并存的主机来运行 Wazuh 管理器,但目前尚不可用。架构、操作限制(蜜罐位于不受信任的免费层 VPS 上;日志通过私有网格传输回家,从不使用端口转发)和构建顺序写在 [docs/phase12-siem-setup.md](docs/phase12-siem-setup.md) 中。核心的承重设计决策是**双网关**:Wazuh 决定什么是*告警*(免费、本地、其自己的规则),而一个单独的选择层决定什么值得得出*结论*(付费模型调用)。将这些问题区分开来,才使得在不削弱检测能力的情况下控制成本成为可能。值得注意的是,这**不需要更改核心代码**:webhook 只是 `POST /triage` 的又一个瘦客户端,这正是单一集成面规则的用途所在。
- **结论质量评估。** 测试套件彻底涵盖了管道 plumbing,但根本没有涵盖结论*质量*。一个小型的标注集(带有预期处置和预期技术 ID 的告警)将允许通过测量来选择生成模型,而不是依靠假设 — [PLAN.md](PLAN.md) 中搁置的 Sonnet 与 Opus 的比较正是受制于此。
## 项目布局
```
pyproject.toml packaging: metadata, pinned deps, `triage` entry point
packaging/
desktop_entry.py PyInstaller entry point for the desktop app
alert-triage-desktop.spec PyInstaller build config (standalone .exe)
Dockerfile multi-stage image build (wheel -> slim runtime, model baked in)
.dockerignore what never enters the build context
docker-compose.yml API service + Chroma volume + /health healthcheck
.github/workflows/
ci.yml ruff + mypy + pytest on every push/PR
release.yml on a v* tag: wheel -> GitHub Release, image -> GHCR
triage/ core package
cli.py the `triage` command (argparse subcommand dispatch)
paths.py data-directory resolution (platformdirs + overrides)
ingest.py ingestion pipeline (plumbing, ATT&CK auto-fetch)
stix.py ATT&CK STIX/JSON → flat Technique records
chunk.py chunking strategy (per-technique + runbooks)
retrieve.py top-k retrieval + sibling-chunk merge
rewrite.py alert → retrieval-optimized query rewrite
query.py query pipeline + grounding prompt
fingerprint.py store staleness fingerprint (written at ingest, checked at load)
api.py FastAPI app: POST /triage, GET /health
serve.py `triage serve` (uvicorn runner)
apiclient.py stdlib-urllib client for /triage (used by every thin client)
backend.py autostart policy: bring the API up if nothing answers
desktop.py native Qt/PySide6 desktop app (thin HTTP client)
desktop_launch.py `triage desktop` (lazy-imports PySide6, runs desktop.py)
schema.py Pydantic output contract for the verdict
corpus/runbooks/ hand-written runbooks (markdown, ship in the wheel)
corpus/
attack/ dev-mode ATT&CK bundle location (downloaded, not committed)
```
## 许可证
[MIT](LICENSE)。本项目基于其进行检索的 MITRE ATT&CK® 语料库是在摄取时下载的,并未在此处重新分发;它仍受 [MITRE 自身的使用条款](https://attack.mitre.org/resources/legal-and-branding/terms-of-use/) 约束。
标签:LLM, Python, RAG检索增强, SOC/MDR, Unmanaged PE, 告警分诊, 安全运营, 扫描框架, 无后门, 请求拦截, 逆向工具