Skip to content

fix(security): SSRF guard + ripgrep timeout (proposal)#5

Open
Closed-Book wants to merge 1 commit into
GYF0311:mainfrom
Closed-Book:fix/ssrf-timeout
Open

fix(security): SSRF guard + ripgrep timeout (proposal)#5
Closed-Book wants to merge 1 commit into
GYF0311:mainfrom
Closed-Book:fix/ssrf-timeout

Conversation

@Closed-Book

Copy link
Copy Markdown
Contributor

⚠️ Proposal — author decision needed

本 PR 引入行为变更:fetch 默认拒绝私网 URL(RFC 1918 / 6890 范围)。在 lorekit 用户场景(个人知识库抓公网文章)下不影响常规用法,但如果你有意让 fetch 抓自建 localhost wiki,需要明确决策:

Option A:合并,按 default deny + env opt-out 走(本 PR 的设计)
Option B:拒绝合并,文档化让用户自己注意
Option C:合并但 default allow + 加 `--block-private` flag opt-in

本 PR 实现的是 Option A(更安全的默认),但所有 default-deny 都有 env 逃生口 `LOREKIT_FETCH_ALLOW_PRIVATE=1`。

Summary

改动 文件 类型
SSRF 防御 `src/lib/fetcher/http.ts:96` 行为变更(M1)
L1 error propagation `src/lib/fetcher/index.ts:68` 配套(PrivateAddressError 不走 L2 fallback)
ripgrep timeout `src/commands/search.ts:30` 纯加 timeout(M3,无副作用)
smoke `tests/smoke/fetch-ssrf.test.mjs` 新增

SSRF 防御细节

  • `redirect: 'manual'` + 手动 max=5 跳
  • 每跳前 `dns.lookup()` 拿目标 IP,用 `net.BlockList` 检查私网
  • 拒绝范围:`127/8 / 10/8 / 172.16/12 / 192.168/16 / 169.254/16 / 0.0.0.0/8 / ::1 / fc00::/7 / fe80::/10`
  • env opt-out:`LOREKIT_FETCH_ALLOW_PRIVATE=1`
  • 拒绝时抛 `PrivateAddressError`,`fetcher/index.ts` L1 catch 识别后直接冒泡为 `reason: 'PRIVATE_ADDRESS_BLOCKED'` 的 FetchResult,不退 L2 playwright(避免 playwright 绕过 guard)
  • 不影响 ollama localhost 调用(在 `lib/ollama.ts`,另一个 import 路径)

ripgrep timeout

纯加 `timeout: 30_000`,规避恶意 query / 退化 regex 在巨型 corpus 上 hang 风险。SIGTERM 时落到内置 fallback + warn。零业务副作用。

Verification

```bash
npm run verify # 全绿(53 旧测试 + 2 新 ssrf 测试 = 55 pass / 1 skip / 0 fail)
```

实测输出:
```
ℹ tests 55
ℹ pass 54
ℹ fail 0
ℹ skipped 1
```

`tsc --noEmit` 也是 clean;`npm run lint` 在 http.ts / fetcher/index.ts / 新 smoke 上零问题(search.ts 的 unused-import warn 是预先存在的,与本 PR 无关)。

Context

来自一次对 lorekit v0.4.0 的代码审查 + GPT-5.4 对抗 review。M3 ripgrep timeout 是纯收益、建议直接接受;M1 SSRF 是 nice-to-have、看作者偏好。

🤖 Generated with Claude Code

新增 fetch SSRF 防御(M1)+ ripgrep timeout(M3)。属
nice-to-have 安全纵深,请作者判断是否合并:

fetcher/http.ts:
- 改 redirect: 'manual' 手动跟随,max=5 跳
- 每跳前 dns.lookup() 检查目标 IP 是否在 RFC 1918 / RFC 6890
  私网范围(127/8 / 10/8 / 172.16/12 / 192.168/16 / 169.254/16
  / ::1 / fc00::/7),拒则抛 PrivateAddressError
- env opt-out LOREKIT_FETCH_ALLOW_PRIVATE=1 跳过私网检查
- 不影响 ollama localhost(在 lib/ollama.ts 另一路径)

fetcher/index.ts:
- L1 catch 时识别 PrivateAddressError,直接冒泡为 FetchResult
  error (reason=PRIVATE_ADDRESS_BLOCKED),不退 L2 fallback
  (否则 playwright 也会绕过 guard 命中私网)

search.ts:
- spawnSync 加 timeout: 30_000(之前仅 maxBuffer 10M)
- SIGTERM 触发时落 fallback + warn

新增 tests/smoke/fetch-ssrf.test.mjs 验证私网拒绝 + opt-out。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant