Skip to content

fix(weixin): handle uppercase CDN upload schemes#5439

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/weixin-cdn-uppercase-scheme
Jun 20, 2026
Merged

fix(weixin): handle uppercase CDN upload schemes#5439
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/weixin-cdn-uppercase-scheme

Conversation

@tt-a1i

@tt-a1i tt-a1i commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Makes Weixin CDN upload URL handling compare HTTP(S) schemes case-insensitively. Full CDN upload URLs with HTTPS:// or mixed-case HtTpS:// now stay on the direct upload URL path, while uppercase HTTP:// is rejected like lowercase http://.

Why it is needed

URL schemes are case-insensitive, but uploadToCdn() used case-sensitive startsWith() checks. That meant uppercase HTTPS upload URLs could be treated as raw upload params and embedded into encrypted_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.ts
  • npx vitest run --config vitest.config.ts from packages/channels/weixin
  • npx eslint packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.ts
  • npx prettier --check packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.ts
  • npm run build --workspace=packages/channels/weixin
  • git diff --check

Evidence (Before & After)

N/A

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Local npm workspace on macOS.

Risk & Scope

  • Main risk or tradeoff: Low. The change only normalizes the scheme comparison before the existing branch, and the direct URL path still enforces the exact Weixin CDN hostname.
  • Not validated / out of scope: Real Weixin CDN upload with a live channel session; Windows and Linux local runs.
  • Breaking changes / migration notes: None.

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.ts
  • packages/channels/weixin 下运行 npx vitest run --config vitest.config.ts
  • npx eslint packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.ts
  • npx prettier --check packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.ts
  • npm run build --workspace=packages/channels/weixin
  • git diff --check

Before & After 证据

N/A

Tested on

macOS 已本地验证;Windows 和 Linux 未本地验证,交给 CI 覆盖。

Environment

macOS 本地 npm workspace。

Risk & Scope

  • 主要风险或取舍:低。改动只在现有分支判断前规范化 scheme 比较;直接 URL 分支仍然保留精确 Weixin CDN hostname 校验。
  • 未验证 / 不在范围内:没有用真实 Weixin channel session 做 CDN 上传;Windows 和 Linux 未本地跑。
  • Breaking changes / migration notes:无。

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.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No review findings. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

✅ Local real-build verification — LGTM

I verified this PR at its head (e551368a8, base 61dcf865de) in an isolated worktree on three layers: a real-source A/B of uploadToCdn (driving the actual api.ts via tsx with a stubbed fetch, observing the exact URL it sends to), a real vitest mutation test, and a host-guard edge-case check. The pre-fix/fixed split is produced by reverting only api.ts to base (tsx transpiles on each run, so the source-revert is the mutation; mutant confirmed by lowerUrlOrParam count 3 → 0 → 3). Environment: macOS, Node v22.22.2.

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 uploadToCdn actually sends to fetch

Stubbed fetch to capture the request URL, then classified it: DIRECT = the CDN URL used as-is; WRAPPED = the input nested inside a new …?encrypted_query_param=<input> URL (the malformed request from #5438); THROWS = rejected before any fetch.

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 comparisonnew 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.

@wenshao
wenshao marked this pull request as ready for review June 20, 2026 08:11
@wenshao

wenshao commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

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 startsWith(). Clear bug. Well within scope for a channel integration fix.

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 urlOrParam, so no case corruption there. The hostname check via new URL() already normalizes the host to lowercase, so that path is also safe.

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 AbCdEf produces the correct constructed URL would guard against future regressions in the lowercasing logic.

Moving on to code review and testing. 🔍

中文说明

感谢贡献!

模板完整 ✓ — 所有必填部分齐全,中英双语正文完整。

方向:这是一个明确的正确性修复 — URL scheme 按 RFC 3986 大小写不敏感,而代码用了大小写敏感的 startsWith()。明确的 bug,完全在 channel 集成修复的范围内。

方案:diff 很精简 — 生产代码只改 2 行,加上了回归测试。将整个输入小写化做 scheme 比较的方法简单有效。else 分支(原始参数路径)正确保留了原始 urlOrParam,不会出现大小写被篡改的问题。通过 new URL() 做 hostname 检查时,URL 构造函数已经将 host 归一化为小写,所以这条路径也安全。

一个小观察:测试覆盖了完整 URL 输入(大写 HTTPS、混合大小写 HTTPS、大写 HTTP),但没有覆盖混合大小写的原始参数路径。不算 blocker,因为那条路径本身没有改动,但加一行测试断言类似 AbCdEf 的原始参数能生成正确的构造 URL,可以防止以后在 lowercase 逻辑上出现回归。

进入代码审查和测试 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent 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:

  • lowerUrlOrParam is used only for the two startsWith() scheme checks — nothing else touches it
  • urlOrParam (original) is preserved for url = urlOrParam and encodeURIComponent(urlOrParam) in the else branch
  • new URL() normalizes hostname to lowercase, so parsed.hostname !== CDN_HOST is safe regardless of input casing
  • No security issues — hostname validation still works correctly for mixed-case URLs

No blockers, no AGENTS.md violations. Clean, minimal change.

Testing

Non-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)

 RUN  v3.2.4

 ✓  @qwen-code/channel-weixin  src/api.test.ts (3 tests) 9ms

 Test Files  1 passed (1)
      Tests  3 passed (3)
   Duration  364ms

Build

> @qwen-code/[email protected] build
> tsc --build

(exit 0)

Lint

npx eslint packages/channels/weixin/src/api.ts packages/channels/weixin/src/api.test.ts
(no output, exit 0)

All checks green. ✅

中文说明

代码审查

在读 diff 之前的独立方案:将输入小写化仅用于 scheme 比较,保留原始输入用于 URL 构造和参数编码。PR 的方案与此完全一致。

实现正确:

  • lowerUrlOrParam 只用于两个 startsWith() scheme 检查 — 其他地方不碰它
  • urlOrParam(原始值)在 url = urlOrParam 和 else 分支的 encodeURIComponent(urlOrParam) 中保留不变
  • new URL() 会把 hostname 归一化为小写,所以 parsed.hostname !== CDN_HOST 在任何大小写输入下都安全
  • 无安全问题 — hostname 校验在混合大小写 URL 下仍然正确

无 blocker,无 AGENTS.md 违规。干净、最小化的改动。

测试

非 TUI 改动(内部 CDN 上传逻辑),tmux 产品测试不适用。从 PR diff 应用到 main 后运行了单元测试、构建和 lint:

  • 单元测试 3/3 通过 ✅
  • tsc 构建通过 ✅
  • ESLint 无报错 ✅

全部通过 ✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

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 qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@wenshao
wenshao merged commit 5924ab3 into QwenLM:main Jun 20, 2026
38 checks passed
wenshao added a commit that referenced this pull request Jun 20, 2026
…#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]>
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.

Weixin CDN upload URL parsing is case-sensitive for HTTP(S) schemes

3 participants