fix(weixin): handle uppercase CDN upload schemes#5439
Conversation
wenshao
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— qwen3.7-max via Qwen Code /review
✅ Local real-build verification — LGTMI verified this PR at its head ( Verdict: the fix is correct, the new tests genuinely guard it, there is no regression, and it makes the CDN host check apply uniformly to uppercase URLs. Safe to merge. 1. Real-source A/B — what
|
Input (urlOrParam) |
Pre-fix | Fixed |
|---|---|---|
HTTPS://novac2c.cdn.weixin.qq.com/c2c/upload?… |
WRAPPED — full URL nested as a param ❌ | DIRECT ✅ |
HtTpS://novac2c.cdn.weixin.qq.com/c2c/upload?… |
WRAPPED ❌ | DIRECT ✅ |
HTTP://novac2c.cdn.weixin.qq.com/c2c/upload?… |
WRAPPED — uploaded, not rejected ❌ | THROWS CDN upload URL must use HTTPS ✅ |
https://… (control) |
DIRECT | DIRECT |
http://… (control) |
THROWS | THROWS |
raw param rawparam123 (control) |
WRAPPED | WRAPPED |
The three controls are identical on both sides → the change is scoped to uppercase/mixed-case schemes only, no regression. Pre-fix, an HTTPS:// CDN URL gets encodeURIComponent-nested into a brand-new encrypted_query_param=HTTPS%3A%2F%2F… URL — exactly the "malformed CDN request" the PR describes — and HTTP:// is silently uploaded instead of rejected.
2. Real vitest mutation test
FIXED (PR head): src/api.test.ts → 3 passed (3) ; full weixin suite → 49 passed (49)
PRE-FIX (api.ts reverted, PR test kept): 3 failed (3)
All 3 new tests fail on pre-fix → they are non-vacuous and guard the fix. Notably the rejects uppercase HTTP test fails by timing out at ~5001ms — pre-fix doesn't reject HTTP://, it enters the real upload + retryWithBackoff path, which matches the WRAPPED routing above.
3. Host guard still enforced for uppercase URLs (no SSRF hole)
The PR claims "the direct URL path still enforces the exact Weixin CDN hostname." Confirmed on the fixed build:
| Input | Fixed |
|---|---|
HTTPS://evil.com/c2c/upload?… |
THROWS CDN upload URL has unexpected host: evil.com (no fetch) |
HTTPS://NOVAC2C.CDN.WEIXIN.QQ.COM/… (uppercase host) |
accepted, fetched directly (URL lowercases the host) |
This is actually a small security-positive: pre-fix, HTTPS://evil.com bypassed the host check entirely (it fell to the WRAPPED branch); post-fix it routes through the same new URL(...).hostname !== CDN_HOST gate as lowercase. The fix only lowercases for the scheme comparison — new URL() and the upload still use the original-case string, which is correct (URL/fetch normalize the scheme, and the host compare is case-insensitive via URL).
4. Minor, non-blocking note
The new tests cover HTTPS:// / HtTpS:// / HTTP://, but not an uppercase URL pointing at a wrong host (the host-guard-on-uppercase path). I verified that path works here; a dedicated regression row would be a nice follow-up, not a blocker.
🇨🇳 中文版(完整对应)
✅ 本地真实构建验证 —— 可以合并
我在隔离 worktree 中以 PR 最新提交(e551368a8,base 61dcf865de)从三个层面做了验证:uploadToCdn 的真实源码 A/B(用 tsx 直接跑真实 api.ts,stub 掉 fetch,观察它最终把请求发到哪个 URL)、真实 vitest 变异测试、以及host 校验边界用例检查。pre-fix/fixed 的对比是通过「只把 api.ts 回退到 base」得到的(tsx 每次运行都即时转译,所以源码回退本身就是变异;变异已由 lowerUrlOrParam 计数 3 → 0 → 3 确认)。环境:macOS,Node v22.22.2。
结论:修复正确,新增测试确实能守住该修复,无回归,并且让 CDN host 校验对大写 URL 也统一生效。可以合并。
1. 真实源码 A/B —— uploadToCdn 实际发给 fetch 的是什么
stub 掉 fetch 捕获请求 URL,再分类:DIRECT = 直接用该 CDN URL;WRAPPED = 把输入嵌进一个新的 …?encrypted_query_param=<输入> URL(即 #5438 的畸形请求);THROWS = 在任何 fetch 之前就被拒绝。
输入(urlOrParam) |
Pre-fix | Fixed |
|---|---|---|
HTTPS://novac2c.cdn.weixin.qq.com/c2c/upload?… |
WRAPPED —— 整个 URL 被当 param 嵌套 ❌ | DIRECT ✅ |
HtTpS://novac2c.cdn.weixin.qq.com/c2c/upload?… |
WRAPPED ❌ | DIRECT ✅ |
HTTP://novac2c.cdn.weixin.qq.com/c2c/upload?… |
WRAPPED —— 被上传而非拒绝 ❌ | THROWS CDN upload URL must use HTTPS ✅ |
https://…(对照) |
DIRECT | DIRECT |
http://…(对照) |
THROWS | THROWS |
原始 param rawparam123(对照) |
WRAPPED | WRAPPED |
三个对照用例在两侧完全一致 → 改动只影响大写/混合大小写 scheme,无回归。pre-fix 下,HTTPS:// 的 CDN URL 会被 encodeURIComponent 嵌进一个全新的 encrypted_query_param=HTTPS%3A%2F%2F… URL —— 正是 PR 描述的「畸形 CDN 请求」;而 HTTP:// 会被静默上传而不是拒绝。
2. 真实 vitest 变异测试
FIXED (PR head): src/api.test.ts → 3 passed (3) ;weixin 全量 → 49 passed (49)
PRE-FIX (回退 api.ts、保留 PR 测试): 3 failed (3)
3 个新测试在 pre-fix 上全部失败 → 非空过,确实守住修复。值得注意的是 rejects uppercase HTTP 这条是超时 ~5001ms 失败的 —— pre-fix 不拒绝 HTTP://,而是进入了真实上传 + retryWithBackoff 重试路径,这与上面的 WRAPPED 路由一致。
3. host 校验对大写 URL 仍然生效(没有 SSRF 漏洞)
PR 声称「直接 URL 分支仍然保留精确 Weixin CDN hostname 校验」。在 fixed 构建上已确认:
| 输入 | Fixed |
|---|---|
HTTPS://evil.com/c2c/upload?… |
THROWS CDN upload URL has unexpected host: evil.com(无 fetch) |
HTTPS://NOVAC2C.CDN.WEIXIN.QQ.COM/…(大写 host) |
接受,直接 fetch(URL 会把 host 小写化) |
这其实是个小小的安全正向收益:pre-fix 下 HTTPS://evil.com 完全绕过了 host 校验(它落到 WRAPPED 分支);post-fix 则和小写一样走 new URL(...).hostname !== CDN_HOST 这道闸。修复只在比较 scheme 时做小写化 —— new URL() 和上传仍用原始大小写字符串,这是正确的(URL/fetch 会规范化 scheme,host 比较经 URL 也是大小写不敏感的)。
4. 非阻塞的小提示
新增测试覆盖了 HTTPS:// / HtTpS:// / HTTP://,但没覆盖「大写 URL 指向错误 host」这条(大写下的 host 校验路径)。我这边已验证该路径正常;补一条专门的回归用例是个不错的后续,但不是阻塞项。
Verified locally by a maintainer: real api.ts driven via tsx + real vitest, in an isolated worktree (pre-fix via source revert). Not an automated CI run.
|
@qwen-code /triage |
|
Thanks for the PR! Template looks good ✓ — all required sections present, bilingual body included. On direction: this is a straightforward correctness fix — URL schemes are case-insensitive per RFC 3986, and the code was doing a case-sensitive On approach: the diff is tight — 2 lines changed in production code, plus regression tests. The approach of lowercasing the entire input for scheme comparison is simple and effective. The else branch (raw param path) correctly continues to use the original One minor observation: the tests cover full URL inputs (uppercase HTTPS, mixed-case HTTPS, uppercase HTTP) but don't cover the raw-param path with mixed-case input. Not a blocker since that path is unchanged, but a one-liner test asserting that a raw param like Moving on to code review and testing. 🔍 中文说明感谢贡献! 模板完整 ✓ — 所有必填部分齐全,中英双语正文完整。 方向:这是一个明确的正确性修复 — URL scheme 按 RFC 3986 大小写不敏感,而代码用了大小写敏感的 方案:diff 很精简 — 生产代码只改 2 行,加上了回归测试。将整个输入小写化做 scheme 比较的方法简单有效。else 分支(原始参数路径)正确保留了原始 一个小观察:测试覆盖了完整 URL 输入(大写 HTTPS、混合大小写 HTTPS、大写 HTTP),但没有覆盖混合大小写的原始参数路径。不算 blocker,因为那条路径本身没有改动,但加一行测试断言类似 进入代码审查和测试 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal before reading the diff: lowercase the input for scheme comparison only, keep original for URL construction and param encoding. The PR's approach matches exactly. The implementation is correct:
No blockers, no AGENTS.md violations. Clean, minimal change. TestingNon-TUI change (internal CDN upload logic), so tmux product testing doesn't apply. Ran the unit tests, build, and lint from the PR diff applied to main: Unit tests (3/3 pass)BuildLintAll checks green. ✅ 中文说明代码审查在读 diff 之前的独立方案:将输入小写化仅用于 scheme 比较,保留原始输入用于 URL 构造和参数编码。PR 的方案与此完全一致。 实现正确:
无 blocker,无 AGENTS.md 违规。干净、最小化的改动。 测试非 TUI 改动(内部 CDN 上传逻辑),tmux 产品测试不适用。从 PR diff 应用到 main 后运行了单元测试、构建和 lint:
全部通过 ✅ — Qwen Code · qwen3.7-max |
|
Clean fix, clean diff, clean tests. The change is 2 production lines that solve a real correctness bug (RFC 3986 says schemes are case-insensitive). The implementation matches the simplest possible approach — lowercase for comparison, preserve original everywhere else. Tests cover the regression cases. Build and lint green. Approving. ✅ 中文说明干净的修复、干净的 diff、干净的测试。改动只有 2 行生产代码,修复了一个真实的正确性 bug(RFC 3986 规定 scheme 大小写不敏感)。实现方式就是最简单的方案 — 小写化做比较,其他地方保留原始值。测试覆盖了回归场景。构建和 lint 全部通过。 批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
…#5461) * fix(extension): accept uppercase URL schemes in Claude plugin sources resolvePluginSource compared a string plugin source against 'http://' and 'https://' case-sensitively, so a marketplace.json source such as 'HTTPS://github.com/owner/repo' fell through to local-path handling and failed with "Plugin source not found". Lowercase the source before the scheme check, matching #5426 / #5429 / #5439. * test: return a GitHubDownloadResult from the download mock The mockImplementation returned void, which tsc --build rejected (TS2345) even though vitest passed. Return a GitHubDownloadResult so the build is clean. --------- Co-authored-by: Shaojin Wen <[email protected]>
What this PR does
Makes Weixin CDN upload URL handling compare HTTP(S) schemes case-insensitively. Full CDN upload URLs with
HTTPS://or mixed-caseHtTpS://now stay on the direct upload URL path, while uppercaseHTTP://is rejected like lowercasehttp://.Why it is needed
URL schemes are case-insensitive, but
uploadToCdn()used case-sensitivestartsWith()checks. That meant uppercase HTTPS upload URLs could be treated as raw upload params and embedded intoencrypted_query_param, producing malformed CDN requests.Reviewer Test Plan
How to verify
Run the Weixin channel tests and build. The new regression tests cover uppercase and mixed-case HTTPS CDN URLs, plus uppercase HTTP rejection.
Commands run locally:
npx vitest run packages/channels/weixin/src/api.test.tsnpx vitest run --config vitest.config.tsfrompackages/channels/weixinnpx eslint packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.tsnpx prettier --check packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.tsnpm run build --workspace=packages/channels/weixingit diff --checkEvidence (Before & After)
N/A
Tested on
Environment (optional)
Local npm workspace on macOS.
Risk & Scope
Linked Issues
Fixes #5438
AI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.
中文说明
这个 PR 做了什么
让 Weixin CDN 上传 URL 的 HTTP(S) scheme 判断变成大小写不敏感。
HTTPS://或混合大小写HtTpS://的完整 CDN 上传 URL 现在会继续走直接上传 URL 分支;大写HTTP://会像小写http://一样被拒绝。为什么需要
URL scheme 本身大小写不敏感,但
uploadToCdn()之前用了大小写敏感的startsWith()。这会让大写 HTTPS 上传 URL 被当成原始 upload param,并嵌入encrypted_query_param,最终生成错误的 CDN 请求。Reviewer Test Plan
如何验证
运行 Weixin channel 测试和 build。新增回归测试覆盖大写 HTTPS、混合大小写 HTTPS,以及大写 HTTP 拒绝。
本地运行过的命令:
npx vitest run packages/channels/weixin/src/api.test.tspackages/channels/weixin下运行npx vitest run --config vitest.config.tsnpx eslint packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.tsnpx prettier --check packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.tsnpm run build --workspace=packages/channels/weixingit diff --checkBefore & After 证据
N/A
Tested on
macOS 已本地验证;Windows 和 Linux 未本地验证,交给 CI 覆盖。
Environment
macOS 本地 npm workspace。
Risk & Scope
Linked Issues
Fixes #5438
AI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.