jessn-dev/osint-atlas
GitHub: jessn-dev/osint-atlas
一个基于力导向图谱的开源情报工具导航平台,收录约 5,000 个资源,以 YAML 为唯一数据源并配备自动化链接健康检查、Wayback 回退和上游同步机制。
Stars: 0 | Forks: 0
# OSINT Atlas
OSINT Atlas 是一个图谱优先、具备链接健康感知的地图,收录了约 5,000 个开源情报工具和资源。通过力导向图谱进行浏览
(分组 → 类别 → 工具),并支持全局搜索、状态/分组过滤器以及 URL 关联枢轴。该项目以每个类别的 YAML 作为
唯一数据源,由 Astro 编译为部署在 GitHub Pages 上的静态网站。失效或被劫持的链接每周会被标记,并回退到
Wayback Machine;新工具会自动从上游备忘单同步。衍生自 Jieyab89 的 OSINT Cheat Sheet。
以 [Jieyab89/OSINT-Cheat-sheet](https://github.com/Jieyab89/OSINT-Cheat-sheet) 作为数据参考从零开始重建。
- **唯一数据源:** `data/categories/` 中每个类别对应一个 YAML 文件(经 Zod 验证)。
- **网站:** [Astro](https://astro.build) 静态构建 + D3 力导向图谱,部署至 GitHub Pages。
- **浏览:** 渐进式展开图谱(分组 → 类别 → 工具),全局模糊搜索、
状态/分组过滤器、URL 关联枢轴以及下钻侧边栏。
- **质量:** schema 验证、每周进行失效/被劫持链接检查并提供 Wayback 回退、
每周上游同步、单元测试、lint 以及部署后的冒烟测试 — 全部在 CI 中完成。
## 为什么进行重建
我经常依赖原始的备忘单,但每次我想修复死链或添加
工具时,都要在一个 6,000 行的文件中挣扎。因此,我围绕数据而不是文档对其进行了重建 —
相同的、我信任的资源,以一种我能够真正**维护、验证并保持安全可用**的形式。
| 原版中的问题 | 造成的后果 | 在此修复 |
| --- | --- | --- |
| 单个 6,300 行的 `README.md` 是唯一数据源 | Diff 痛苦、合并冲突频发、无法附加元数据 | 每个类别对应一个小型 YAML 文件,由 schema 强制约束 |
| `osint_data.json` 是从 README 中*反向抓取出来的* | 数据模型永远不会比 markdown 链接更丰富 | 直接编写的 YAML,包含标签、状态、描述和笔记 |
| 抓取器存在双 `requests.get` 漏洞,硬编码 `sleep(6)`,且无错误处理 | 速度慢、脆弱、会静默丢失数据 | 具备超时 + 重试 + 回写功能的并发验证器;仅添加的上游同步 |
| 尽管开发笔记中强烈呼吁,但没有链接健康检查 | 失效和被劫持的域名(被篡改为赌博重定向)依然被列出 | `check-links.ts` 每周标记 `dead`/`risky`;死链会获得 Wayback 回退 |
| 手写的漂移动画;点击仅触发 `window.open` | 没有真正的布局,没有集群,没有元数据,在约 5k 个节点时变成无法阅读的一团糟 | 真正的 `d3-force`,渐进式展开,搜索/过滤,关联,详情面板 |
| 扁平的类别,没有分组或标签 | 难以浏览约 5k 条目 | 15 个图谱分组 + 推断出的标签(`open-source`、`onion`、`official` 等) |
| 没有测试,没有 lint,没有 CI | 隐式发布功能衰退 | 单元测试,eslint/prettier,验证 + 部署 + 冒烟工作流 |
## 改变了什么以及为什么
按区域分组。每一行都是一个有目的性的变更及其动机。
### 数据与内容
| 变更 / 实现 | 原因 |
| --- | --- |
| **每个类别一个 YAML** (`data/categories/*.yml`) + Zod schema (`data/schema.ts`) | 小巧且易于审查的 Diff;在扁平的 markdown 中无法实现的元数据(标签/状态/描述/存档) |
| 通过 `scripts/regroup.ts` 实现 **15 组分类法**(`transport`、`finance`、`media`、`ai`、`search` 等) | 原本的扁平列表将约 108 个类别倾倒进一个桶里;分组驱动了图谱集群和图例 |
| **去重** (`scripts/dedupe.ts`) | 删除了 106 个类别内的重复 URL(真正的 bug);跨类别共享的 URL 被有意保留 — 它们为关联功能提供了动力 |
| **启发式标签** (`scripts/enrich.ts`) | 从 URL/名称信号中获取低成本的、确定性的标签(`open-source`、`onion`、`official` 等) — 无需 API |
| **LLM 描述/标签** (`scripts/llm-enrich.ts` Anthropic, `scripts/llm-enrich-gemini.ts` Gemini) | 真正的单行描述;两者皆是幂等且可恢复的。Gemini 路径为免费层级,因此贡献者无需付费密钥 |
| **死链 Wayback 回退** (`scripts/archive-dead.ts`) | 17% 的链接已失效 — 与其删除,不如链接至 `web.archive.org`,使资源保持有用 |
### 浏览体验
| 变更 / 实现 | 原因 |
| --- | --- |
| **渐进式展开**(分组 → 类别 → 工具,分批,轨道布局) | 同时渲染所有 182 个类别 + 5k 个工具会变成一团无法阅读、无法点击的混乱 |
| **URL 关联开关** | 跨类别共享的 URL 成为枢轴链接 — 这正是原始版本暗示过的“情报”视图 |
| **全局模糊搜索** (Fuse.js) + **状态/分组过滤器** | 在数千个工具中查找,无需手动展开图谱 |
| **下钻侧边栏**,带有过滤框、状态点、存档链接 | 原版只能打开一个 URL;该面板使任何节点都可完全检查和导航 |
| **深层链接** (`?q`/`?group`/`?cat`/`?status`/`?corr`) + **移动端布局** + **ARIA** | 可共享的视图,在手机上可用,对屏幕阅读器友好 |
### 工具、CI 与部署
| 变更 / 实现 | 原因 |
| --- | --- |
| **单元测试** (`node:test`) + **eslint/prettier** | 锁定纯逻辑(`slugify`/`inferGroup`/`parseMarkdown`)和代码风格;作为每个 PR 的门禁 |
| **更智能的链接检查**(`FRESH_DAYS` 跳过,针对单个主机的礼貌策略) | 不要每周重新探测 5k 个链接或猛击单个主机;行为更快且更规范 |
| **SEO 再生成** (`scripts/seo.ts`: robots/sitemap/llms.txt) + OG meta + favicon | 可发现性,从数据中重新生成,因此永远不会产生偏移 |
| **构建守卫** (`scripts/check-build.ts`) + **部署后冒烟测试** (`scripts/smoke.ts`) | 过去“绿色”的部署有时会发布一个空白页;这些操作在缺少 asset / feed 为空 / 实时 URL 错误时会响亮地报警 |
| **`.nojekyll`** + 正确的 `configure-pages` 顺序 | GitHub Pages 的 Jekyll 剥离了 Astro 的哈希 asset 目录;base path 必须在构建前设置好 |
## 数据模型
```
category: Social Media Search
slug: social-media-search # unique, kebab-case
group: socmint # one of the 15 graph clusters
description: ...
items:
- name: Tool X
url: https://example.com
tags: [free, account-required]
status: active # active | dead | risky | unchecked
last_checked: 2026-06-22
archive_url: https://web.archive.org/web/2/https://example.com # set for dead links
note: optional
```
Schema 和类型:`data/schema.ts`(唯一数据源,被每个脚本导入)。
## 命令
```
npm install
npm run dev # local site
npm run build # data:build + seo + astro build + build guard
npm test # unit tests
npm run lint # eslint + prettier check (npm run format to fix)
# data pipeline
npm run data:validate # schema-check all categories (CI gate)
npm run data:build # compile YAML -> public/osint.json
npm run data:dedupe # drop within-category duplicate URLs
npm run data:regroup # re-apply the group taxonomy
npm run data:enrich # heuristic tags + templated descriptions (idempotent)
npm run data:archive # add Wayback fallback to dead links (idempotent)
# ingestion & health
npm run data:sync # add-only pull of new tools from upstream (DRY=1 to preview)
npm run links:check # probe URLs, write status back (LIMIT/FRESH_DAYS/CONCURRENCY)
# LLM enrichment (选择其一;两者均为 idempotent + resumable)
GEMINI_API_KEY=... npm run data:llm-enrich-gemini # free tier, slow, daily cap
ANTHROPIC_API_KEY=... npm run data:llm-enrich # paid Batch API, one-shot
# ops
npm run seo # regenerate robots.txt / sitemap.xml / llms.txt
SMOKE_URL=... npm run smoke # check a deployed URL’s page + assets + feed
```
## 架构
```
add-only sync ─┐
upstream cheat-sheet ───────────────────┤
▼
data/categories/*.yml ── validate ──► build-data ──► public/osint.json
▲ ▲ ▲ │
check-links│ archive│ enrich/regroup│ (status / archive_url / tags writeback)
└──────┴──────┴──── (all write back into the YAML) ───┘
▼
src/ (Astro page + D3 graph island)
│
build guard ──► GitHub Pages ──► smoke test
```
YAML 是唯一数据源。每个脚本都会读取/写入 YAML;`public/osint.json` 是一个生成的、
被 git 忽略的 feed,供前端获取。
## 维护计划
该仓库将**内容**与**质量控制**分离开来,因此大部分维护工作都可以自动化。
### 1. 添加 / 编辑资源(人工)
- 编辑 `data/categories/*.yml`,或添加一个带有唯一 `slug` + `group` 的新文件。
- 最低限度需要 `name` + `url`;将 `status` 留为 `unchecked` — 链接检查器会将其填充完整。
- 适用于多个类别的工具应出现在每个类别中 — 共享的 URL 是有意为之的,并
将成为图谱中的关联链接。
- 发起一个 PR。`validate.yml` 将运行 lint + typecheck + 测试 + schema 验证 + 构建。
### 2. 上游同步(自动,每周 — `sync.yml`,UTC 时间周一 05:00)
- 运行 `data:sync` 并使用在上游发现的新工具打开 `bot/upstream-sync`。
- **仅添加**:从不编辑/删除现有条目,因此手动添加的标签/描述/状态得以保留。
新工具以 `status: unchecked` 状态落地。审查其相关性和安全性,然后合并。
### 3. 链接健康(自动,每周 — `links.yml`,UTC 时间周一 06:00)
- 运行 `links:check` 然后 `data:archive`,打开 `bot/link-health` 并带有更新后的
`status` / `last_checked`,以及针对新死链的 Wayback 回退。
- 对 PR 进行分类:清理/替换 `dead`,立即移除 `risky`(已知的不良域名或赌博/篡改
关键词)。将新被劫持的域名录入到 `scripts/check-links.ts` 的 `KNOWN_BAD` 中。
### 4. 丰富数据(手动,根据需要)
- 在批量导入之后,运行 `data:enrich`(启发式)和/或进行一次 LLM 处理以获取真实描述。
- Gemini 路径属于免费层级且可恢复 — 可跨天运行;它会自动跳过已有描述的条目。
### 5. 发布(自动 — `deploy.yml`)
- 合并到 `main` → 构建(带有守卫) → 部署至 Pages → 对实时站点进行**冒烟测试**。
- `public/osint.json` 和 SEO 文件是生成的,从不进行手动编辑(被 git 忽略)。
### 6. 定期维护
- 在大批量导入后重新运行 `data:dedupe` / `data:regroup` / `data:enrich`(皆是幂等操作)。
- 随着数据的增长,重新审视 `scripts/derive.ts` / `scripts/enrich.ts` 中的分组分类法和标签规则。
**健康信号:** 网站页眉显示实时的 `categories · tools · dead · risky` 计数。上升的
`dead`/`risky` 意味着是时候进行一次清理了。
## 部署
GitHub Pages 通过 `.github/workflows/deploy.yml` 实现。**Settings → Pages → Source 必须设置为“GitHub
Actions.”** `PAGES_BASE` 是从 Pages 配置中设置的(`configure-pages` 在构建*之前*运行,因此
base path 已被固化到 asset URL 中)。两个不太明显的要求,都是惨痛教训后学到的,
现在都由冒烟测试把关:
- **`public/.nojekyll`** — 没有它,Pages 会运行 Jekyll 并剥离 Astro 哈希过的 `assets/` 目录,
导致尽管部署“成功”,提供的却是一个空白页。
- **没有 `static_site_generator` 输入项** 在 `configure-pages@v5` 上 — 它在 v5 上会抛出隐晦的
`TypeError`;`.nojekyll` 已经处理好了 Jekyll。
## 致谢
资源列表衍生自 Jieyab89 的 OSINT Cheat Sheet(由社区维护)。代码为净室重新实现;请参阅[为什么进行重建](#why-this-was-rebuilt)。
标签:Astro, D3.js, ESC4, OSINT, 自动化攻击, 资源导航, 静态站点