fix(core): reject userinfo URLs in WebFetch validation#5783
Conversation
e3a9011 to
de69257
Compare
Add malformed URL cases that pass the initial scheme check but fail URL parsing, covering the validation fallback path. Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
de69257 to
10e9ae6
Compare
|
@qwen-code /review |
| _Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/28075935218)._ |
doudouOUC
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
The credential check logic is sound — WHATWG URL parser correctly detects all userinfo forms (user:pass@, user@, :pass@, encoded variants). Existing reviewer suggestions (duplicate test cases, catch-block error message) are valid polish items but not blockers.
— qwen3.7-max via Qwen Code /review
✅ Local build verification (macOS) — merge referenceVerified PR head Env: macOS 26.5.1 (arm64) · Node v22.22.2 · npm 10.9.7 Results
Mutation test — the new tests are not vacuousRemoving the new Adversarial robustness (WHATWG
|
| Input | Verdict |
|---|---|
https://user@@example.com |
REJECT (username → user%40) |
https://user:pass@@example.com |
REJECT |
https://user%40real@example.com |
REJECT |
HTTPS://User:Pass@example.com |
REJECT (uppercase scheme) |
https://⟨tab⟩user@…, https://user⟨newline⟩@… |
REJECT (control-char injection) |
https://@example.com |
accept — no actual credentials |
https://example.com\@evil.com |
accept — host is example.com, @evil.com is the path (not an SSRF bypass) |
https://foo.com?x=user:pass@bar |
accept — user:pass@ is in the query string, not userinfo |
No credential leak found. The two accepts are correct (they carry no credentials).
Why both the regex and new URL() are needed
new URL() alone would accept http:/example.com, http:example.com, and ftp://example.com (it normalizes/accepts them). The ^https?:// regex is what rejects those; new URL() adds userinfo detection + malformed-authority rejection. Keeping both (with the new explanatory comment) is correct defense-in-depth.
Review threads — all resolved in the current head
- ✅ Distinct
catchmessage"The 'url' is malformed and could not be parsed."(no longer duplicates the scheme message) - ✅ Explanatory comment added above the regex
- ✅ Malformed cases merged into the existing scheme
it.each(no duplicatedftp:///http:…cases) - ✅
catchbranch now has test coverage (https://,http://[::1)
Minor (non-blocking)
The PR description's Evidence block is from a superseded commit (d464833, "22 passed"); the current head 10e9ae6df has 24 tests. Worth refreshing the description before merge — code is correct.
Verdict
LGTM — safe to merge. Surgical, well-tested, no regressions in sibling credentialed-URL subsystems (proxy / git clone / npm registry / redaction paths are separate and unaffected by this validation).
中文版(点击展开)
✅ 本地构建验证(macOS)— 合并参考
在基于 origin/main(85fef8527)的独立干净 worktree 中、经全新 npm ci 后验证了 PR head 10e9ae6df。本次验证补齐了 macOS 覆盖空缺(PR 表格中 macOS 标为 not tested),并独立印证了 @doudouOUC 的 approve。
环境: macOS 26.5.1 (arm64) · Node v22.22.2 · npm 10.9.7
结果
| 检查项 | 命令 | 结果 |
|---|---|---|
| 单元测试 | npx vitest run src/tools/web-fetch.test.ts |
✅ 24 通过 |
| 类型检查 | npm run typecheck(tsc --noEmit) |
✅ exit 0 |
| Lint | eslint web-fetch.ts web-fetch.test.ts |
✅ exit 0 |
| 构建 | npm run build |
✅ exit 0(校验已编译进 dist) |
合并到 main |
git merge-tree origin/main |
✅ 干净,无冲突 |
变异测试 — 新增测试并非空过
从源码中移除新增的 try/catch 块(保留测试),恰好 6 个测试失败且仅此 6 个 —— 2 个畸形 URL 用例(https://、http://[::1,命中 catch 分支)+ 4 个凭据用例(user:secret@、user@、:secret@、%75ser@)。还原后 24/24 全绿。这证明新增测试确实覆盖了新校验逻辑。
对抗性健壮性(Node 22 上的 WHATWG new URL(),独立于测试套件)
| 输入 | 判定 |
|---|---|
https://user@@example.com |
拒绝(username → user%40) |
https://user:pass@@example.com |
拒绝 |
https://user%40real@example.com |
拒绝 |
HTTPS://User:Pass@example.com |
拒绝(大写 scheme) |
https://⟨tab⟩user@…、https://user⟨换行⟩@… |
拒绝(控制字符注入) |
https://@example.com |
放行 —— 没有真实凭据 |
https://example.com\@evil.com |
放行 —— host 是 example.com,@evil.com 落在 path(非 SSRF 绕过) |
https://foo.com?x=user:pass@bar |
放行 —— user:pass@ 在 query 里,非 userinfo |
未发现凭据泄漏。两个放行用例是正确的(不含任何凭据)。
为什么 regex 和 new URL() 都需要保留
仅靠 new URL() 会放行 http:/example.com、http:example.com、ftp://example.com(它会归一化/接受它们)。^https?:// 正则负责拒绝这些;new URL() 负责 userinfo 检测 + 畸形 authority 拒绝。两者并存(加上新增注释)是正确的纵深防御。
Review 线程 — 当前 head 已全部解决
- ✅
catch改为独立报错"The 'url' is malformed and could not be parsed."(不再与 scheme 报错重复) - ✅ 在 regex 上方补充了说明性注释
- ✅ 畸形 URL 用例合并进既有 scheme
it.each(不再重复ftp:///http:…) - ✅
catch分支现已有测试覆盖(https://、http://[::1)
次要(不阻塞)
PR 描述中的 Evidence 取自已被取代的 commit(d464833,"22 passed");当前 head 10e9ae6df 实为 24 个测试。合并前建议刷新描述 —— 代码本身正确。
结论
LGTM —— 可安全合并。 改动精准、测试充分;相邻的携带凭据 URL 子系统(代理 / git clone / npm registry / 脱敏路径)相互独立,不受本校验影响,无回归。
Local build verification by the maintainer; not an automated bot review.
|
@qwen-code /triage |
|
Thanks for the PR @VectorPeak! Template looks good ✓ — all required sections present and filled in. On direction: this is a straightforward credential-security hardening. URLs with embedded userinfo ( On approach: the scope is tight — 9 lines of validation logic + 31 lines of tests, 2 files. No unrelated changes, no scope creep. The approach of parsing with Moving on to code review. 🔍 中文说明感谢贡献 @VectorPeak! 模板完整 ✓ — 所有必需章节均已填写。 方向:这是一个直接的凭据安全加固。包含嵌入式 userinfo( 方案:范围紧凑 — 9 行校验逻辑 + 31 行测试,2 个文件。无无关改动,无范围蔓延。在正则 scheme 检查之后用 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal: Before reading the diff, my approach was: after the existing Diff comparison: The PR's approach matches my proposal exactly — and goes slightly further by also adding malformed-URL test cases (
No blockers found. ✅ Real-Scenario TestingBefore (installed build — main without PR #5783)The bug is clearly visible: all 4 credential URLs and both malformed URLs are silently accepted. After (this PR — built from PR head)All credential URLs rejected, malformed URLs caught, normal URLs unaffected. Unit Tests中文说明代码审查独立方案: 在阅读 diff 之前,我的方案是:在现有的 Diff 对比: PR 的方案与我的方案完全一致——并且进一步补充了畸形 URL 测试用例(
未发现阻塞问题 ✅ 真实场景测试改前(main 不含 PR #5783)Bug 清晰可见:4 个带凭据的 URL 和 2 个畸形 URL 全部被静默放行。 改后(PR head 构建)所有凭据 URL 被拒绝,畸形 URL 被捕获,正常 URL 不受影响。 单元测试— Qwen Code · qwen3.7-max |
|
This is a clean, surgical security fix. The before/after evidence speaks for itself — 6 previously-accepted dangerous inputs (credential URLs, malformed URLs) are now properly rejected, with zero regressions on valid inputs. Going back to my independent proposal from the code review: the PR's approach matched it exactly and went slightly further with malformed-URL coverage. I don't see a simpler path — 9 lines of validation logic is about as minimal as this gets while still being correct. The code is straightforward, the why-comment earns its place, error messages are distinct per failure mode, and the test suite is thorough without being bloated. If I were maintaining this in six months, I'd thank the author. Approving. ✅ 中文说明这是一个干净、精准的安全修复。改前/改后的证据说明了一切——6 个此前被静默接受的危险输入(凭据 URL、畸形 URL)现在被正确拒绝,有效输入零回归。 回顾代码审查中的独立方案:PR 的方案与之完全一致,并进一步补充了畸形 URL 覆盖。我找不到更简路径——9 行校验逻辑在保证正确性的前提下已接近最小化。 代码简洁,why 注释名副其实,三种失败模式的错误消息各不相同,测试充分而不臃肿。如果六个月后维护这段代码,我会感谢作者。 批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅

What this PR does
This PR hardens WebFetch URL validation so
httpandhttpsURLs with embedded userinfo are rejected before the tool invocation continues. Normal URLs such ashttps://example.com/pageremain valid, while inputs such ashttps://user:pass@example.com/page,http://user@example.com/page, and encoded userinfo variants are treated as invalid.Why it's needed
Embedded credentials in URLs are not needed for normal WebFetch usage and can expose sensitive values through validation, confirmation, logging, diagnostic, or error-reporting surfaces. Issue #5782 was accepted as a small, focused credential-security hardening change, and the maintainer bot specifically welcomed rejecting URLs where the parsed username or password component is present, with focused unit coverage.
Reviewer Test Plan
How to verify
Confirm that WebFetch still accepts normal
httpandhttpsURLs without userinfo.Confirm that WebFetch rejects URLs containing a username, password, or both before the request proceeds.
Run:
cd packages/core && npx vitest run src/tools/web-fetch.test.tscd packages/core && npm run typecheckEvidence (Before & After)
Before: issue #5782 reports that the existing validation accepts URLs with embedded userinfo as long as they start with
http://orhttps://.After: WebFetch rejects userinfo URLs during validation. Local focused test coverage now includes
https://user:secret@example.com/page,http://user@example.com/page,https://:secret@example.com/page, andhttps://%75ser@example.com/page.Console validation, Windows:
Console validation, WSL:
Tested on
Environment (optional)
Windows 11 Professional Workstation 10.0.26200 with Node.js v24.15.0 and npm 11.12.1. WSL validation used Ubuntu 22.04.5 LTS on WSL2 with Linux 6.6.114.1-microsoft-standard-WSL2, Node.js v24.15.0, and npm 11.12.1.
Risk & Scope
npm run preflightand macOS validation have not been run locally.Linked Issues
Fixes #5782
中文说明
What this PR does
该 PR 加强了 WebFetch 的 URL 校验,使包含嵌入式 userinfo 的
http和httpsURL 在工具调用继续之前被拒绝。普通 URL(例如https://example.com/page)仍然有效,而https://user:pass@example.com/page、http://user@example.com/page以及编码后的 userinfo 变体会被视为无效。Why it's needed
URL 中嵌入的凭据并不是正常 WebFetch 使用所必需的,并且可能通过校验、确认、日志、诊断或错误报告等表面暴露敏感值。Issue #5782 已被接受为一个小型、聚焦的凭据安全加固改动,维护者机器人也明确欢迎在解析出的用户名或密码组件存在时拒绝该 URL,并补充聚焦的单元测试覆盖。
Reviewer Test Plan
How to verify
确认 WebFetch 仍然接受不包含 userinfo 的普通
http或httpsURL。确认 WebFetch 会在请求继续之前拒绝包含用户名、密码或二者皆有的 URL。
运行:
cd packages/core && npx vitest run src/tools/web-fetch.test.tscd packages/core && npm run typecheckEvidence (Before & After)
Before:issue #5782 报告称,现有校验只要 URL 以
http://或https://开头,就会接受包含嵌入式 userinfo 的 URL。After:WebFetch 会在校验阶段拒绝 userinfo URL。本地聚焦测试覆盖了
https://user:secret@example.com/page、http://user@example.com/page、https://:secret@example.com/page和https://%75ser@example.com/page。Windows 与 WSL 控制台验证均已通过。Tested on
Environment (optional)
Windows 11 Professional Workstation 10.0.26200,Node.js v24.15.0,npm 11.12.1。WSL 验证使用 Ubuntu 22.04.5 LTS / WSL2,Linux 6.6.114.1-microsoft-standard-WSL2,Node.js v24.15.0,npm 11.12.1。
Risk & Scope
npm run preflight和 macOS 验证。Linked Issues
Fixes #5782