Local Deep Research(LDR)Semgrep 自定义安全规则完全指南:从 12 类漏洞检测到 SSRF 防护的源码级剖析
【免费下载链接】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
本指南以仓库 .semgrep/rules/README.md 为核心,系统讲解 Local Deep Research(LDR)项目为自身代码库定制的 Semgrep 安全规则集:覆盖硬编码密钥、SQL/命令/代码注入、路径穿越、反序列化、SSRF、XSS、CSRF 等 12+ 类漏洞,并结合 .semgrep/rules/ldr-security.yaml、.semgrep/rules/check-safe-requests.yaml 两个规则文件的真实内容,以及 src/local_deep_research/security/safe_requests.py 的安全请求封装实现与 .github/workflows/semgrep.yml 的 CI/CD 集成,深入讲解每条规则的检测原理、适用场景、本地运行方法与扩展方式。读完本文,你将掌握如何在本仓库内运行、验证、扩展这套 LDR 专属安全规则,并理解规则如何与项目源码中的 SSRF 防护层相互印证。
LDR(Local Deep Research)是一个支持本地与云端 LLM、聚合 arXiv/PubMed 等 10+ 搜索引擎的深度研究工具。由于它天然需要发起大量外部 HTTP 请求、处理用户提供的文档与搜索词、并可能暴露 Web 端点,其安全面覆盖了经典的注入类漏洞与 LLM 应用特有的 SSRF(服务端请求伪造)风险。.semgrep/rules/目录下的自定义规则,正是为这道安全面量身定制的"门禁"。
一、为什么 LDR 需要一套自定义 Semgrep 规则
Semgrep 是静态分析工具,通过 YAML 规则文件定义代码模式,无需执行代码即可扫描语法树。官方规则库(如p/security-audit)覆盖面广,但存在两个问题:一是规则过于通用,无法体现 LDR 的架构约定(例如必须走safe_requests封装而非直接调用requests);二是官方规则缺少对项目特有 API(如 SQLAlchemy 的session.execute、Flask 的render_template_string)的精准约束。
LDR 的解决方案是双轨并行,这一点可从 .github/workflows/semgrep.yml 看出:
- 通用规则:运行
p/security-audit与p/secrets两个官方规则包; - 项目自定义规则:运行
--config=.semgrep/rules/指向本目录。
自定义规则与通用规则互补:官方包负责"广撒网",自定义规则负责"精准打击"——把 LDR 开发者约定的安全编码规范(禁用os.system、禁用yaml.load、禁用直接requests.get、必须参数化 SQL 等)固化成可自动执行的规则。
二、规则全景:ldr-security.yaml 的 12+ 类漏洞检测
.semgrep/rules/ldr-security.yaml 是核心规则文件。README 中列出了 12 类规则,而实际文件内实现了16 条规则(README 之外还包含 SQL 字符串格式化执行、SSL 校验关闭、危险文件权限 3 类额外检测,以及独立的 SSRF URL 抓取提醒)。下表按 README 分类整理,并标注实际 severity 与 CWE 映射:
| 规则 ID | 检测目标 | Severity | CWE | OWASP 2021 |
|---|---|---|---|---|
hardcoded-secret-detection | 硬编码 API Key/密码/Token | ERROR | CWE-798 | A07 身份认证失效 |
sql-string-concatenation | SQL 字符串拼接注入 | ERROR | CWE-89 | A03 注入 |
sql-execute-with-string-format | session.execute(f"...")等格式化执行 | ERROR | CWE-89 | A03 注入 |
dangerous-eval-usage | eval/exec/compile动态执行 | ERROR | CWE-95 | A03 注入 |
os-system-command-injection | os.system与shell=True | ERROR | CWE-78 | A03 注入 |
path-traversal-risk | 用户输入拼入文件路径 | WARNING | CWE-22 | A01 失效访问控制 |
unsafe-yaml-load | yaml.load反序列化 | ERROR | CWE-502 | A08 软件与数据完整性失效 |
unsafe-pickle-load | pickle.load(s)/cPickle.load | ERROR | CWE-502 | A08 软件与数据完整性失效 |
weak-random-generation | random模块用于安全场景 | WARNING | CWE-338 | A02 加密失败 |
flask-debug-mode-enabled | Flaskdebug=True | ERROR | CWE-489 | A05 安全配置错误 |
insecure-ssl-verification-disabled | verify=False关闭证书校验 | ERROR | CWE-295 | A02 加密失败 |
dangerous-file-permissions | chmod 0o777/0o666 | WARNING | CWE-732 | A01 失效访问控制 |
ldr-url-fetching-ssrf | requests.get/post、urlopen抓取 URL | WARNING | CWE-918 | A10 SSRF |
ldr-user-input-in-html | 用户输入进入 HTML 上下文 | WARNING | CWE-79 | A03 注入 |
ldr-missing-csrf-protection | POST 端点缺少 CSRF 提醒 | INFO | CWE-352 | A01 失效访问控制 |
ldr-password-in-logs | 密码写入日志/print | ERROR | CWE-532 | A09 安全日志与监控失败 |
三、逐类规则深度解析:模式、原理与修复建议
3.1 硬编码密钥检测(CWE-798,ERROR)
规则hardcoded-secret-detection使用pattern-either匹配四种赋值形态:
- pattern: $VAR = "sk-..." - pattern: $VAR = 'sk-...' - pattern: password = "..." - pattern: api_key = "..." - pattern: secret = "..." - pattern: token = "..."其中sk-...模式专门针对 OpenAI 风格的 API Key 前缀(LDR 支持 OpenAI 兼容的本地/云端模型,这类密钥形态在项目中高度相关)。触发后规则要求改用环境变量或安全配置存储凭据。这与项目中 .github/workflows/gitleaks.yml 等密钥泄露防线形成互补:Semgrep 在代码库内查"写死的密钥",Gitleaks 在 git 历史中查"已泄露的密钥"。
3.2 SQL 注入防护(CWE-89,ERROR)
LDR 的存储层基于 SQLAlchemy,因此规则聚焦 SQLAlchemy 的注入面。sql-string-concatenation匹配三类字符串拼 SQL 的写法:
- pattern: | $QUERY = "..." + $USER_INPUT + "..." - pattern: | $QUERY = f"...{$USER_INPUT}..." - pattern: | $QUERY = "..." % ($USER_INPUT)sql-execute-with-string-format更进一步,直接拦截执行层的危险调用:
- pattern: $SESSION.execute(f"...") - pattern: $SESSION.execute("..." + ...) - pattern: $ENGINE.execute(f"...")规则消息明确要求使用 SQLAlchemy 的参数化写法text()with parameters。两条规则双保险:无论拼接发生在查询构造阶段还是执行阶段都会被捕获。
3.3 代码注入与命令注入(CWE-95 / CWE-78,ERROR)
dangerous-eval-usage匹配eval($X)、exec($X)、compile($X, ...)三个动态执行入口,防止任意代码执行。os-system-command-injection则覆盖 Python 中最常见的命令注入面:
- pattern: os.system($CMD) - pattern: subprocess.call($CMD, shell=True) - pattern: subprocess.Popen($CMD, shell=True) - pattern: subprocess.run($CMD, shell=True)修复方向是改用subprocess的参数列表形式(subprocess.run(["ls", "-l"], ...)),避免 shell 解释用户输入。对于 LDR 这类需要调用本地工具链(如向量化、PDF 解析)的项目,这类规则能有效阻止"命令拼接"反模式混入代码库。
3.4 路径穿越(CWE-22,WARNING)
path-traversal-risk匹配四类将用户输入拼入文件路径的写法:
- pattern: open($PATH + $USER_INPUT, ...) - pattern: open(f"{$PATH}/{$USER_INPUT}", ...) - pattern: Path($USER_INPUT) - pattern: os.path.join($PATH, $USER_INPUT)注意此类规则设为 WARNING 而非 ERROR,因为部分场景(如用户明确指定输出路径)属于合理使用,仅需提示开发者校验与净化。这与仓库中 src/local_deep_research/security/path_validator.py、src/local_deep_research/security/filename_sanitizer.py 等运行时校验模块形成呼应:静态规则负责"提醒",运行时模块负责"拦截"。
3.5 不安全反序列化(CWE-502,ERROR)
两条规则分别针对 YAML 与 pickle:
- id: unsafe-yaml-load pattern: yaml.load($X, ...) - id: unsafe-pickle-load pattern-either: - pattern: pickle.load($X) - pattern: pickle.loads($X) - pattern: cPickle.load($X)yaml.load在旧版本 PyYAML 中可执行任意对象构造,pickle系列更是公认的任意代码执行入口。规则强制改用yaml.safe_load(),pickle 则建议替换为 JSON 等安全格式。
3.6 弱随机数(CWE-338,WARNING)
weak-random-generation匹配random.random()、random.randint(...)、random.choice(...),提示安全敏感操作(Token 生成、会话 ID 等)必须使用secrets模块。LDR 作为涉及用户会话与 API 凭据的应用,这一约束能防止开发者顺手用random生成可预测的凭据。
3.7 生产环境调试模式(CWE-489,ERROR)
- pattern: app.run(debug=True) - pattern: app.config["DEBUG"] = TrueFlask 调试模式会暴露 Werkzeug 调试器(可远程执行代码)与敏感堆栈信息,属于信息泄露风险,规则以 ERROR 级别硬性禁止。
3.8 SSRF 提醒(CWE-918,WARNING)
ldr-url-fetching-ssrf匹配requests.get($URL)、requests.post($URL)、urllib.request.urlopen($URL),提醒开发者校验 URL 防止对内网资源的 SSRF 攻击。这条 WARNING 规则是 LDR 安全体系中最有特色的部分——项目不仅有静态提醒,还有配套的强制规则与运行时实现,详见第四节。
3.9 XSS 与 CSRF(CWE-79 / CWE-352)
ldr-user-input-in-html匹配两类把用户输入注入 HTML 上下文的写法:
- pattern: | render_template_string($TEMPLATE, user_input=$INPUT) - pattern: | f"<html>...{$USER_INPUT}...</html>"要求使用 Jinja2 自动转义。ldr-missing-csrf-protection则以 INFO 级别提醒 POST 端点(模式为@app.route($PATH, methods=["POST"])的函数体)必须启用 CSRF 保护——INFO 级别说明它属于"开发提醒"而非"阻断错误",避免误报干扰正常开发流程。
3.10 凭据日志泄露(CWE-532,ERROR)
- pattern: logger.info(..., password=...) - pattern: logger.debug(..., password=...) - pattern: logger.error(..., password=...) - pattern: print(..., password=...)禁止将密码写入任何日志级别或标准输出。LDR 项目在运行时侧还提供了 src/local_deep_research/security/log_sanitizer.py 与 src/local_deep_research/security/secure_logging.py 作为纵深防御——即使开发者在日志调用中意外传入敏感参数,运行时也会在落盘前净化。
四、SSRF 防护的完整闭环:强制规则 + 运行时实现
4.1 check-safe-requests.yaml:把"必须走封装"变成 ERROR
.semgrep/rules/check-safe-requests.yaml 是第二份规则文件,包含 6 条规则,全部针对SSRF 防护的"架构强制"——禁止绕过安全封装直接调用requests库:
| 规则 ID | 匹配模式 | 要求 |
|---|---|---|
unsafe-requests-get | requests.get(...) | 改用safe_get() |
unsafe-requests-post | requests.post(...) | 改用safe_post() |
unsafe-requests-session | requests.Session() | 改用SafeSession() |
unsafe-requests-put | requests.put(...) | 改用SafeSession() |
unsafe-requests-delete | requests.delete(...) | 改用SafeSession() |
unsafe-requests-patch | requests.patch(...) | 改用SafeSession() |
这 6 条规则的 severity 全部为 ERROR,且通过paths.exclude精准放行例外区域:
paths: exclude: - "**/security/safe_requests.py" # 封装自身 - "**/tests/**" # 测试用例 - "**/*_test.py" - "**/test_*.py" - "**/examples/**" # 示例代码规则消息直接给出修复指引:from ...security import safe_get,访问 localhost 服务时用safe_get(url, allow_localhost=True)。这意味着 LDR 代码库中任何新增的、未经过 SSRF 校验的 HTTP 调用都会在 CI 阶段被 ERROR 拦截——把安全架构约定从"文档建议"升级为"硬性门禁"。
4.2 运行时实现:safe_requests.py 的六重防护
被规则"强制使用"的封装位于 src/local_deep_research/security/safe_requests.py,提供safe_get(L202)、safe_post(L353)、SafeSession(L512)、safe_get_with_retries(L699)四个入口。其防护机制可拆解为六层:
- URL 预校验:每次请求前调用
ssrf_validator.validate_url(),拒绝命中内网/元数据地址的 URL(校验逻辑在 src/local_deep_research/security/ssrf_validator.py)。allow_localhost与allow_private_ips参数用于放行可信的自托管服务(如 SearXNG、Ollama),但云端元数据端点(AWS/Azure/OCI 等)始终被封锁。 - DNS 固定(DNS Pinning):通过
dns_pinning.pinned_request()将"解析并校验过的 IP"固定为实际连接地址(src/local_deep_research/security/dns_pinning.py),堵住"解析-连接"间隙中的 DNS 重绑定攻击(TOCTOU)。 - 逐跳重定向校验:
safe_get/safe_post关闭requests原生重定向,手动跟随每个 301/302/303/307/308 跳转,对每个目标 URL 重新执行 SSRF 校验,最多跟随_MAX_REDIRECTS = 10次(L31-L34)。 - 凭据隔离:
_headers_for_redirect()与SafeSession.rebuild_auth()在跳转离开凭据作用域时剥离authorization、cookie以及*key/*token/*secret后缀的请求头,防止 API Key 泄露给重定向目标(L168-L199、L567-L585)。 - 响应大小护栏:
MAX_RESPONSE_SIZE = 1GB(L28),_check_response_size()校验Content-Length,缺失或可疑时安装有界读取器(body guard)防止内存耗尽(L95-L148)。 - 超时与重试:默认
DEFAULT_TIMEOUT = 30秒(L23);safe_get_with_retries对连接错误、超时、429/5xx 状态码执行指数退避重试(_RETRY_BACKOFF_SECONDS = (1, 2, 4)),并解析Retry-After头(上限 300 秒,L662-L696)。
上述行为在 tests/security/test_check_response_size.py 等测试中得到验证(例如SafeSession.send()的中间重定向分支测试),形成"规则强制使用封装 → 封装实现防护 → 测试验证防护"的完整闭环。
五、本地运行与 CI/CD 集成
5.1 本地命令行使用
README 给出的两条核心命令,第一条仅运行 LDR 自定义规则,第二条叠加官方审计规则包:
# 仅运行 LDR 自定义规则扫描 src/ 目录 semgrep --config=.semgrep/rules/ src/ # 官方安全审计规则 + LDR 自定义规则 semgrep --config=p/security-audit --config=.semgrep/rules/ src/可结合 severity 过滤(如--severity=WARNING)或输出 JSON(--json)用于脚本消费。注意:p/security-audit与p/secrets需要网络访问 Semgrep Registry 下载规则包。
5.2 CI/CD 工作流:semgrep.yml 的完整流程
规则由 .github/workflows/semgrep.yml 驱动的 Semgrep 扫描工作流自动执行,该工作流可被 release-gate.yml 等发布门禁调用(workflow_call)。关键设计:
- 扫描分两阶段:第一阶段运行
p/security-audit+p/secrets(--severity=INFO全量收集),第二阶段运行.semgrep/rules/自定义规则,各产出独立 JSON; - 崩溃检测:扫描命令以
|| true兜底,随后检查 JSON 文件是否生成——若 Semgrep 崩溃未产出文件,则输出::error::并失败退出,避免"静默跳过扫描"; - 结果合并与 SARIF 转换:Python 脚本合并两个 JSON 为
semgrep-combined-results.json,再转换为 SARIF 2.1.0 格式; - 上报 GitHub Security 标签页:通过
github/codeql-action/upload-sarif上传,category: semgrep-security。工作流注释特别强调:绝不能伪造空的 SARIF 文件,否则会把所有已修复的历史告警误标为"已修复"(L177-L182); - 门槛汇总:在 GitHub Actions Summary 中统计 ERROR/WARNING/INFO 数量,ERROR 或 WARNING 大于 0 时标注"需要处理"。
环境中还需注意 Python 3.12 的兼容细节:工作流固定setuptools<82(82.0 移除了pkg_resources),并固定semgrep==1.87.0(L35-L40)。
六、扩展规则:新增规则三步走与模板
README 给出了向.semgrep/rules/添加新规则的流程,结合本仓库实践可细化为:
- 创建规则文件:在
.semgrep/rules/下新建 YAML 文件(如my-rule.yaml),参考 .semgrep/rules/ldr-security.yaml 的既有写法(pattern-either、metadata、paths.exclude等); - 本地验证:
semgrep --config=.semgrep/rules/your-rule.yaml src/单独测试该规则,确认命中率与误报率;必要时用--validate校验规则语法; - 文档同步:在 .semgrep/rules/README.md 的规则清单中登记新规则(ID、检测目标、Severity、CWE)。
项目规定的规则模板如下(含 metadata 规范):
rules: - id: your-rule-id pattern: | # Your pattern here message: Description of the security issue languages: [python] severity: ERROR # or WARNING, INFO metadata: category: security cwe: "CWE-XXX: Description" owasp: "AXX:2021 - Category"实践要点:metadata中的cwe与owasp字段会被 CI 工作流的 SARIF 转换读取(.github/workflows/semgrep.yml),直接影响 Security 标签页的告警信息完整性;多模式规则优先使用pattern-either(参见 ldr-security.yaml 中hardcoded-secret-detection的写法);需要排除测试或封装自身时,用paths.exclude精确放行(参见 check-safe-requests.yaml)。
七、参考与延伸
本仓库相关文件索引:
- 规则文档:.semgrep/rules/README.md
- 通用安全规则集:.semgrep/rules/ldr-security.yaml
- SSRF 强制规则集:.semgrep/rules/check-safe-requests.yaml
- SSRF 运行时封装:src/local_deep_research/security/safe_requests.py
- URL 校验器:src/local_deep_research/security/ssrf_validator.py
- DNS 固定实现:src/local_deep_research/security/dns_pinning.py
- 运行时日志净化:src/local_deep_research/security/log_sanitizer.py
- CI/CD 集成:.github/workflows/semgrep.yml
- 安全封装测试:tests/security/test_check_response_size.py
规则所映射的行业标准(OWASP Top 10 2021、CWE Top 25、Semgrep 官方 Registry 规则包)均可在公开标准文档中查阅;本仓库通过metadata.cwe与metadata.owasp字段在每条规则上建立了到这两套标准的追踪链,这也是静态分析结果可审计、可整改、可追溯的关键。
【免费下载链接】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),仅供参考