Skip to content

fix(security): address vulnerabilities from security audit#1242

Merged
JinyuXiang-Mimo merged 11 commits into
XiaomiMiMo:mainfrom
JinyuXiang-Mimo:security/fix-file-read-vuln-v3
Jun 23, 2026
Merged

fix(security): address vulnerabilities from security audit#1242
JinyuXiang-Mimo merged 11 commits into
XiaomiMiMo:mainfrom
JinyuXiang-Mimo:security/fix-file-read-vuln-v3

Conversation

@JinyuXiang-Mimo

Copy link
Copy Markdown
Collaborator

Summary

修复安全审计发现的 25 个漏洞,覆盖 SSRF、XSS、认证绕过、信息泄露、速率限制等类别。

Changes

P0 — Critical

  • SSRF 防护 (packages/opencode/src/util/ssrf.ts NEW): 新增 SSRF 检查模块,阻止私有 IP (10.x, 172.16-31.x, 192.168.x)、云元数据端点 (169.254.169.254, metadata.google.internal)、IPv4-mapped IPv6 绕过;集成到 webfetch 和 MCP remote connect
  • 认证加固 (server.ts): 非 loopback 绑定时强制要求 MIMOCODE_SERVER_PASSWORD
  • Share 端点鉴权 (api.ts): share_create 需要 Bearer token;getData/fetch 在 secret 不存在时返回空/404

P1 — High

  • XSS 防护 (content-markdown.tsx): DOMPurify 清洗 markdown HTML 输出
  • 信息泄露 (middleware.ts): 错误响应移除 stack trace,仅返回 message
  • auth_token 移除 (middleware.ts): 删除 query param 认证方式
  • 日志脱敏 (webhook.ts, api.ts): stripe/feishu webhook 不再记录完整请求体

P2 — Medium

  • XSS 防护 (oauth-callback.ts, codex.ts): HTML 模板中 error 参数使用 escapeHtml
  • 速率限制 (rate-limit.ts NEW, session.ts): prompt_async 和 shell 路由添加滑动窗口限流 (20 req/min)
  • 分页限制 (session.ts): messages 查询添加 .max(1000) 校验和默认 limit: 100
  • ErrorBoundary ([shareID].tsx): 移除 stack trace 显示
  • Webhook 签名 (api.ts): Feishu webhook 验证 X-Lark-Signature (SHA-256)

Files Changed (13 files)

M  packages/console/app/src/routes/stripe/webhook.ts
M  packages/enterprise/src/routes/share/[shareID].tsx
M  packages/function/src/api.ts
M  packages/opencode/src/mcp/index.ts
M  packages/opencode/src/mcp/oauth-callback.ts
M  packages/opencode/src/plugin/codex.ts
M  packages/opencode/src/server/middleware.ts
M  packages/opencode/src/server/routes/instance/session.ts
M  packages/opencode/src/server/server.ts
M  packages/opencode/src/tool/webfetch.ts
M  packages/web/src/components/share/content-markdown.tsx
A  packages/opencode/src/server/rate-limit.ts
A  packages/opencode/src/util/ssrf.ts

Test Plan

  • TypeScript 编译通过 (tsc --noEmit 无新增错误)
  • SSRF 模块单元验证:私有 IP、云元数据、IPv4-mapped IPv6 均被拦截;loopback 放行
  • Rate limiter 功能验证:超过阈值后返回 429
  • server/ 测试套件 47/47 通过
  • webfetch 测试 3/3 通过
  • MCP OAuth 测试通过
  • 无新增测试回归(所有失败均为环境/沙箱预存问题)

- SSRF: block private IPs and metadata endpoints in webfetch/MCP (DNS rebinding protection)
- Auth: enforce password on non-loopback bind, remove auth_token query param
- XSS: sanitize markdown HTML (DOMPurify), escape OAuth error templates
- Share: require bearer token for share_create, guard getData/fetch on secret existence
- Rate limiting: add sliding-window limiter to prompt_async and shell routes
- Info disclosure: strip stack traces from error responses and ErrorBoundary
- Logging: remove sensitive data from stripe/feishu webhook logs
- Webhook: verify Feishu X-Lark-Signature (SHA-256)
- Pagination: cap session messages at 1000, default limit 100
URL parsers normalize ::ffff:127.0.0.1 to ::ffff:7f00:1 (hex form),
which bypassed the dotted-decimal regex. Now parses both forms.
opencode is a user-facing CLI tool — users legitimately fetch from their
own local dev servers. Block private networks and cloud metadata only.
- SSRF: DNS resolution failure now rejects (fail-closed)
- Feishu: require signature for all non-challenge requests
- Rate limiter: add periodic sweep to prevent memory leak
- Tests: add unit tests for SSRF module and rate limiter
@qiaozongming

Copy link
Copy Markdown
Collaborator

Re-review (19a9508)

上一轮 4 个 finding 已全部修对 👍 (#3 DNS fail-closed / #7 Feishu 签名 / #6 sweep / #15 tests)。本轮新发现两个不在已有清单里的问题。


🔴 必须看

1. SSRF 被 HTTP 重定向绕过 —— assertSafeUrl 只校验初始 URL

packages/opencode/src/tool/webfetch.ts:37 (校验) vs :77 (实际请求)

  • assertSafeUrl(params.url) 只检查用户传入的 URL,紧接着 httpOk.execute(request) 用的是 FetchHttpClient (tool/registry.ts:408),底层 fetch 默认 redirect: "follow"
  • 触发:让 webfetch 请求一个公网 URL,服务端返回 302 Location: http://169.254.169.254/latest/meta-data/...(或 10.x、阿里云 100.100.100.200)。fetch 自动跟随,重定向后的地址完全不过 assertSafeUrl,直接打到内网/云元数据。
  • 影响:比 文档中分享页面或存在问题 #4 接受的 DNS rebinding(需控 NS + 时间窗)容易得多 —— 无需任何 DNS 控制,一个返回 302 的公网页面即可绕过。等于本次 SSRF 防护在 webfetch 主路径上基本失效。
  • 修法:webfetch client 设 redirect: "manual",逐跳读 Location 并对每一跳重新 assertSafeUrl;或跟随前对最终 URL 再校验。mcp/index.ts:288/751 同理。

2. 默认 message 接口静默截断到最新 100 条,且无分页游标

packages/opencode/src/server/routes/instance/session.ts:747

  • 改动:session.messages({ sessionID, agentID }){ ..., limit: 100 }
  • Session.messagesif (input.limit)MessageV2.page(...).items (session.ts:687),只取 .items,丢掉了 more / cursor (message-v2.ts:914-923)。
  • 触发:任何客户端 GET /session/:id/message 不带 limit(limit === undefined 分支,line 740),对消息 >100 的会话:以前返回全部历史(stream().reverse()),现在只返回最新 100 条,响应是裸数组 c.json(messages),客户端无从知道被截断、也拿不到游标翻页
  • 影响:长会话静默丢历史。TUI 走的是显式 limit:100, agent_id:"*" + cursor 那条路(sync.tsx:779),不受影响;受影响的是走默认无 limit 的 API 消费者。
  • 修法:默认分支保留分页元信息(page 对象 + cursor)而非裸数组;或默认值设大些并明确语义为“最新 N 条”。

🟡 建议

3. Feishu 签名缺时间戳新鲜度校验 → 重放 packages/function/src/api.ts (feishu handler)
签名算 timestamp + nonce + encryptKey + rawBody,但未校验 timestamp 是否在合理窗口(如 ±5min)内。抓到的合法请求可被无限重放 —— 飞书协议里 timestamp 正是为防重放设的。

4. JSON.parse(rawBody) 未保护 同文件
改成 JSON.parse(await c.req.text()) 后,畸形 body 抛未捕获异常 → 500。非回归,但既然在改这块顺手包一下更稳。


对“不改”清单的意见

基本认同。补充:#4 (DNS rebinding) 接受可以,但 assertSafeUrllookup()(单地址)校验、fetch 会独立再解析一次,多 A 记录域名连重放窗口都不需要就可能 check/connect 解析到不同 IP。这点和 🔴#1 叠加,说明 SSRF 层在 webfetch 上偏弱 —— 至少把 🔴#1 的 redirect 补上,DNS rebinding 才值得单独接受。#8/#12/#13/#14 同意,无技术 blocker。

结论:🔴#1 (redirect SSRF) 和 🔴#2 (默认接口静默截断) 建议合并前处理,其余为建议项。

Co-authored-by: MiMo-Code noreply@mimo.xiaomi.com

…arse

- webfetch: use safeFetch with redirect:"manual" + per-hop SSRF check
  to prevent 302-to-internal-IP bypass
- session messages: raise default limit from 100 to 1000 (matches max
  validation) to avoid silent data loss for API consumers
- feishu: reject timestamps older than ±5 minutes (replay window)
- feishu: wrap JSON.parse in try/catch, return 400 on malformed body
- add safeFetch redirect tests to ssrf.test.ts
…esolution

The Effect type signature of WebFetchTool is part of the tool registry's
Layer requirements. Removing it caused runtime Layer resolution failures
in the full test suite.
…SRF check

Replacing Effect's HttpClient with native fetch broke the full test suite
due to Effect Layer/fiber dependencies across the tool registry. Revert
to the original HttpClient.execute() pipeline and add a post-response
check: if the response was redirected, validate the final URL against
the SSRF blocklist before returning content to the AI.
Bun.serve socket allocation may cause resource contention in the full
CI test suite (332 files). Replace with globalThis.fetch mocks that
test the same redirect-checking logic without binding ports.
…pollution

bun test runs all files in the same process; mocking globalThis.fetch
leaked to other test files and broke 19 tool.edit tests in CI.
@JinyuXiang-Mimo JinyuXiang-Mimo merged commit 4ef5dfb into XiaomiMiMo:main Jun 23, 2026
2 of 3 checks passed
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.

2 participants