news 2026/9/16 15:05:39

Local Deep Research 1.9.1 版本解析:Sofya 搜索引擎接入、SQL LIKE 通配符注入加固与 API 密钥脱敏修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Local Deep Research 1.9.1 版本解析:Sofya 搜索引擎接入、SQL LIKE 通配符注入加固与 API 密钥脱敏修复

Local Deep Research 1.9.1 版本解析:Sofya 搜索引擎接入、SQL LIKE 通配符注入加固与 API 密钥脱敏修复

【免费下载链接】local-deep-research~95% on SimpleQA (e.g. Qwen3.6-27B on a 3090). Supports all local and cloud LLMs (llama.cpp, Ollama, Google, ...). 10+ search engines - arXiv, PubMed, your private documents. Everything Local & Encrypted.项目地址: https://gitcode.com/GitHub_Trending/lo/local-deep-research

Local Deep Research(LDR)1.9.1 是一次以安全加固为主轴、并带来首个 Sofya 托管搜索引擎支持的版本。本篇基于仓库中 docs/release_notes/1.9.1.md 的发布说明,结合对应源码实现,完整拆解该版本的五类变更:SQLLIKE通配符转义、diagnose 门控的安全日志体系、GET /settings/api/bulk明文密钥泄露修复、异常信息在 HTTP 边界的净化,以及 Sofya 引擎的两阶段检索实现。读完本文,你可以理解 LDR 如何在自己数据库与 API 层封堵通配符注入和 CWE-209 信息泄露,并能实际配置 Sofya 引擎接入本地深度研究流程。

1. SQL LIKE 通配符注入:用户输入如何变成通配匹配

发布说明在 Security 一节列出的第一条修复,针对的是"用户提供的字符串流入 SQLLIKE查询"这一通用场景,涉及四处:

  • 资料库(library)搜索与域名过滤器;
  • 域名分类器(domain-classifier)的采样;
  • 新闻订阅过滤器(news subscription filters)。

修复前的行为是:SQL 中%_是通配符,%匹配任意长度任意字符,_匹配单个任意字符。因此当用户输入100%(例如"显示 100% 的相关性"这类过滤条件)或a_b时,这两个字符被数据库当作通配符解释,匹配到了无关的行。发布说明将其定性为"通配符注入/枚举向量(wildcard-injection/enumeration vector)"——攻击面局限在用户自己的数据库内,但依然可以被用来枚举本不该命中的记录。修复方式是统一转义%_,使搜索与过滤按字面量精确匹配(对应上游 PR #3094)。

这一类漏洞的通用排查思路可以推广到任何使用LIKE的 Python 项目:凡是"用户输入 →LIKE子句"的链路,都需要显式转义通配符,否则100%50%_offa_b这类看起来无害的值都会扩大匹配范围。

2. 安全日志体系:diagnose 门控的 secure_logging 包装器

1.9.1 的第二个安全变更影响面最大:所有 LLM provider、embedding provider 和 web search engine 模块的日志,现在统一走 diagnose 门控的security.secure_logging包装器。其核心目标是:这些路径上的异常堆栈(traceback)只有在显式开启 diagnose 模式时才会写入日志——生产日志不再收到可能回显 API key 或请求内部状态的完整堆栈。

仓库中的实现是 src/local_deep_research/security/secure_logging.py。从源码可以看到关键设计:

  • 该模块是一个对 loguru 的薄代理,重写exception()方法;
  • 是否附加异常堆栈由is_diagnose_mode()决定,该函数要求LDR_APP_DEBUGLDR_LOGURU_DIAGNOSE两个环境变量同时为真("1""true""yes");
  • 未开启 diagnose 时,调用logger.exception(...)不会通过.opt(exception=True)附加堆栈,日志中只剩消息文本。
# src/local_deep_research/security/secure_logging.py(节选) def is_diagnose_mode() -> bool: """Requires BOTH ``LDR_APP_DEBUG`` and ``LDR_LOGURU_DIAGNOSE`` to be for loguru's frame-locals ``diagnose`` rendering.""" return env_truthy("LDR_APP_DEBUG") and env_truthy("LDR_LOGURU_DIAGNOSE")

也就是说,排查 provider 故障时,你可以临时设置这两个环境变量来获得完整堆栈;生产环境则默认拿不到堆栈细节,从机制上降低了 API key 经日志泄露的概率。

发布说明还提到,配套引入的check-sensitive-loggingpre-commit 钩子将上述约定"以构造方式强制(enforces this by construction)"。钩子的拒绝面相当宽:

  • 在这些目录内,直接的 loguru 原始 import 会被拒绝;
  • 所有已知绕过路径都会被拦截:logger.opt()@logger.catch、私有 logger 句柄、traceback/sys.exc_info()插值、动态 import loguru/traceback、logger 重绑定、getattr规避;
  • bind()/patch()链式调用的消息,与直接 logger 调用接受同样的异常清洗检查;
  • 通过属性或下标访问拿到的异常细节(如e.argse.response.texte.strerror)会被按与str(e)同级的规则标记。

这种"包装器 + 静态钩子"的双层设计值得借鉴:包装器负责运行时行为正确,钩子负责在提交前阻止未来代码重新绕过它。

3.GET /settings/api/bulk明文密钥泄露修复

这是 1.9.1 中最典型的一个"接口不对称"漏洞,涉及两个 PR:

#5028:命名空间请求返回未脱敏的嵌套密钥。GET /settings/api/bulk支持按keys[]参数批量取设置。当请求一个命名空间(例如keys[]=llm)时,接口会返回该命名空间下所有设置,其中嵌套的*.api_key值原本没有被打码——因为脱敏检查只看最外层请求的 key 名,没有递归进入设置子树。更严重的是keys[]=%可以通过未转义的LIKE把整张设置表都导出来。修复包含两点:脱敏现在递归进入设置子树;key 查找对LIKE通配符做了转义。

#5038:非常规命名的 password 类型设置漏网。bulk 端点只按 key 名做脱敏,而单条 GET 端点按类型打码。于是叶子名不在"经典 secret token"清单内的 password 类型设置(例如client_secretsecret_keybearer_tokenapi_secretapp_secret)在 bulk 请求中被明文带出,而单条 GET 会正确打码。修复后 bulk 端点也按类型 + 这类命名补齐脱敏。

对使用 LDR 自托管部署的用户,这个修复的意义在于:任何调用/settings/api/bulk的前端页面或第三方集成,都不可能再经由命名空间批量请求拿到 API key 明文。

4. 异常字符串的 HTTP 边界净化:关闭 CWE-209 信息暴露

发布说明的第四条安全项描述了一条完整的泄露链路:被捕获的异常被插值进策略层的错误字段后,可以原样到达 API 客户端——出现在quick_summary()/analyze_documents()实际返回的summary字段、formatted_findings,或逐条 finding 的content中,凭据和堆栈文本都可能完整保留。CodeQL 将其标记为 CWE-209(错误消息中的信息暴露)。

修复方案是在两个层面净化:

  1. HTTP 边界/api/v1/quick_summary/api/v1/analyze_documents的 JSON 响应中,凡是由异常派生的字符串都经过sanitize_error_for_client处理;
  2. 策略层SourceBasedSearchStrategy内部同样净化,独立覆盖 web-UI 的 SSE 路径(research_service.pyErrorReportGenerator,该路径直接消费策略输出而不经过 HTTP 边界)。

净化函数的实现见 src/local_deep_research/security/log_sanitizer.py:

def sanitize_error_for_client(message: str, max_length: int = 200) -> str:

其行为约束在发布说明中写得很明确:凭据被清洗、长度截断到上限(默认 200 字符),同时保留"Error: "前缀,让客户端仍能识别错误载荷的语义。另外/api/v1/generate_report也加上了同样的边界净化,发布说明注明这是"纯预防"——其当前载荷(content/metadata)并不携带错误字段。

从源码结构看,这种"边界 + 生产层"双净化是必要的:SSE 路径消费的是策略输出而非 HTTP 响应,只净化 HTTP 层会漏掉 UI 侧的推送通道。

5. Sofya 搜索引擎:一次请求覆盖两阶段检索

1.9.1 唯一的新功能是为可选的托管搜索引擎加入 Sofya 支持。其设计契合点是 LDR 的两阶段检索模型:先取预览(previews)做相关性过滤,再对命中的结果取全文(full content)。Sofya 的POST /v1/search在单次调用中同时返回排序结果、SERP 摘要(description)和抽取的页面正文(content,markdown 格式),因此不需要第二个抓取往返。

实现位于 src/local_deep_research/web_search_engines/engines/search_engine_sofya.py。关键常量与参数如下:

项目值/取值说明
API 基础地址https://sofya.co/v1BASE_URL常量
结果数上限max_results默认 10,硬上限 20Sofya 每次请求最多 20 条
域名 include/exclude各最多 10 个超出部分被截断
请求超时30 秒REQUEST_TIMEOUT
topic"general"/"news"非法值回落到"general"
freshness"day""week""month""year""YYYY-MM-DD:YYYY-MM-DD"None表示不过滤
search_depthbasic(含正文,3 credits)/snippets(仅摘要,1 credit)按是否消费正文自动选择

其中"按需付费"是源码里一个值得注意的细节。_build_payload只在正文确实会被消费时才使用basic深度:

# src/local_deep_research/web_search_engines/engines/search_engine_sofya.py(节选) wants_content = (self.include_full_content and not self.search_snippets_only) payload = { "query": query, "search_depth": "basic" if wants_content else "snippets", "max_results": min(self.MAX_RESULTS_CAP, self.max_results), "topic": self.topic, }

search_snippets_onlyNone时从include_full_content推导,显式值(例如由工厂从search.snippets_only设置转发)优先。_get_previews把每条结果的完整响应暂存在_full_result内部字段中;_get_full_content再把其中抽取的content提升为full_content(报告引用处理器在回退到snippet之前读取该字段),无正文时回退到摘要,保证full_content始终可用。

引擎在工厂中通过search_tool == "sofya"分支接入,见 src/local_deep_research/web_search_engines/search_engine_factory.py。

配置方式

按发布说明,API key 有三种提供途径(_resolve_api_key的解析顺序:参数 → 设置快照 → 环境变量):

  1. 设置界面:Settings → Search → Sofya,对应设置键search.engine.web.sofya.api_key
  2. 环境变量LDR_SEARCH_ENGINE_WEB_SOFYA_API_KEY
  3. 引擎构造参数直接传入。

此外,GitHub 账号注册 Sofya 每月赠送 1000 免费 credits,意味着按snippets深度(1 credit/次)或basic深度(3 credits/次)计费时,免费额度足以支撑相当规模的检索。

6. 其他 Bug 修复:从会话回滚到引用标签

发布说明的 Bug Fixes 一节还有六个修复,按影响模块归类:

新闻订阅历史端点。/news/api/subscriptions/<id>/history对任何发生过研究运行的订阅都会抛内部错误:get_subscription_historycreated_at/completed_at调用了.isoformat(),而这两个字段存储的本身就是 isoformat 字符串。修复为直接返回原值(#3094)。

SearXNG 标题空白折叠。HTML 解析器用BeautifulSoup.Tag.get_text(strip=True)读取结果标题,该调用会剥离首尾空白并把内部所有空白串折叠为空,导致 "Word One Word Two Word Three" 在## Sources块中渲染为 "WordOneWordTwoWordThree"。修复改为get_text(" ", strip=True)(BeautifulSoup 官方文档中"去首尾、内部空白归一为单空格"的惯用写法),摘要做了同样处理(#4970)。修复的收益不止于显示层:标题保真会传导到 LLM 相关性过滤与BaseCandidateExplorer/ProgressiveExplorer的候选短语提取,这两处此前从 SearXNG 来源的集合中只能看到单 token 标题。

注册/登录的部分会话回滚。认证后的初始化流程中途失败时,用户会处于"静默已登录"状态。现在部分会话会被回滚,报告的失败与实际登录状态一致——你并没有登录(#5006)。

资料库/RAG 来源的引用标签渲染为空[]DOMAIN_HYPERLINKSDOMAIN_ID_HYPERLINKSDOMAIN_ID_ALWAYS_HYPERLINKS三种模式此前把urlparse(url).netloc直接用作引用标签。相对 URL(最典型的是形如/library/document/<uuid>的 RAG/资料库命中)的netloc为空字符串,导致单条相对 URL 引用坍缩成[[]](url)、多条时泄漏出[[-1]](url)[[-2]](url)后缀。现在格式器回退到文档标题的 slug 化版本(小写、仅字母数字),最终回退到引用编号本身,标签永远非空且链接携带文档名;真实 web URL(arxiv.org、github.com 等)行为不变——netloc非空时域名仍优先。

formatBytes在 1 TB 以上显示 "N undefined"。共享的字节格式化器只定义到 GB,库中一个 1 TB 的 blob 总量会显示为1 undefined。现在单位表覆盖到 TB~EB,并在两端钳制单位索引,超大值不再越界、亚 1 字节值也不再产生负索引。

Research Logs 面板的 500 条 DOM 上限修剪策略。面板达到上限时原先直接丢弃最旧条目,导致大量常规 info 条目冲掉上限时,旧的 warning/error 也被丢掉。现在修剪按优先级遍历超出上限的槽位:info 最先,依次是 milestones、warnings、errors,保证长时间研究运行时面板保留的是最有诊断价值的条目。

7. 其他变更:content_fetcher 惰性导入与 pre-commit 钩子补强

Other Changes 一节的变更集中在导入结构与工程基建,但它们共同支撑了安全日志改造的落地:

移除citation_formatter.py的磁盘加载 workaround。此前content_fetcher在包__init__中急切导入,导致citation_formatter引入url_classifier时必须绕开importlib.util磁盘加载(#4880 引入)。现在content_fetcher.ContentFetcher改为首次属性访问时通过 PEP 562 模块级__getattr__惰性解析,url_classifier(以及任何经content_fetcher包的导入)不再拖入requestsplaywrightbs4lxml等传递依赖,citation_formatter得以使用普通的from ..content_fetcher.url_classifier import URLClassifier, URLType。对外 API 不变(#4992,同时修正了 #5023 测试文件中子进程测试的 project-root 路径 off-by-one 和隐藏 stderr 的check=True调用)。

带来源标签的引用格式化不再重复查询 URL 分类器。预先计算好的标签缓存改为惰性查询,每个来源的标签只解析一次。

安全导入回退测试的顺序依赖修复。相关测试曾用新副本替换local_deep_research.security*模块,会重建Sensitivity等枚举,破坏同一 worker 上后运行测试的身份(identity)检查。

Sofya egress-labels 测试改用.value比较。在 Docker 测试环境中,包同时可从/app/src(PYTHONPATH)与已安装 wheel 解析,SofyaSearchEngine的类属性与测试侧的Sensitivity可能解析为两个成员相同但不同的类对象。Python 的Enum.__eq__跨类返回NotImplemented==is一样失败),只有.value(或.name)能在这种双类场景下存活。

check-pathlib-usage钩子补上别名盲区。此前 AST 匹配器只标记字面名osimport os as <alias>后的<alias>.path.*会静默绕过检查——#4880 中的_os.path.join(...)就是这样漏到人工审查才被抓住的。

8. 版本小结:安全修复的三条主线

把 1.9.1 的安全项放在一起看,可以看到三条互相印证的防线:

  1. 输入侧:用户字符串进入LIKE查询前必须转义通配符(资料库、域名分类、新闻订阅、设置 key 查找均覆盖);
  2. 日志侧:敏感模块的异常堆栈默认不进日志,diagnose 模式(LDR_APP_DEBUG+LDR_LOGURU_DIAGNOSE)显式开启,pre-commit 钩子阻止绕过;
  3. 输出侧:异常派生字符串在 HTTP 边界与策略层两处经sanitize_error_for_client净化,CWE-209 向量关闭,SSE 通道独立覆盖。

功能侧的 Sofya 引擎则是"零额外抓取往返"的两阶段检索范例:预览、正文同响应返回,深度与 credits 按实际消费自动切换。对自托管 LDR 的用户,该版本的实际收益是可验证的:设置批量接口不再可能带出明文密钥,日志默认不再回显 provider 内部状态,而检索侧多了一个按 credits 计费的托管选项。

如需核对本文引用的实现细节,可依次查看 release notes、secure_logging.py、log_sanitizer.py 与 search_engine_sofya.py。

【免费下载链接】local-deep-research~95% on SimpleQA (e.g. Qwen3.6-27B on a 3090). Supports all local and cloud LLMs (llama.cpp, Ollama, Google, ...). 10+ search engines - arXiv, PubMed, your private documents. Everything Local & Encrypted.项目地址: https://gitcode.com/GitHub_Trending/lo/local-deep-research

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 15:03:10

MAA 下载安装完整指南:3 分钟选对版本,装到能跑

MAA 下载安装完整指南&#xff1a;3 分钟选对版本&#xff0c;装到能跑 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手&#xff0c;全日常一键长草&#xff01;| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地址: https:…

作者头像 李华
网站建设 2026/9/16 15:01:44

基于Java Web的博客系统课程设计:从数据库设计到部署实战

简介&#xff1a;这是一份基于Java Web的博客系统课程设计完整资源包&#xff0c;面向计算机相关专业学生、Java初学者及需要完成课程设计的开发者&#xff0c;解决选题难、缺少可运行示例与配套说明的痛点。项目采用MVC开发模式&#xff0c;基于Eclipse/MyEclipse集成开发环境…

作者头像 李华
网站建设 2026/9/16 15:00:19

Harness内存治理:Lua热更脚本的分代池与引用计数实践

1. 这不是“换框架”&#xff0c;而是给游戏脚本做一次精准的内存外科手术你有没有遇到过这样的情况&#xff1a;一个原本跑得挺顺的Lua热更脚本&#xff0c;在接入新功能模块后&#xff0c;内存占用曲线突然像坐上了火箭——GC频率翻倍、帧率偶尔掉点、甚至在低端机上出现偶发…

作者头像 李华
网站建设 2026/9/16 15:00:11

Pentagi:基于Docker+Neo4j+AI Agents的安全知识操作系统

1. 项目概述&#xff1a;Pentagi 是什么&#xff1f;它解决的不是“渗透测试自动化”&#xff0c;而是安全研究范式的迁移 Pentagi 这个名字乍看像一个拼写变体&#xff0c;但结合热搜词 pentagi、penetration testing、ai agents、docker、neo4j &#xff0c;它绝非某个小众…

作者头像 李华