stecrin/legiontrap-ti
GitHub: stecrin/legiontrap-ti
LegionTrap 是一个隐私优先的本地化蜜罐威胁情报平台,通过多维行为指纹和确定性聚类,在运营者自有基础设施上构建纵向攻击者行为画像,弥补传统指标型情报易过期的结构性缺陷。
Stars: 0 | Forks: 0
# LegionTrap TI
## 目录
- [论点](#thesis)
- [这是什么](#what-this-is)
- [情报模型](#the-intelligence-model)
- [架构概览](#architecture-overview)
- [主权模型](#sovereignty-model)
- [当前状态](#current-state)
- [发展方向](#direction)
- [路线图](#roadmap)
- [技术栈](#tech-stack)
- [快速开始](#quick-start-local)
- [API 参考](#api-reference)
- [数据库操作](#database-operations)
- [环境配置](#environment-configuration)
- [隐私与匿名化](#privacy--anonymization)
- [测试与 CI](#tests--ci)
- [故障排除](#troubleshooting)
- [发布自动化](#release-automation)
- [贡献](#contributing)
- [许可证](#license)
## 论点
大多数威胁情报是围绕指标组织的:IP 地址、域名、文件哈希、签名。每个指标都代表了针对攻击者基础设施的特定时间点的观测。操作周期变成了观测、发布、封禁。该观测的有效期受限于攻击者继续使用该基础设施的时间。轮换一个 IP 成本很低。一个域名只需几美元。
这一直是基于指标的防御的结构性限制。当 AI 工具被广泛使用时,改变的是衰减率。生成新域名、轮换基础设施和循环凭证列表变成了脚本任务,而不是手动任务。废弃一个已暴露指标的成本接近于零。最容易被击败的情报类别,恰恰正是大多数防御工具组织起来去生产的那个类别。
行为模式的变化则要慢得多。攻击者如何开展一项行动——他们的时间分布、探测顺序、凭证偏好、目标选择背后的逻辑——反映了修改成本高昂的操作决策。这些模式在基础设施轮换中依然存在,因为它们描述的是行为,而不是地址。一个基于数月观测构建的行为指纹,能够在任何指标都会过期的变更中存活下来。LegionTrap 的建立正是基于这样一个论点:这种类型的情报——纵向的、行为的、复合的——值得去构建它所需的架构复杂性。
## 这是什么
LegionTrap 运行在单个运营者的基础设施上,分析来自其控制的传感器的对抗性流量。该设计是本地优先的:没有共享的情报源,没有云依赖。该系统是为了处理运行在隔离网段的蜜罐部署中的真实事件而构建的——来自外部对抗活动的实际扫描、探测和凭证尝试。
架构遵循了该操作背景。存储是本地 SQLite,在需要规模扩展时可与 PostgreSQL 兼容。AI 推理层支持完全本地化的推理;云端 backend 作为运营者的选择提供,而不是硬性依赖。PRIVACY_MODE 是一等公民配置选项,而不是事后补充。设计中的每一个约束都反映了相同的基本原则:运营者应该完全拥有他们的情报——不是通过订阅或数据源来访问它,而是拥有数据、分析 pipeline 以及从中得出的结论。
## 情报模型
LegionTrap 不会将事件视为孤立的日志条目。每个事件都会为其来源贡献一条行为记录:一个指纹,它在所有观察到的活动中累积了时间模式、探测序列、协议行为、凭证选择和目标选择。指纹是情报的单位。事件是原始材料。
指纹跨越五个维度构建。时间维度捕获探测间隔及其分布——特定工具的特征节奏。序列维度捕获探测端口和服务的顺序,这在基础设施变更中保持稳定。协议维度捕获 session 内的行为:认证顺序、banner 处理、握手模式。凭证维度捕获登录尝试中使用的集合和策略。目标维度捕获运营者的哪些服务持续受到关注。每个维度独立评分;当多个维度被填充且事件量足够时,指纹被认为是可信的。
活动聚类使用加权相似度算法将指纹分配给活动。该算法是确定性的:相同的指纹总是产生相同的结果。新指纹将与所有现有活动进行比较;如果相似度超过阈值,则该来源加入现有活动。如果相似度处于边缘,则该关联被标记为不确定,并排队等待分析师审查。不涉及任何机器学习。决策及其每个维度的相似度得分与每次观测一起存储,因此推理始终是可审计的。
每次重新计算指纹时,都会将一个快照附加到该来源的历史记录中。随着时间的推移,这成为了纵向记忆:一份关于观察到的行为在数月的活动中发生了怎样的变化或保持不变的记录。一个被持续观察了六个月的活动拥有任何购买的数据源都无法复制的行为记录。情报随着时间的推移而积累。
行为稳定性衡量活动在其指纹历史中表现的一致性程度。高稳定性表明该活动的工具、时间和目标在所有观察到的快照中都保持可识别。最近快照中稳定性下降可能表明正在进行调整。稀疏标识意味着历史记录太短,无法计算有意义的稳定性指标。稳定性是一个信号,而不是定论;运营者决定特定的稳定性状况在其特定背景下意味着什么。
AI 推理层在这些结构化的确定性数据——指纹、活动记录、稳定性得分——上运行,并根据运营者的请求生成自然语言分析。AI 不是情报的来源。它解释并提供了完全由确定性算法生成的数据的上下文。AI 层得出的每一个结论都可以追溯到特定的行为维度和相似度得分。运营者始终是最终解释者;系统不会自动采取任何行动。
## 架构概览
该系统有两个结构上隔离的路径。摄入路径在每个事件上运行;推理路径在运营者请求时运行。摄入路径不导入 AI 层的任何内容。推理路径从不写入活动、指纹或事件表。移除推理路径后,摄入路径仍能完全正常工作。
```
External sensors
│
▼
Ingest + GeoIP enrichment
│
▼
Behavioral fingerprinting (5 dimensions)
│
▼
Campaign clustering (deterministic similarity scoring)
│
├─→ Fingerprint history
└─→ Behavioral stability
│
[on operator request]
▼
AI reasoning layer (read-only)
│
▼
Operator review and decision
```
摄入路径接收事件批次,对其进行验证和归一化,使用 GeoIP 数据进行丰富,并写入本地数据库。每次摄入后,都会为受影响的源 IP 更新行为指纹,并运行活动聚类:将每个指纹与所有活动的活动指纹进行比较,并记录确定性决策——自动关联、不确定关联或新活动——以及各维度的相似度得分。
活动记录累积了跨多个 IP 和跨时间的观测。每次指纹重新计算都会将一个快照附加到该来源的历史记录中。行为稳定性源自该历史记录:连续快照之间的成对相似度产生各维度的稳定性得分。高稳定性表明行为一致;最近快照中稳定性下降可能表明工具或操作上的调整。
在运营者请求时,AI 推理层会读取活动记录、指纹和稳定性得分,并生成自然语言摘要或威胁简报。endpoint 会立即返回一个作业 ID;分析在后台运行。AI 输出与其数据源、prompt 哈希和安全验证结果一起不可变地存储。AI 审计日志仅记录元数据——操作类型、字节数、延迟——而不存储内容。
不确定的聚类关联会被呈现给分析师进行审查。审查决策记录在观测上,但不会改变原始的聚类结果或活动成员资格。Actor 归因将由运营者分配。系统中的任何地方都不会采取自动行动。
有关完整的组件映射、仓库结构和 API 合约详细信息,请参阅 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。
## 主权模型
LegionTrap 构建的行为指纹特定于某个运营者的暴露画像:他们的服务、他们的网络、他们的攻击面。本地存储主要不是一项隐私措施——它是情报具有相关性的必要条件。你的攻击历史只能从你的观察中构建。无法访问你的传感器数据的服务无法为你构建行为记忆。
商业威胁源提供源自外部、聚合观测的情报。该情报对于封禁已知的恶意基础设施很有用。它对于理解针对你特定服务的行为模式是否曾经出现过,或者休眠的活动是否已回归则毫无用处。没有数据源能提供这个答案,因为没有任何数据源拥有你的纵向记录。
PRIVACY_MODE 解决了一个特定的操作需求:IOC 导出——防火墙封禁列表、拒绝规则——通常在团队之间共享或集成到合作方系统中。运营者可能需要发布可操作的封禁规则,而不透露他们观察到的特定 IP。PRIVACY_MODE 将情报资产与操作工件分离开来。两者可以独立管理。
聚类算法、指纹构建器和稳定性评分器是确定性的:相同的输入总是产生相同的输出。这是一项操作要求。需要了解为什么将某个来源分配给活动的运营者,可以读取每次观测中存储的各维度相似度得分。可解释性不是附加在情报 pipeline 之上的层——它是核心数据模型的一部分。
AI 推理层默认禁用。启用后,它支持完全本地化的推理;云 backend 是一种运营者配置选择,而不是硬性依赖。每个 AI 请求都记录有元数据——操作类型、字节数、延迟——而不存储 prompt 内容或响应文本。每个 AI 输出与其数据源和安全验证结果一起不可变地存储。审计跟踪回答了操作问题“对什么数据执行了什么分析”,而无需重建分析内容。
LegionTrap 中的任何决策都不是自动做出的。活动成员资格是确定计算的;不确定的情况会提交给运营者审查。AI 分析根据请求生成;它不会触发行动。运营者不是自动化 pipeline 中的一个步骤。运营者就是决策层。
## 当前状态
通过第 7 阶段,LegionTrap 支持完整的行为情报 pipeline 以及 actor 级别的情报:事件摄入、行为指纹识别、活动聚类、纵向指纹历史、行为稳定性评分、根据运营者请求的 AI 辅助推理,以及带有建议引擎和稳定性聚合的运营者分配的 actor 配置。
从摄入到活动分配的情报 pipeline 是确定性的,不需要 AI backend。行为指纹在每次摄入周期自动构建。活动通过生命周期状态——活动、休眠、历史——根据可配置的时间阈值进行转换。不确定的聚类关联作为审查队列呈现;确认或拒绝的决策累积成每个活动的权重配置,使聚类算法随着时间的推移对运营者的判断产生响应。当稳定性得分越过可配置的阈值时,行为漂移警报会触发。
运营者可以创建 actor 配置,查看代表性指纹超过可配置相似度阈值的候选活动对,并检查跨链接到某个 actor 的所有活动的聚合行为稳定性。建议引擎是只读的——没有任何归因是自动的。Actor 名称由运营者分配;关系类型从定义的词汇表中选择。
AI 推理层在配置后不会改变确定性输出。活动相似度得分、指纹置信度值、行为稳定性指标和 actor 建议完全由不受 AI 配置影响的算法产生。每个 AI 输出与其数据源、prompt 哈希和安全验证结果一起不可变地存储。AI 层从不写入活动、指纹或 actor 表。
## 发展方向
第 7 阶段弥合了第 6 阶段遗留的三个架构差距。在不确定关联队列中累积的分析师审查决策现在会调整每个活动的相似度权重配置,使聚类算法随着时间的推移对运营者的判断产生响应。累积在 `fingerprint_history` 和 `behavioral_stability_json` 中的行为漂移信号现在显示为可配置的警报。在第 6 阶段作为空 schema 准备的 Actor 配置,现在可以通过完整的 CRUD 和链接 API 访问,通过只读的建议引擎进行查询,并在 dashboard 中展示。运营者可以显式地将活动链接到 actor,查看链接到某个 actor 的所有活动,并查询链接到某个活动的所有 actor。
第 8 阶段处理行为记录的边界。单个部署的指纹历史特定于其自身的攻击面,这既是它的优势,也是它的限制。联邦机制用于在独立部署之间共享行为模式,而不共享得出这些模式的观测数据。指纹编码的是行为特征——时间分布、探测序列、协议行为——而不是 IP 地址。可以在不共享来源的情况下共享模式。当两名真实的运营者愿意参与试点交换,并且指纹序列化格式已通过两个部署的数据验证时,第 8 阶段将随即开始。基础已经构建完成。
## 路线图
| 阶段 | 重点领域 | 状态 |
|-------|-------------|--------|
| **第 0 阶段** | 安全与基础设施优化 | ✅ 完成 |
| **第 1 阶段** | SQLite 存储基础 | ✅ 完成 |
| **第 2 阶段** | HTTP 摄入 API | ✅ 完成 |
| **第 3 阶段** | GeoIP 丰富与情报导出 | ✅ 完成 |
| **第 4 阶段** | 活动情报与导出成熟度 | ✅ 完成 |
| **第 5 阶段** | AI 集成 | ✅ 完成 |
| **第 6 阶段** | 异步 AI、输出持久化与简报 UI | ✅ 完成 |
| **第 7 阶段** | Actor 情报 | ✅ 完成 |
| **第 8 阶段** | 行为联邦 | ○ 视操作先决条件而定 |
每个阶段都建立在前一个阶段的基础之上。有关完整详细信息,请参阅 [docs/ROADMAP.md](docs/ROADMAP.md)。
## 技术栈







## 快速开始(本地)
```
# 1. 复制并填充所需的环境变量
cp .env.example .env
# 编辑 .env:设置 API_KEY、FEED_SALT、DASH_USER、DASH_PASS、JWT_SECRET
# DASH_PASS 必须是 bcrypt 哈希值,而不是明文密码。使用以下命令生成一个:
# python -c "from passlib.context import CryptContext; print(CryptContext(schemes=['bcrypt']).hash('your-password'))"
# JWT_SECRET 必须是一个长随机字符串。使用以下命令生成一个:
# python -c "import secrets; print(secrets.token_hex(32))"
# 2. 安装依赖
pip install -r requirements.txt
# 3. 执行数据库迁移
make db-migrate
# 4. 下载 GeoIP 数据库(可选 — 启用地理和 ASN 丰富化)
# 没有这些文件系统也能正常运行,但 country、city 和 ASN 字段
# 在所有摄入的事件上将为 NULL。需要免费注册。
# 下载源:https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
# 将解压后的 .mmdb 文件放置于:
# storage/GeoLite2-City.mmdb
# storage/GeoLite2-ASN.mmdb
# 5. 启动 API
make run
# 6. 填充演示数据(可选 — 如果数据库已经包含真实的 sensor 数据,则跳过)
# 警告:此脚本通过 POST /api/ingest 写入合成事件。
# 请勿在包含真实 sensor 数据的数据库上运行此操作。
# 覆盖 API key 以匹配你的 .env:
H='x-api-key: ' bash scripts/seed_demo.sh
# 从不同的源 IP 填充 5 个合成事件。触发 fingerprinting 和
# campaign clustering。对于在没有实时 honeypot 的情况下评估 dashboard 非常有用。
# 7. 启动 dashboard(在独立终端中,从项目根目录开始)
cd ui/dashboard && npm install # first time only
npm run dev # dashboard at http://localhost:5173, proxies /api to :8088
# 使用 DASH_USER 和与 DASH_PASS 对应的明文密码登录。
# 对于全新的数据库,Events 和 Campaigns 视图可能会是空的,直到运行了第 6 步。
# AI Summaries 需要配置 AI backend(在 .env 中设置 AI_BACKEND)。
# 8. 健康检查
curl -s http://127.0.0.1:8088/api/health | python -m json.tool
# 9. 摄入测试事件
# 注意:这会向选定的数据库写入一个合成事件。
# 如果数据库已经包含真实的 sensor 数据,请跳过此步骤。
H='x-api-key: '
curl -s -H "$H" -H 'Content-Type: application/json' \
-d '{"events":[{"ts":"2025-10-28T18:31:08+00:00","source":"cowrie","type":"cowrie.login.failed","data":{"ip":"1.2.3.4","username":"root","password":"bad"}}]}' \
http://127.0.0.1:8088/api/ingest | python -m json.tool
# 10. Stats 和 IOC 导出
curl -s -H "$H" http://127.0.0.1:8088/api/stats | python -m json.tool
curl -s -H "$H" http://127.0.0.1:8088/api/iocs/ufw.txt
curl -s -H "$H" http://127.0.0.1:8088/api/iocs/pf.conf
```
## API 参考
完整的 API endpoint 参考、认证模型和合约详细信息记录在 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) 中。
有关将真实的蜜罐传感器连接到摄入 endpoint 的信息,请参阅 [docs/SENSOR_INTEGRATION.md](docs/SENSOR_INTEGRATION.md)。
## 数据库操作
```
# 应用所有待处理的 migrations(在首次部署后以及每次新 migration 后运行一次)
make db-migrate
# 检查当前 migration 版本
make db-status
# 显示 migration 历史
make db-pending
# 回退一个 migration 步骤(谨慎使用)
make db-rollback
# 清理早于截止日期的事件
make db-prune PRUNE_BEFORE=2025-01-01T00:00:00+00:00
# 导入现有的 JSONL 数据
make import-jsonl JSONL_FILES="storage/events.jsonl"
# 验证 migration 正确性(表、索引、版本)
# 根据预期的 head revision(当前为 0013)进行验证。在每个 migration 周期后重新运行。
make db-validate
```
## 环境配置
| 变量 | 必需 | 描述 |
| ------------------ | :------: | ---------------------------------------------------------------- |
| `API_KEY` | 是 | 受保护 endpoint (`x-api-key`) 所需的 Header。 |
| `FEED_SALT` | 是 | 用于隐私模式 IP 哈希的 HMAC salt。 |
| `DASH_USER` | 是 | Dashboard 登录用户名。 |
| `DASH_PASS` | 是 | Dashboard 密码(bcrypt 哈希值)。 |
| `JWT_SECRET` | 是 | 用于签名 dashboard JWT token 的密钥。使用以下命令生成:`python -c "import secrets; print(secrets.token_hex(32))"` |
| `JWT_EXPIRE_SECONDS` | 否 | JWT token 的生命周期(以秒为单位,默认:3600)。 |
| `PRIVACY_MODE` | 否 | 设置为 `on` 以在 IOC 导出时启用隐私掩码并阻止 STIX 导出(默认关闭)。 |
| `CORS_ORIGINS` | 否 | 逗号分隔的允许来源(默认:localhost 变体)。 |
| `DB_PATH` | 否 | SQLite 文件路径(默认:`storage/legiontrap.db`)。 |
| `LOGIN_RATE_LIMIT` | 否 | `/api/login` 的速率限制(默认:`5/minute`)。 |
| `AI_BACKEND` | 否 | AI 推理 backend:`none`(默认)、`claude` 或 `ollama`。 |
| `ANTHROPIC_API_KEY`| 否 | 当 `AI_BACKEND=claude` 时必需。 |
| `AI_MODEL` | 否 | 覆盖 Claude 或 Ollama 的模型名称(适用合理的默认值)。 |
| `OLLAMA_HOST` | 否 | Ollama API endpoint(默认:`http://localhost:11434`)。 |
| `AI_TIMEOUT_SECONDS` | 否 | AI backend 请求超时时间(以秒为单位,默认:30)。 |
复制 `.env.example` 以获取包含所有必需变量的模板。
## 隐私与匿名化
**`PRIVACY_MODE=off`**(默认):完整 IP 按原样导出。
```
8.8.8.8
```
**`PRIVACY_MODE=on`,无 `FEED_SALT`**:最后一个八位字节被掩码。
```
8.8.8.x
```
**`PRIVACY_MODE=on`,设置了 `FEED_SALT`**:确定性 HMAC token(相同 IP + salt = 相同 token)。
```
ip-a3b4c5d6e7f8
```
无论隐私模式如何,私有 IP、环回 IP、链路本地 IP 和保留 IP 始终会从导出中过滤掉。
## 测试与 CI
```
# 完整的测试套件
pytest -q
# 包含覆盖率
pytest -q --cov=app
# Lint 检查(提交前必须通过)
black --check .
ruff check .
```
CI 在每次推送和 PR 时运行:lint → 测试 → `pip-audit` → `bandit`。请参阅 `.github/workflows/ci.yml`。
## 故障排除
**`401 Unauthorized`**
设置与 `.env` 中 `API_KEY` 匹配的 `x-api-key` header。
**IOC 输出为空**
通过 `POST /api/ingest` 摄入至少一个具有可路由公共 IP 的事件。私有 IP(`RFC1918`、环回、链路本地)存储为 `src_ip=NULL`,且永远不会出现在导出中。
**找不到数据库 / 无表**
运行 `make db-migrate` 以创建 schema。应用程序在启动时不会自动迁移。
**端口已被使用**
释放端口 8088,或者在调用 `make run` 时设置 `PORT=<其他>`。
**启动时出现 `ValueError` / dashboard 登录总是失败**
`DASH_PASS` 必须是 bcrypt 哈希,而不是明文密码。使用以下命令重新生成:`python -c "from passlib.context import CryptContext; print(CryptContext(schemes=['bcrypt']).hash('your-password'))"` —— 更新 `.env` 中的 `DASH_PASS` 并重新启动。
## 发布自动化
此仓库使用 **semantic-release** 自动处理版本控制、标记和更新 changelog。
每次提交推送到 `main` 时:
1. GitHub Actions 会运行 **Auto Version & Release** 工作流。
2. 根据提交消息,它会确定正确的语义化版本更新。
3. 它生成或更新 `CHANGELOG.md`。
4. 它创建并发布新的 GitHub Release。
### 约定式提交示例
| 提交类型 | 示例 | 效果 |
| ----------- | ----------------------------------- | ------------------------- |
| **fix:** | `fix: resolve missing IOC export` | 修订版本发布 (x.x.+1) |
| **feat:** | `feat: add new dashboard API route` | 次要版本发布 (x.+1.0) |
| **perf!:** | `perf!: refactor ingestion engine` | 主要版本发布 (+1.0.0) |
## Changelog 与发布历史
[](https://github.com/stecrin/legiontrap-ti/releases/latest)
[查看 CHANGELOG.md →](https://github.com/stecrin/legiontrap-ti/blob/main/CHANGELOG.md)
## 许可证
基于 **MIT License** 获得许可 © 2025 **Stefan Cringusi**。
请参阅 [`LICENSE`](LICENSE) 中的全文。
**SPDX-License-Identifier:** MIT
标签:Elastic, GitHub, 威胁情报, 实时处理, 密码管理, 开发者工具, 测试用例, 网络安全, 蜜罐系统, 逆向工具, 隐私保护