jackkbrosnan/nti-osint
GitHub: jackkbrosnan/nti-osint
面向核设施安全的每日开源情报管道,聚合多个公开数据源并基于地理围栏和相关性评分实现事件监控与自动摘要推送。
Stars: 0 | Forks: 0
# NTI OSINT 威胁情报 Pipeline
一个用于核安全监控的每日威胁情报 pipeline。从九个开源 feed 摄取数据,针对全球 195 个核与辐射站点进行事件的地理围栏匹配,根据相关性进行评分,并通过 webhook 将摘要推送到 Microsoft Teams 频道。
作为 Nuclear Threat Initiative (NTI) 的概念验证而构建。
## 工作原理
```
Feeds (9)
├── USGS Earthquakes
├── NOAA/NWS Active Alerts
├── ACLED Conflict Events ──┐
├── GDELT Events 2.0 ──┤─── Geofence (200km radius) ──► Alerts table
│ │
├── GDELT DOC News ──┐│
├── NewsAPI ──┤┘
├── NIST NVD (ICS CVEs) ──┤─── Relevance scoring ──► Digest (skip geofence)
├── DHS NTAS Advisories ──┤
└── CISA KEV ──┘
Digest ──► Microsoft Teams Adaptive Card
```
**地理空间 feed**(USGS、NOAA、ACLED、GDELT Events)使用半正矢半径过滤器对每个受监控的站点进行检查。通过距离和最低严重性阈值的事件将被写入 `alerts` 表。
**非地理空间 feed**(GDELT DOC、NewsAPI、NVD、NTAS、CISA KEV)跳过地理围栏,并直接在指定的摘要部分展示。
所有事件都会根据包含 34 个词的关键词列表进行 0.0–1.0 的评分。低于 `MIN_RELEVANCE_SCORE` 的事件会被存储,但不会在摘要中展示。这是主要的降噪手段。
## Feed
| Feed | 类型 | 认证 | 状态 |
|------|------|------|--------|
| USGS Earthquakes | 地理空间 | 无 | 活跃 |
| NOAA/NWS Alerts | 地理空间 | 无 | 活跃 |
| GDELT Events 2.0 | 地理空间(冲突) | 无 | 活跃 — 当 ACLED Research 级别处于活动状态时禁用 |
| ACLED Conflict | 地理空间 | OAuth2 Bearer | 受阻 — 需要 Research 级别访问权限 |
| GDELT DOC News | 非地理空间 | 无 | 活跃 |
| NewsAPI | 非地理空间 | API key | 活跃 |
| NIST NVD | 非地理空间(ICS CVE) | 可选 API key | 活跃 |
| DHS NTAS | 非地理空间 | 无 | 活跃 — feed 目前为空 |
| CISA KEV | 非地理空间 | 无 | 活跃 |
## 前置条件
- Docker Desktop(推荐的部署方式)
- **或者** Python 3.11+ 配合 pyenv 用于本地开发
所需的 API 凭据:
- `NEWSAPI_KEY` — 在 [newsapi.org](https://newsapi.org) 获取免费层级
- `TEAMS_WEBHOOK_URL` — Teams 频道 → **...** → Workflows → "Post to a channel when a webhook request is received"
- `ACLED_USERNAME` / `ACLED_PASSWORD` — [acleddata.com](https://acleddata.com)(需要 Research 级别权限)
- `NVD_API_KEY` — 可选;可提高速率限制。在 [nvd.nist.gov/developers](https://nvd.nist.gov/developers) 注册
## 部署 (Docker)
这是实现持久化、易于交接的部署的推荐路径。
**1. 克隆并配置**
```
git clone
cd nti-osint
cp .env.example .env # then fill in credentials
```
`.env` 文件格式:
```
TEAMS_WEBHOOK_URL=https://prod-xx.westus.logic.azure.com/...
NEWSAPI_KEY=your_key_here
ACLED_USERNAME=you@example.com
ACLED_PASSWORD=your_password_here
NVD_API_KEY=your_key_here # optional
```
**2. 构建并启动**
```
docker compose up -d
```
容器将会:
1. 初始化 SQLite 数据库并加载 195 个站点的核站点参考数据
2. 启动调度器 — pipeline 默认每天 07:00 UTC 运行
数据库持久化存储在一个命名的 Docker volume (`nti-osint_nti-osint-data`) 中,并且在容器重启和镜像重建后依然保留。
**3. 常用命令**
```
# 查看实时日志
docker compose logs -f nti-osint
# 立即运行 pipeline(例如,在部署后进行测试)
docker compose run --rm -e RUN_ON_START=true nti-osint
# 在正在运行的 container 内手动运行
docker exec -it python /app/run.py
# Dry run(将 digest 打印到 stdout 而不发送到 Teams)
docker exec -it python /app/run.py --dry-run
# 在代码更改后重新构建
docker compose up -d --build
```
**更改运行时间**
编辑 `docker-compose.yml` 并将 `RUN_HOUR` 设置为所需的 UTC 小时 (0–23),然后运行 `docker compose up -d`。
## 本地开发
```
# 需要 Python 3.11(通过 pyenv 管理)
pyenv install 3.11.9
pyenv local 3.11.9
pip install haversine requests
# 初始化 database,加载 sites,填充 keywords
python db/init_db.py
# 完整的 pipeline 运行
python run.py
# 仅 ingest,不进行 digest 发送
python run.py --no-digest
# 打印 digest 到 stdout 而不发送
python run.py --dry-run
```
## 配置
所有调整参数都位于 [config.py](config.py) 中。进行阈值调整时无需编辑其他文件。
### 降噪
| 参数 | 默认值 | 效果 |
|-----------|---------|--------|
| `MIN_RELEVANCE_SCORE` | `0.2` | 出现在摘要中的最低关键词评分 (0–1)。调高以减少噪音。 |
| `MIN_SEVERITY[event_type]` | 视情况而定 | 写入 alerts 表的最低标准化严重性 (0–10)。 |
| `GDELT_EVENTS_MIN_GOLDSTEIN` | `-3.0` | GoldsteinScale 下限。`-3` = 有意义的冲突。向 `0` 调高以减少数据量。 |
| `GDELT_EVENTS_MIN_MENTIONS` | `2` | 最小跨源提及次数。过滤单篇文章的 GDELT 噪音。 |
### 回溯窗口
| 参数 | 默认值 | 备注 |
|-----------|---------|-------|
| `DIGEST_LOOKBACK_HOURS` | `24` | 摘要往回查找未发送警报的时间范围。 |
| `NEWSAPI_LOOKBACK_HOURS` | `48` | 延长至 48 小时,以弥补免费层级索引延迟。 |
| `NVD_LOOKBACK_HOURS` | `48` | NVD 有约 24 小时的处理延迟;48 小时窗口可捕获最近的 CVE。 |
| `GDELT_EVENTS_LOOKBACK_HOURS` | `24` | 24 小时 = 96 个文件(每次运行下载约 5.5MB)。 |
### 地理围栏半径
默认值为 200 公里。`SITE_TYPE_RADIUS` 中针对站点类型的覆盖设置:
```
"reactor": 200 km
"research_reactor": 100 km
"enrichment": 300 km
"storage": 150 km
```
## 项目结构
```
nti-osint/
├── config.py # All tunable parameters and credentials
├── run.py # Pipeline orchestrator
├── db/
│ ├── schema.sql # SQLite schema
│ └── init_db.py # DB init, site load, keyword seed (idempotent)
├── ingest/ # One module per feed; each returns raw dicts
│ ├── usgs.py
│ ├── noaa.py
│ ├── acled.py
│ ├── gdelt_events.py
│ ├── gdelt.py
│ ├── newsapi.py
│ ├── nvd.py
│ ├── ntas.py
│ └── cisa_kev.py
├── normalize/
│ └── normalizer.py # Feed-specific normalizers → common event schema
├── alerts/
│ └── geofence.py # Haversine geofence; writes to alerts table
├── digest/
│ └── digest.py # Fetch, score, format, deliver
├── data/
│ └── sites/
│ └── nuclear_sites.csv # 195 nuclear/radiological sites (gitignored)
├── Dockerfile
├── docker-compose.yml
└── docker-entrypoint.sh
```
## 数据库
使用开启了 WAL 模式的 SQLite。由 `db/init_db.py` 初始化(可以安全地重复运行)。
关键表:
- **`sites`** — 195 个受监控的核与辐射站点,包含经纬度和站点类型
- **`events`** — 来自所有 feed 的标准化摄取数据;`source_id` + `source` 是去重键
- **`alerts`** — 通过了地理围栏和严重性阈值的事件;通过 `digest_sent` 跟踪推送状态
- **`keywords`** — 用于相关性评分的包含 34 个词的加权关键词列表
- **`feed_runs`** — 包含状态和事件计数的各 feed 运行日志
`events` 表上的 `geofenced_at` 列是地理围栏步骤的幂等标记。`geofenced_at IS NULL` 的事件将在下次运行时处理,因此 pipeline 在运行中途崩溃不会丢失事件。
## 交接说明
除了 Docker volume 和 `.env` 文件之外,该 pipeline 没有其他外部状态依赖。要将其迁移到新主机:
```
# 在新的 host 上
git clone
cd nti-osint
# 使用 credentials 恢复 .env
docker compose up -d --build
```
数据库在首次运行时将为空,并根据 feed 的回溯窗口 (24–48 小时,具体取决于 feed) 进行回填。首次运行时 GDELT Events 耗时最长(约 96 个文件,约 30 秒)。
如果迁移现有数据库,请复制 Docker volume 数据:
```
# 在旧的 host 上
docker run --rm -v nti-osint_nti-osint-data:/data -v $(pwd):/backup alpine \
tar czf /backup/nti-osint-data.tar.gz -C /data .
# 在新的 host 上(在执行 docker compose up -d 之后)
docker run --rm -v nti-osint_nti-osint-data:/data -v $(pwd):/backup alpine \
tar xzf /backup/nti-osint-data.tar.gz -C /data
```
## 已知限制
- **ACLED** 需要 Research 级别访问权限。在此期间,GDELT Events 2.0 充当地理空间冲突的桥梁,但 GDELT 的 NLP 会将一些企业/法律事件错误归类为冲突。调整 `GDELT_EVENTS_MIN_GOLDSTEIN` 和 `GDELT_EVENTS_MIN_MENTIONS` 以减少噪音。
- **DHS NTAS** feed schema 未公开文档化。解析器可以处理已知的字段名变体,但在发布真正的安全警报时可能需要进行调整。
- **GDELT DOC** 的速率限制非常严格。超时会被捕获并跳过;下一次每日运行将通过回溯窗口补齐遗漏的文章。
- **NewsAPI** 免费层级有约 24 小时的索引延迟,并会返回重大新闻的重复报道。48 小时的回溯窗口弥补了这一延迟;重复报道属于 Phase B LLM 摘要要解决的问题。
标签:ESC4, OSINT, Python, 地理围栏, 威胁情报, 开发者工具, 数据管道, 无后门, 版权保护, 自动化通知, 请求拦截, 软件工程, 逆向工具