Skip to content

Require secure transport for Zhihu upstream - #517

Open
GOLDKUN wants to merge 4 commits into
chaitin:mainfrom
GOLDKUN:security/require-secure-upstream-transport
Open

Require secure transport for Zhihu upstream#517
GOLDKUN wants to merge 4 commits into
chaitin:mainfrom
GOLDKUN:security/require-secure-upstream-transport

Conversation

@GOLDKUN

@GOLDKUN GOLDKUN commented Sep 2, 2026

Copy link
Copy Markdown

问题

知乎 Open API 服务允许使用明文 http:// 上游地址,并支持通过配置关闭 TLS 证书校验。

影响

如果低权限配置可以修改这些选项,服务会通过明文传输或不可信 TLS 连接发送 accessSecret 和 OAuth 凭证,可能造成凭证窃取和中间人攻击。

修复内容

  • 生产/远程上游的 baseUrl 强制使用 https://
  • 仅为 L2 本地 mock 保留 http://127.0.0.1http://[::1] 例外;其他明文 HTTP 地址仍被拒绝。
  • skipTlsVerifytlsInsecureSkipVerifyinsecureSkipVerify 只能为 false
  • 运行时显式拒绝尝试关闭 TLS 校验的配置。
  • 更新配置 schema、冒烟测试兼容性和错误提示。

验证

  • node --check src/zhihu-open-api.js 通过。
  • node --check test/zhihu-open-api.test.js 通过。
  • cd services && npm run validate 通过。
  • CI 的 service-l2-gate 冒烟测试使用 loopback HTTP mock,不再因 schema 拒绝而失败。

@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Title: Require secure transport for Zhihu upstream

Commit: dcca7ea

本次变更对知乎 Open API 服务做安全加固:强制 HTTPS 并禁止关闭 TLS 证书校验。

主要改动:

  1. config.schema.json:baseUrl 增加 pattern: "^https://"skipTlsVerifytlsInsecureSkipVerifyinsecureSkipVerify 三个布尔开关由 type: boolean 改为 const: false,从配置模式层面拒绝任何非 false 值。
  2. src/zhihu-open-api.js:normalizeBaseUrl 只接受 https: 协议;resolveSettings 新增运行时守卫,任一 TLS 跳过标志为 true 时抛出 INVALID_ARGUMENTdispatcher 恒为 undefined,彻底移除 insecureTlsDispatcher 的使用。
  3. 测试相应更新:http 用例改为 https,并新增对 skipTlsVerify: true 触发抛错的断言。

整体评估:安全目标(任何路径都无法关闭 TLS 校验)已达成,因为 dispatcher 已被硬编码为 undefined,且 schema 会拒绝非 false 值。存在两点低危问题:(a) 运行时守卫用 firstDefined(...) === true 判断,若靠前的标志为 false、靠后的 legacy 别名却为 true(如 skipTlsVerify:false + tlsInsecureSkipVerify:true),守卫会静默放行而不会报错,与 schema 的 const:false 及守卫意图不一致(连接仍是安全的,属 fail-secure);(b) insecureTlsDispatcher 及其 createTlsDispatcher 导入已成为死代码,却仍被创建并导出,与本次加固目标相悖,易误导后续维护者。

Comment thread services/zhihu__open-api/src/zhihu-open-api.js
Comment thread services/zhihu__open-api/src/zhihu-open-api.js
@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Title: Require secure transport for Zhihu upstream

Commit: 814b475

本次改动为知乎 Open API SDK 的 baseUrl 校验放宽传输层限制:在 https 之外,允许 http 仅用于回环地址 127.0.0.1 与 [::1](本地开发/联调场景),并同步更新 config.schema.json 的 pattern 与测试报错文案。整体方向合理:运行时守卫仍 fail-closed,非回环 http 一律拒绝;由于任意 https 原本就允许,该放宽不会新增攻击者对远程明文目标的利用面。

主要问题有两处:(1) config.schema.json 的 baseUrl pattern 采用 ^ 前缀匹配并要求回环主机后紧跟 : 或 /,与运行时 normalizeBaseUrl 的判定不一致——http://127.0.0.1(无端口/尾斜杠)会被 schema 拒绝但被运行时接受,而 http://127.0.0.1:8080@evil.com 这类 userinfo 变体会被 schema 放行但被运行时拒绝;(2) 安全边界放宽缺少对应的正向与边界测试,尤其 IPv6 分支依赖 URL hostname 序列化含方括号的实现细节,存在回归风险。

Comment thread services/zhihu__open-api/config.schema.json Outdated
Comment thread services/zhihu__open-api/test/zhihu-open-api.test.js Outdated
@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Title: Require secure transport for Zhihu upstream

Commit: 480faf2

本变更为 zhihu__open-api 服务收紧出站传输安全:1) config.schema.json 的 baseUrl pattern 由「https 或 loopback http」收紧为仅 ^https://;2) zhihu-open-api.js 移除 createTlsDispatcher/insecureTlsDispatcher 导入与实例,normalizeBaseUrl 拒绝所有非 https 协议,dispatcher 恒为 undefined;3) TLS 守卫由 firstDefined(...)===true 改为数组 .some(v=>v===true),修复了「前位显式 false 后位 legacy true 被绕过」的已确认缺陷;4) 测试更新错误文案并新增 legacy 别名被拒绝的断言;5) scripts/service-package-smoke.mjs 将 mock 上游从明文 http 改为自签 https,并为 daemon 进程设置 NODE_TLS_REJECT_UNAUTHORIZED=0 以放行测试连接。

评估:两条历史 finding(insecureTlsDispatcher 死代码、firstDefined 顺序绕过)均已被本次修订解决,未发现新的可利用安全问题或功能回归;schema 与运行时对「仅 https」的约束一致,smoke 脚本随之上线自签 TLS 以保持可运行。唯一发现是低严重度测试缺口:新增测试仅覆盖单标志为 true 的场景,而这些用例在旧的 firstDefined 缺陷实现下同样会通过,真正被 .some() 修复的「前位 false + 后位 true」组合缺少回归断言,建议补充以锁定修复行为。

@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Title: Require secure transport for Zhihu upstream

Commit: 3a4fb51

本次变更仅修改 scripts/service-package-smoke.mjs(+2/-1),是一次针对冒烟测试的 TLS 安全加固:将启动 octobus daemon 时的 NODE_TLS_REJECT_UNAUTHORIZED=0(全局禁用证书校验)替换为 NODE_EXTRA_CA_CERTS=mock.caFile(仅将本地 mock upstream 的自签名证书加入信任锚),并在 startMockUpstream() 返回对象中新增 caFile: certPath 字段。

核对结论:

  1. mock upstream 证书为 CN=127.0.0.1、SAN IP:127.0.0.1 的自签名证书;caFile 指向 cert.pem 绝对路径。daemon 经 mock.baseURL(https://127.0.0.1:)访问上游,主机名与 SAN 匹配,Node 默认安全上下文会加载 NODE_EXTRA_CA_CERTS,TLS 校验可通过。
  2. 全文件仅 daemon 进程需要出站 TLS 到 mock;cli 子命令与 grpc/mcp 调用均走 http:// 本地管理面,不涉及 TLS,替换范围与旧逻辑一致。
  3. 生命周期无竞态:cert.pem 在 daemon spawn 前已生成,mock.close()(删除临时目录)位于 finally 中 daemon 退出之后。
  4. 未发现该变更引入的正确性、安全或可维护性问题;该改动缩小信任面,属正向改进,无需提交 finding。

() => _test.resolveSettings({ config: { baseUrl: 'https://example', [alias]: true }, secret: { accessSecret: 'a' } }),
/TLS certificate verification cannot be disabled/,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「前置 false + 后置 legacy true」组合缺少回归测试,新增用例在旧逻辑下同样通过

本次将 TLS 守卫从 firstDefined(...) === true 改为数组的 .some(v => v === true),正是为了修复历史确认的问题:当靠前标志显式为 false 而靠后的 legacy 别名为 true 时(例如 config: { skipTlsVerify: false, tlsInsecureSkipVerify: true }),firstDefined 会直接返回 false,守卫被绕过,本应抛出的「TLS certificate verification cannot be disabled」被静默跳过。但新增的测试仅覆盖「单个标志为 true」的场景(skipTlsVerify: true、tlsInsecureSkipVerify: true、insecureSkipVerify: true 各自单独出现)。在旧的 firstDefined 实现下,这些场景中靠前的标志都是 undefined(未定义),firstDefined 会继续向后取到 true 并照常抛出,因此这些新用例在修复前后行为完全相同,无法区分 bug 与修复。真正需要保护的回归场景(前位 false、后位 true 的组合)没有任何断言覆盖,将来若有人改回 firstDefined 或等价逻辑,测试套件依然会全部通过。

Problem code:

Changed code at services/zhihu__open-api/test/zhihu-open-api.test.js:523-528

Recommendation:
补充一个覆盖「前位 false + 后位 legacy true」组合的断言,锁定本次 .some() 修复的行为,例如:assert.throws(() => _test.resolveSettings({ config: { baseUrl: 'https://example', skipTlsVerify: false, tlsInsecureSkipVerify: true }, secret: { accessSecret: 'a' } }), /TLS certificate verification cannot be disabled/); 同理可对 bindings 层级与 insecureSkipVerify 各补一组。

Suggested diff:

diff --git a/services/zhihu__open-api/test/zhihu-open-api.test.js b/services/zhihu__open-api/test/zhihu-open-api.test.js
--- a/services/zhihu__open-api/test/zhihu-open-api.test.js
+++ b/services/zhihu__open-api/test/zhihu-open-api.test.js
@@ -520,6 +520,14 @@ test('respects config timeouts, custom headers, and legacy aliases', async () =>
   for (const alias of ['tlsInsecureSkipVerify', 'insecureSkipVerify']) {
     assert.throws(
       () => _test.resolveSettings({ config: { baseUrl: 'https://example', [alias]: true }, secret: { accessSecret: 'a' } }),
       /TLS certificate verification cannot be disabled/,
     );
   }
+  // 回归用例:前位显式 false + 后位 legacy true 在旧的 firstDefined 实现下会被绕过。
+  assert.throws(
+    () => _test.resolveSettings({ config: { baseUrl: 'https://example', skipTlsVerify: false, tlsInsecureSkipVerify: true }, secret: { accessSecret: 'a' } }),
+    /TLS certificate verification cannot be disabled/,
+  );

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