thunderstornX/rag-threat-intel

GitHub: thunderstornX/rag-threat-intel

rag-threat-intel:一个主权 RAG 管道,用于漏洞和威胁情报问答。

Stars: 1 | Forks: 0

# rag威胁情报 [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20480465.svg)](https://doi.org/10.5281/zenodo.20480465) [![许可: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![测试](https://img.shields.io/badge/tests-40%20passing-brightgreen)](#tests) [![MRR@10](https://img.shields.io/badge/MRR%4010-0.83%20semantic-blue)](#real-measured-eval) [![堆栈](https://img.shields.io/badge/stack-Ollama%20%2B%20pgvector%20%2B%20FastAPI-blue)](#stack) ![banner](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/0ef61dce89081730.png) 一个**主权 RAG 管道**,它将 NIST NVD CVE 源和公共安全 PDF 文件摄取到 pgvector 中,通过余弦相似度检索,+ MMR 重新排序,并使用 Ollama 提供的 LLM 生成——答案中的每个断言都必须引用检索集中或模型被告知拒绝的 `[doc_N]` 标签。 这个仓库的目的是一个“魔法问答箱”,而是在真实评估集上对三种分块策略进行的**测量比较**,报告了 MRR@5 / MRR@10 和三个轴的忠实度指标。下面的数字来自真实的本地运行,不是虚构的——请参阅 `results/README.md` 以获取复制配方。 ## 真实测量评估 (v1.0.0) 语料库:7 个命名的历史 CVE(Log4Shell、Heartbleed、Apache Struts、BlueKeep、Zerologon、Spring4Shell、regreSSHion)+ 30 个最近 CVE 记录,嵌入 `all-minilm`(384-d)。 ### 14 个 CVE 回忆查询的 MRR | 策略 | MRR@5 | MRR@10 | 平均延迟 | |----------------|-------:|-------:|-------------:| | fixed_size | 0.768 | 0.780 | 39.9 毫秒 | | **语义** | **0.821** | **0.833** | 42.2 毫秒 | | sentence_window | 0.762 | 0.762 | 40.4 毫秒 | **语义策略比句子窗口策略高约 5 个百分点**。句子窗口表现不佳,因为每个 CVE 都很短(每条记录一个描述);当父文档本身是一段时,±N 邻居上下文购买力很小。 ### 忠实度,语义策略,llama3.2:1b,n=12 | 信号 | 值 | |---------------------------|----------:| | 平均引用密度 | 0.201 | | **平均引用有效性**| **1.000** | | 平均拒绝诚实度 | 0.333 | | 平均时钟/查询 | 53.4 秒 | 诚实的阅读:1B 模型在我们的严格引用合同下**选择拒绝而不是冒险提出未引用的断言**。12 个查询中有 8 个收到了规范拒绝,即使检索器返回了相关片段。当它参与时,引用有效性是完美的(没有虚构的 `[doc_N]` 标签)。这正是三个轴评分器旨在揭示的权衡——一个数字会隐藏它。 ## 堆栈 ``` ┌──────────────────────────────┐ │ FastAPI /query │ └────────────┬─────────────────┘ │ ┌────────────────────────┼─────────────────────────┐ │ │ │ ┌────▼────┐ ┌─────────▼──────────┐ ┌──────▼──────┐ │ Ollama │ │ pgvector (HNSW) │ │ Ollama │ │ embed │ │ chunks_fixed_size │ │ llama3.2:3b│ │ nomic-* │ │ chunks_semantic │ │ generate │ │ all-minilm │ chunks_sentence_* │ └─────────────┘ └─────────┘ └────────────────────┘ ``` 三个 pgvector 表——每个分块策略一个——所以运行时的查询只是“我在哪个表中查找?”HNSW 索引是 pgvector 随带提供的默认余弦索引;我们不调整 `m` / `ef_construction`,因为语料库足够小(几千个 NVD 记录和一些 PDF 文件),默认值占主导地位。 ## 快速入门 ``` git clone https://github.com/thunderstornX/rag-threat-intel.git cd rag-threat-intel cp .env.example .env # 1. 启动 pgvector + Ollama + FastAPI docker compose up -d # 2. 拉取模型(仅第一次) docker compose exec ollama ollama pull llama3.2:3b docker compose exec ollama ollama pull nomic-embed-text docker compose exec ollama ollama pull all-minilm # 3. 将一些PDF文件放入 corpus/pdfs/ 中(NIST SP 800-53,ATT&CK 白皮书等) # 然后导入: docker compose exec api python -m ingest.bootstrap # 4. 提出一个问题 curl -sS -X POST http://localhost:8000/query \ -H 'content-type: application/json' \ -d '{"question":"What is the CVSS score of CVE-2021-44228?", "strategy":"semantic", "top_k":10, "top_n":4}' ``` 响应是一个包含答案、引用的 `[doc_N]` 标签、检索来源(带相似度分数)和每阶段时间的 JSON 封装。 ### 运行评估通过 ``` docker compose exec api python -m eval.eval_mrr # MRR@5/10 across strategies docker compose exec api python -m eval.eval_faithfulness # answer citation density + refusal honesty ``` 评估脚本在 `results/` 下写入每个查询的 CSV 文件,并将摘要 JSON 打印到 stdout。50 个问题的测试集位于 `eval/test_queries.json`——它是有意混合的 **CVE 回忆查询**(答案应出现在语料库中)、**PDF 查找查询**(语料库取决于您摄取的内容)和 **硬负例**,包括 `expected_refusal: true` 行,以测试系统是否在应该时诚实地说“我无法回答这个问题”。 ## 仓库布局 ``` . ├── ingest/ │ ├── document.py # uniform Document shape (text + source + metadata + fingerprint) │ ├── nvd_fetcher.py # NIST NVD 2.0 API client (paginated, polite-sleep) │ ├── pdf_loader.py # pypdf-based per-page loader │ ├── chunker.py # 3 chunking strategies — see below │ └── bootstrap.py # one-shot: fetch + chunk + embed + write ├── embeddings/ │ ├── embed.py # Ollama /api/embeddings client │ └── embed_compare.py # nomic-embed-text vs all-minilm side-by-side ├── retrieval/ │ ├── store.py # pgvector + HNSW + cosine; one table per strategy │ └── reranker.py # Maximal Marginal Relevance (Carbonell-Goldstein) ├── generation/ │ ├── prompts.py # mandatory-citation system prompt + refusal contract │ └── generator.py # Ollama /api/chat + citation-tag extractor ├── api/ │ └── main.py # FastAPI: /query, /health, /health/ready ├── eval/ │ ├── test_queries.json # 50 queries (CVE recall · PDF lookup · hard negatives · expected refusals) │ ├── eval_mrr.py # MRR@5/10 per chunking strategy │ └── eval_faithfulness.py # citation density · validity · refusal honesty ├── tests/ # 40 pytest cases (run in <2s, no Ollama required) ├── docker-compose.yml # pgvector/pgvector:pg16 + ollama + api ├── Dockerfile ├── corpus/pdfs/ # operator-populated PDF corpus └── paper/ # 3-page IEEE methodology paper ``` ## 三种分块策略 仓库的核心实验。所有三个都位于 `ingest/chunker.py` 中,并通过 `chunk_documents(docs, ChunkStrategy.X)` 暴露: | 策略 | 它做什么 | |----------------|--------------------------------------------------------------------------------------------------------------------------------| | `fixed_size` | 字符预算为 ≈512 个标记,≈50 个标记重叠。退回到最近的空白处,这样我们就不会在单词中间分割。 | | `semantic` | 在标题模式(markdown `#`、NIST 风格 `1.2.3 标题`、全大写短行)和段落之间分割。**块永远不会跨越标题**。短段落仅在**一个部分内**合并到 `max_chars`。 | | `sentence_window` | 每个句子都是自己的块;块文本包括中心句子加上 ±N 邻居,以便嵌入器看到上下文。 | 目标不是宣布一个赢家——不同的语料库偏爱不同的策略。仓库的评估报告了每个策略的 MRR,论文讨论了 *为什么* 你会选择一个而不是另一个。 ## 测试 40 个 pytest 用例。**完整套件运行在 ~1.3 秒**——它们都不需要 Ollama 或 pgvector 运行;它们模拟了线。 ``` python -m pytest tests/ -v ``` 覆盖率: - **chunker 不变** — fixed-size 尊重预算 + 重叠,语义分割在 NIST 风格部分 ID 上,永远不会跨越标题,sentence-window 包括 ±N 邻居 - **文档指纹** — 在实例之间稳定,当文本更改时更改 - **NVD 捕获器** — 扁平化提取 CVSS + CWE + 引用,HTTP 错误不会中止摄取,跳过无 ID 的项 - **嵌入器** — 线格式,维度检测,**HTTP 错误永远不会回显响应体** - **MMR 重新排序器** — 正交余弦 = 0,相同 = 1,当 lambda 偏好多样性时选择不同的对,长度不匹配引发 - **生成器** — 提取引用标签,删除虚构的 `[doc_42]` 标签,检测拒绝句子,**在错误路径中永远不会回显服务器体** - **评估辅助工具** — 互反排名正确性,忠实度评分器,拒绝诚实度逻辑 ## 方法论说明 ### 为什么没有 LLM 作为检索的法官 `eval_mrr.py` 使用测试集中的真实 `expected_relevant_source_ids`,而不是 LLM 评分“这是否相关”。原因与记录在兄弟 [`llm-red-team-toolkit`](https://github.com/thunderstornX/llm-red-team-toolkit) 和 [`agentic-osint-agent`](https://github.com/thunderstornX/agentic-osint-agent) 中的相同: LLM 判决者不可重复,在不同模型检查点之间漂移,并且在标准模糊时膨胀分数。作者标记的真实信息是可审计的。 ### 为什么我们不将答案忠实度合并为一个数字 忠实度评估报告了三个正交信号: 1. **引用密度** — 以 `[doc_N]` 标签结束的句子比例 2. **引用有效性** — 指向真实检索文档的引用标签比例(不是虚构的 `[doc_42]`) 3. **拒绝诚实度**——对于标记为 `expected_refusal: true` 的查询,模型实际上拒绝了吗?对于非拒绝查询,它避免了虚假拒绝吗? 将这些合并为一个数字会隐藏权衡。拒绝每个问题的模型在有效性(没有无效标签,因为没有标签)和引用句子密度(同样)上得分为 100%,这显然不是“好的 RAG”。三个数字一起告诉了真实的故事。 ## 道德使用 这是一个用于漏洞和威胁情报**阅读**的研究成果——不是用于制作漏洞利用的工具。系统提示明确拒绝有害请求;测试集包含一些那些行(`category: harmful`),评估验证模型拒绝。 如果您找到一种方法使此管道输出它不应该输出的内容,请提出问题——这正是测试集旨在捕获的发现。 ## 引用此工作 ``` @software{bhutto2026ragthreatintel, author = {Bhutto, Ali Murtaza}, title = {rag-threat-intel: A sovereign RAG pipeline for vulnerability and threat-intelligence Q\&A}, year = {2026}, doi = {10.5281/zenodo.20480465}, url = {https://github.com/thunderstornX/rag-threat-intel}, orcid = {0009-0007-2787-943X} } ``` 同一系列中的相关工作: - [`agentic-osint-agent`](https://github.com/thunderstornX/agentic-osint-agent) — LangGraph ReAct OSINT 调查员(使用相同的评估纪律 / 无 LLM 判决者哲学) - [`llm-red-team-toolkit`](https://github.com/thunderstornX/llm-red-team-toolkit) — 对 LLM 部署的对抗性探测(此项目的逆:测试模型,而不是使用它们) - [`sovereign-llm-quickstart`](https://github.com/thunderstornX/sovereign-llm-quickstart) — 此仓库指向的本地 Ollama 堆栈 ## 许可证 MIT © 2026 Ali Murtaza Bhutto ``` ▓▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓▓▓▓▓ │ ▓▓▓▓▓▓ NVD · PDFs · OSINT · ... >>>>> retrieve · rerank · generate · cite >>>>> ``` ~ AMB · ORCID 0009-0007-2787-943X · v1.0 · 2026 ~
标签:AI风险缓解, Apex, AV绕过, BSD, Chaos, CVE, FastAPI, LLM评估, MRR评估, NVD, Ollama, pgvector, 信息检索, 反取证, 向量数据库, 威胁情报, 安全测试, 安全漏洞, 安全研究工具, 安全研究平台, 安全策略, 安全评估, 安全防护, 开发者工具, 性能比较, 提示词设计, 攻击性安全, 数字签名, 数据挖掘, 文本分析, 机器学习, 漏洞分析, 相似度搜索, 请求拦截, 路径探测, 问答系统