feat(serve): stop enforcing public-only extension network policy - #10156
Conversation
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Pausing this one for a maintainer call before it goes further — this is a security-boundary change, not a routine feature.
What the PR does well
- Small, focused diff (9 production lines plus tests pinning the new behavior), and the template is complete.
- The motivating use case is real: a consented, credentialed HTTPS install from an enterprise Git hostname that resolves to an RFC1918 address currently dies at DNS with
Extension network host resolved to a blocked address.
Why I'm escalating
- The
publicpolicy being removed is the serve-mode SSRF hardening that shipped deliberately with Extension management v2 (#6825): it rejects hosts whose DNS resolves into private/reserved ranges and pins the resolved connection against DNS rebinding. The design doc states the daemon install endpoint operates "under the public network policy" and that looser sources "remain local CLI features" — the asymmetry between the local CLI (the local user is the principal) and the daemon (remote authenticated callers) reads as designed, and this PR removes it without updating the doc. - The scope is wider than the motivating case: because the policy lives on the shared manager, removing it also opens npm, archive, GitHub-release, marketplace, and nested-extension network operations to hosts that resolve privately — not just the Git install path.
- The remaining guards don't cover the gap: the route-level host check blocks literal localhost/private IPs (including legacy encodings), but not hostnames that resolve to them — which is exactly the case this PR wants to allow, and also the classic SSRF vector. Consent here is a caller-supplied request-body flag validated by the route, not a host-side guarantee.
- No linked issue, and the PR itself notes no real enterprise Git host was reachable from the test environment — so the change was never validated end-to-end against the exact scenario it exists for.
Questions for the maintainer (@doudouOUC authored the v2 policy, @wenshao merged it)
- Is dropping the daemon's public-only network policy outright acceptable, or should private hosts be opt-in — a daemon flag, an allowlist, or a per-request field? The PR explicitly declines those alternatives.
- If it is acceptable, should the relaxation be scoped to credentialed Git installs rather than all manager network operations, and should
docs/design/extension-management-v2.mdbe updated to match?
@callmeYe not a rejection of the use case — but this is sandbox/security-boundary territory, so the people who own that design need to sign off on the direction before code review proceeds.
中文说明
先暂停,等 maintainer 拍板再继续——这是安全边界变更,不是常规功能。
PR 的优点
- 改动小而聚焦(生产代码 9 行 + 固定新行为的测试),模板完整。
- 动机场景真实:带凭据、已同意的 HTTPS 安装,如果企业 Git 主机解析到 RFC1918 私网地址,目前会在 DNS 阶段被
Extension network host resolved to a blocked address拒绝。
升级原因
- 被移除的
public策略是随 Extension management v2(#6825)有意引入的 serve 模式 SSRF 加固:既拒绝 DNS 解析到私网/保留地址段的主机,又对解析结果做连接 pinning 防 DNS rebinding。设计文档明确写了 daemon 安装端点 "under the public network policy"、更宽松的源 "remain local CLI features"——本地 CLI(本地用户即主体)与 daemon(远端已认证调用方)之间的这种不对称看起来是有意设计,本 PR 在未更新文档的情况下将其移除。 - 影响范围大于动机场景:策略挂在共享 manager 上,移除后 npm、archive、GitHub release、marketplace、嵌套扩展的网络操作同样可以指向解析到私网的主机,而不只是 Git 安装路径。
- 剩余防线盖不住缺口:路由层 host 检查只拦字面量 localhost/私网 IP(含传统编码),拦不住"解析到"私网的主机名——而这正是本 PR 要放行的情形,也是典型 SSRF 入口。此处的 consent 是调用方在请求体里自报、由路由校验的字段,不构成宿主侧保证。
- 无关联 issue,且 PR 自述本地环境无法连通真实企业 Git 主机——改动未在其目标场景下端到端验证。
请 maintainer 决定(@doudouOUC 是 v2 策略的作者,@wenshao 合入了该功能)
- 直接取消 daemon 的仅公网网络策略是否可接受?还是私网主机应改为显式开启——daemon 参数、白名单或按请求字段?PR 明确拒绝了这些替代方案。
- 若可接受,放宽是否应收窄到带凭据的 Git 安装,而不是 manager 的全部网络操作?并同步更新
docs/design/extension-management-v2.md?
@callmeYe 不是否定这个使用场景——但这属于沙箱/安全边界领域,需要负责该设计的人先对方向拍板,再进入代码审查。
— Qwen Code · qwen3.8-max
|
Thanks for the careful escalation. I agree this is a product/security-boundary decision rather than a routine implementation detail. I addressed the concrete documentation drift in The direction proposed for maintainer sign-off is intentional:
The observed failure is the reported @doudouOUC @wenshao, could you confirm whether this unrestricted-by-default daemon direction is acceptable? If it is not, I will not try to disguise an allowlist or per-request exception as the same minimal change; that would be a different product design. 中文说明感谢谨慎升级。我同意这属于产品/安全边界决策,而不是普通实现细节。 我已在 当前请求 maintainer 拍板的方向是有意为之:
已观测问题是:企业 Git 主机解析到私网地址时会报 @doudouOUC @wenshao,请确认 daemon 默认不限制的方向是否可接受。如果不可接受,我不会把白名单或按请求例外伪装成同一个最小改动;那将是另一套产品设计。 |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 12 scenario(s). — Qwen Code · serve A/B |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
— qwen3.8-max via Qwen Code /review (v0.22.2)
…xtension-network # Conflicts: # packages/cli/src/serve/routes/workspace-extensions.ts
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
2 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R1-2 leftover
parseInstallSourcepolicy stamp in the localSource pre-validation branch — already reported (comment 3864031647) - R1-3 dead
ExtensionManagerOptions.networkPolicyconfiguration surface with no production setter — already reported (comment 3864031656)
Not explored to full depth (tool budget reached): "agent 1d": none — no check was cut short..
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
Test Plan (not a blocker): 6 tests passed — this review observed 24988 passed.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
packages/cli/src/serve/server.test.ts:8059 — [review] no effect-level test that a private-resolving hostname install succeeds
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未探索到全部深度(达到工具调用预算):"agent 1d":none — no check was cut short.。
未审查:反向审计——评审时间预算不足,未能开始第 4 轮。
Test Plan(非阻断):6 tests passed — this review observed 24988 passed。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.2)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): "agent 6c": none — no checks were cut short..
[Critical] R1-7 (still stands — re-checked against the code at this commit): Removing the networkPolicy: 'public' stamp also strips the only protocol/host/DNS/environment gate that applied to manifest-derived nested plugin sources. The route edge (parseExtensionInstallSource) validates only the top-level install source; nested plugin sources inside Claude marketplace manifests flow through resolvePluginSource in packages/core/src/extension/claude-converter.ts, which passes manifest-supplied object-source URLs (url, git-subdir) verbatim into cloneFromGit. With the stamp gone, nested fetches skip the HTTPS-only refusal, the pinned transport config (http.followRedirects=false, protocol.allow=never), the DNS blocklist, and the restricted git environment. Concrete trigger: an attacker publishes a marketplace repo whose .claude-plugin/marketplace.json lists a plugin with source {"source": {"source": "url", "url": "ssh://internal-git.corp/repo.git"}} (or git-subdir with an internal http URL); a daemon client installs https://github.com/attacker/marketplace:plugin with consent: true — the outer https URL passes the route edge, installExtensionInternal clones the outer repo, and the nested source then reaches cloneFromGit against the attacker-chosen scheme and host with the daemon user's full environment (SSH agent included), while consent only covered the outer plugin. Witness (probe at the reviewed commit 32ff7946, this round, in an isolated scratch tree — driving the real convertClaudePluginPackage with an attacker-shaped marketplace manifest and the real cloneFromGit, with a FLIP arm restoring the pre-PR shape): post-PR nested url-source metadata {"source":"ssh://internal-git.corp/repo.git","type":"git"} with no networkPolicy; simpleGit factory args ["/dest",{}] — no pinned transport config; git env-restriction call count 0 (git inherits the daemon user's full environment, SSH agent included); dns.lookup call count 0 — DNS blocklist skipped; clone proceeds against ssh://internal-git.corp/repo.git; the FLIP arm with networkPolicy: 'public' rejects with Public extension Git installs must use HTTPS. and git.clone is never called. Fix direction: gate manifest-derived nested sources independently of the removed manager policy — in resolvePluginSource, reject non-https nested source URLs (re-running the route edge's protocol/host validation), or keep threading a daemon-origin marker so nested cloneFromGit calls retain the HTTPS-only refusal and the restrictive transport config. Acceptance criterion: a collocated test in packages/core/src/extension/claude-converter.test.ts asserting that convertClaudePluginPackage/resolvePluginSource reject ssh:// and http:// nested plugin sources when called with networkPolicy: undefined, and the test must go red when the new guard is removed.
中文说明
未探索到全部深度(达到工具调用预算):"agent 6c":none — no checks were cut short.。
[Critical] R1-7 (still stands — re-checked against the code at this commit): Removing the networkPolicy: 'public' stamp also strips the only protocol/host/DNS/environment gate that applied to manifest-derived nested plugin sources. The route edge (parseExtensionInstallSource) validates only the top-level install source; nested plugin sources inside Claude marketplace manifests flow through resolvePluginSource in packages/core/src/extension/claude-converter.ts, which passes manifest-supplied object-source URLs (url, git-subdir) verbatim into cloneFromGit. With the stamp gone, nested fetches skip the HTTPS-only refusal, the pinned transport config (http.followRedirects=false, protocol.allow=never), the DNS blocklist, and the restricted git environment. Concrete trigger: an attacker publishes a marketplace repo whose .claude-plugin/marketplace.json lists a plugin with source {"source": {"source": "url", "url": "ssh://internal-git.corp/repo.git"}} (or git-subdir with an internal http URL); a daemon client installs https://github.com/attacker/marketplace:plugin with consent: true — the outer https URL passes the route edge, installExtensionInternal clones the outer repo, and the nested source then reaches cloneFromGit against the attacker-chosen scheme and host with the daemon user's full environment (SSH agent included), while consent only covered the outer plugin. Witness (probe at the reviewed commit 32ff7946, this round, in an isolated scratch tree — driving the real convertClaudePluginPackage with an attacker-shaped marketplace manifest and the real cloneFromGit, with a FLIP arm restoring the pre-PR shape): post-PR nested url-source metadata {"source":"ssh://internal-git.corp/repo.git","type":"git"} with no networkPolicy; simpleGit factory args ["/dest",{}] — no pinned transport config; git env-restriction call count 0 (git inherits the daemon user's full environment, SSH agent included); dns.lookup call count 0 — DNS blocklist skipped; clone proceeds against ssh://internal-git.corp/repo.git; the FLIP arm with networkPolicy: 'public' rejects with Public extension Git installs must use HTTPS. and git.clone is never called. Fix direction: gate manifest-derived nested sources independently of the removed manager policy — in resolvePluginSource, reject non-https nested source URLs (re-running the route edge's protocol/host validation), or keep threading a daemon-origin marker so nested cloneFromGit calls retain the HTTPS-only refusal and the restrictive transport config. Acceptance criterion: a collocated test in packages/core/src/extension/claude-converter.test.ts asserting that convertClaudePluginPackage/resolvePluginSource reject ssh:// and http:// nested plugin sources when called with networkPolicy: undefined, and the test must go red when the new guard is removed.
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
@doudouOUC @wenshao — maintainer go/no-go decision requested. Please decide the exact daemon trust boundary rather than start another implementation round:
R1-7 is technically valid and has not been dismissed. The question is whether the unrestricted daemon default is an acceptable product/security tradeoff. Please give one explicit decision:
Current evidence: CI is complete with no actionable failures; the PR is mergeable and remains blocked by |
Maintainer decision: AcceptI accept the unrestricted-by-default R1-7 is technically valid. This decision accepts its consequences as an intentional product/security tradeoff; it does not dismiss or dispute the finding. The following risks and operational consequences are explicitly acknowledged:
This settles the requested product/security direction. It does not waive separate concrete implementation findings, tests, or normal merge gates that remain applicable to the PR. |
Stale after maintainer decision and current-head follow-up. doudouOUC explicitly accepted the technically valid R1-7 tradeoff in issuecomment-5440114061; R1-2 and R1-4 are fixed in b692158 with focused tests; R1-3 is explicitly deferred because full removal is a separate core public-API compatibility change. All review threads are resolved. This dismissal records the completed disposition and does not dispute R1-7.
|
Thanks @doudouOUC — the accepted direction is now fully reflected in the review disposition on current HEAD
GitHub now reports |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
[Critical] R1-7: (still stands — re-checked against the code at this commit) Removing the networkPolicy: 'public' stamp also strips the only protocol/host/DNS/environment gate that applied to manifest-derived nested plugin sources. The route edge (parseExtensionInstallSource) validates only the top-level install source; nested plugin sources inside Claude marketplace manifests flow through resolvePluginSource in packages/core/src/extension/claude-converter.ts, which passes manifest-supplied object-source URLs (url, git-subdir) verbatim into cloneFromGit — verified again at this commit: no scheme or host gate exists there, and no test rejects ssh:// or http:// nested sources when networkPolicy is undefined. With the stamp gone from the daemon, nested fetches skip the HTTPS-only refusal, the pinned transport config (http.followRedirects=false, protocol.allow=never), the DNS blocklist, and the restricted git environment. Concrete trigger: an attacker publishes a marketplace repo whose .claude-plugin/marketplace.json lists a plugin with source {"source": {"source": "url", "url": "ssh://internal-git.corp/repo.git"}}; a daemon client installs https://github.com/attacker/marketplace:plugin with consent: true — the outer https URL passes the route edge, and the nested source then reaches cloneFromGit against the attacker-chosen scheme and host with the daemon user's full environment (SSH agent included), while consent only covered the outer plugin. Witness (probe at this commit, flipped between the two call shapes, driving the real convertClaudePluginPackage → resolvePluginSource → cloneFromGit with only the simple-git transport stubbed): policy-undefined arm — resolved, git.clone called with ssh://internal-git.corp/repo.git (and http://internal-git.corp/repo.git via the git-subdir arm), simpleGit factory args [<dest>,{}] (no pinned transport config), env-restriction call count 0; FLIP arm with networkPolicy: 'public' — rejected with Public extension Git installs must use HTTPS., cloneCalls []. Round-3's probe observed the same shape at the then-reviewed commit. Maintainer @doudouOUC explicitly accepted this boundary and its consequences as an intentional product/security tradeoff in the PR thread; this re-post records that the mechanism is still present in the code, per the review contract that only code retires a blocker. Fix direction (should the decision ever change): gate manifest-derived nested sources independently — in resolvePluginSource, reject non-https nested source URLs, or keep threading a daemon-origin marker so nested cloneFromGit calls retain the HTTPS-only refusal and the restrictive transport config. Acceptance criterion: a collocated test in packages/core/src/extension/claude-converter.test.ts asserting that convertClaudePluginPackage/resolvePluginSource reject ssh:// and http:// nested plugin sources when called with networkPolicy: undefined, going red when the guard is removed.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
[Critical] R1-7: (still stands — re-checked against the code at this commit) Removing the networkPolicy: 'public' stamp also strips the only protocol/host/DNS/environment gate that applied to manifest-derived nested plugin sources. The route edge (parseExtensionInstallSource) validates only the top-level install source; nested plugin sources inside Claude marketplace manifests flow through resolvePluginSource in packages/core/src/extension/claude-converter.ts, which passes manifest-supplied object-source URLs (url, git-subdir) verbatim into cloneFromGit — verified again at this commit: no scheme or host gate exists there, and no test rejects ssh:// or http:// nested sources when networkPolicy is undefined. With the stamp gone from the daemon, nested fetches skip the HTTPS-only refusal, the pinned transport config (http.followRedirects=false, protocol.allow=never), the DNS blocklist, and the restricted git environment. Concrete trigger: an attacker publishes a marketplace repo whose .claude-plugin/marketplace.json lists a plugin with source {"source": {"source": "url", "url": "ssh://internal-git.corp/repo.git"}}; a daemon client installs https://github.com/attacker/marketplace:plugin with consent: true — the outer https URL passes the route edge, and the nested source then reaches cloneFromGit against the attacker-chosen scheme and host with the daemon user's full environment (SSH agent included), while consent only covered the outer plugin. Witness (probe at this commit, flipped between the two call shapes, driving the real convertClaudePluginPackage → resolvePluginSource → cloneFromGit with only the simple-git transport stubbed): policy-undefined arm — resolved, git.clone called with ssh://internal-git.corp/repo.git (and http://internal-git.corp/repo.git via the git-subdir arm), simpleGit factory args [<dest>,{}] (no pinned transport config), env-restriction call count 0; FLIP arm with networkPolicy: 'public' — rejected with Public extension Git installs must use HTTPS., cloneCalls []. Round-3's probe observed the same shape at the then-reviewed commit. Maintainer @doudouOUC explicitly accepted this boundary and its consequences as an intentional product/security tradeoff in the PR thread; this re-post records that the mechanism is still present in the code, per the review contract that only code retires a blocker. Fix direction (should the decision ever change): gate manifest-derived nested sources independently — in resolvePluginSource, reject non-https nested source URLs, or keep threading a daemon-origin marker so nested cloneFromGit calls retain the HTTPS-only refusal and the restrictive transport config. Acceptance criterion: a collocated test in packages/core/src/extension/claude-converter.test.ts asserting that convertClaudePluginPackage/resolvePluginSource reject ssh:// and http:// nested plugin sources when called with networkPolicy: undefined, going red when the guard is removed.
— qwen3.8-max via Qwen Code /review (v0.22.2)
Dismissed as stale on current head b692158: this review repeats only R1-7 and introduces no new finding. Maintainer @doudouOUC explicitly accepted the exact nested SSH/HTTP/private-network and unrestricted daemon-environment tradeoff in #10156 (comment); all associated R1-7 threads have been dispositioned and resolved, and there is no live human objection. This dismissal records the accepted product/security decision; it does not dispute the technical finding.
Maintainer verification — real
|
|
Thanks @wenshao — this closes the live-daemon end-to-end verification gap on current head I classified the additional observations as follows:
The implementation diff remains unchanged; all checks are green and all six review threads are resolved. GitHub still requires one formal human approval. Since your executed verification concludes no merge blocker, could you please submit a formal APPROVE review on |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 99 passed · 0 failed · 99 total Flakiness gate: ✅ 3 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:99 通过 · 0 失败 · 99 总计 抖动门:✅ 3 changed test file(s) x 5 identical rounds, no divergence Verification reportPR 10156 — feat(serve): stop enforcing public-only extension network policyVerdict: 中文摘要
Scope
Central claim: A/B proofHarness: real compiled
Per-cell scripted checks: 93/93 passed ( Mutation matrix — which hunk is load-bearing?Two injection sites were removed (manager + routes). Re-adding one at a time in a scratch copy of the head dist:
Capture: Vacuity of the new testsHEAD's test files copied onto base source (== reverting the source hunks):
Every new assertion fails on the un-fixed source with the expected-versus-actual values the test exists to catch. Capture: Targeted gates (head)
FindingsNone blocking. Two observations, both consistent with the PR's own stated tradeoffs (not defects):
Not covered
MethodologyEnvironment: Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed current head b692158e93 and its clean merge with the latest main. No new actionable findings.
Verified the full diff and downstream Extension network-policy consumers, both strict daemon install routes, credential redaction and isolation, legacy persisted-policy behavior, current review threads, and the documented trust-boundary tradeoff. R1-7 remains technically real, but it is the explicitly accepted maintainer product/security decision rather than an unaddressed implementation defect.
Local merge-tree verification passed build/bundle, repository typecheck, changed-file lint/format/diff checks, 6 focused CLI regressions, and 226 related Core tests. The two unrelated full-file flakes both passed when rerun by name. All current GitHub checks are green and no review thread is unresolved.









What this PR does
Stops
qwen servefrom imposing a public-only network policy on the shared Extension manager and on newly parsed Extension install sources. The workspace-compatible and global install APIs now inherit the Extension subsystem's existing default of no network restriction, so an authenticated caller such as opencode can install from an HTTPS Git hostname that resolves to an enterprise-private address without a new flag, setting, or request field.Why it's needed
The daemon currently injects the public-only policy at both the manager and route parsing boundaries. DNS resolution therefore rejects enterprise Git hosts before Git can clone them, including credentialed HTTPS sources that already passed consent, URL validation, credential stripping, and daemon authentication. The standalone Extension CLI does not inject this policy, so removing the daemon-only override also restores consistent defaults between the two entry points.
Reviewer Test Plan
How to verify
/workspace/extensions/installusing a hostname that resolves to an RFC1918 address; expect the operation to reach Git instead of failing withExtension network host resolved to a blocked address./extensions/install; expect the same behavior.networkPolicy; confirm the shared daemon Extension manager also has no default policy.Evidence (Before & After)
Before:
qwen serverejects a private enterprise Git hostname after DNS resolution. After: both daemon install APIs pass policy-free metadata to a policy-free shared Extension manager. Focused regression tests pass: 3 files, 6 tests passed. Full affected-file test execution passed 1,139 of 1,142 tests; the three failures were unrelated timing/resource flakes and all passed when rerun by name.npm run build,npm run typecheck, the CLI workspace typecheck, focused ESLint, formatting, andgit diff --checkalso pass.Tested on
Environment (optional)
macOS, Node.js v22.17.0, npm 10.9.2. Route tests were run outside the filesystem sandbox because the sandbox blocks localhost DNS resolution.
Risk & Scope
insteadOfrules may rewrite a validated source after daemon-side validation, so the recorded source is not authoritative for the actual network destination. Existing strict daemon authentication, explicit install consent, workspace trust checks, credentialed HTTPS URL validation, URL credential stripping, credential-scoped Git headers, isolated credentialed Git environment, and literal localhost/private-IP source rejection remain in place.qwen serve; opencode requires no code change. Existing installed metadata that already containsnetworkPolicy: "public"retains it, so reinstalling that Extension may be required before a private-host update can work. One-time credential installs remain snapshots and do not gain automatic updates.Linked Issues
N/A
中文说明
本 PR 做了什么
停止让
qwen serve在共享 Extension manager 和新解析的 Extension 安装源上强制注入仅公网网络策略。兼容 workspace 的安装接口与全局安装接口现在都会继承 Extension 子系统原有的默认行为,即不施加网络限制,因此 opencode 等已认证调用方可以直接从解析到企业私网地址的 HTTPS Git 主机安装,无需新增启动参数、setting 或请求字段。为什么需要
daemon 当前同时在 manager 和路由解析边界注入仅公网策略,因此 DNS 解析阶段会在 Git clone 之前拒绝企业 Git 主机;即使是已经通过用户同意、URL 校验、凭据剥离和 daemon 鉴权的带凭据 HTTPS 源也会失败。独立运行的 Extension CLI 并不会注入这一策略,因此删除 daemon 专属覆盖也让两个入口恢复一致的默认行为。
Reviewer 测试计划
如何验证
/workspace/extensions/install提交已同意的带凭据 HTTPS Git 安装;预期请求能够进入 Git,而不是报Extension network host resolved to a blocked address。/extensions/install重复验证;预期行为一致。networkPolicy,同时确认 daemon 共享 Extension manager 也没有默认策略。证据(Before & After)
Before:
qwen serve会在 DNS 解析后拒绝企业私网 Git 主机。After:两个 daemon 安装接口都会把不含策略的 metadata 交给同样不含默认策略的共享 Extension manager。聚焦回归测试通过:3 个文件、6 个测试全部通过。受影响测试文件的完整执行共 1,142 个测试,其中 1,139 个通过;3 个与本次改动无关的时序/资源抖动用例在按名称单独重跑后全部通过。npm run build、npm run typecheck、CLI workspace typecheck、聚焦 ESLint、格式化以及git diff --check也全部通过。测试平台
环境(可选)
macOS,Node.js v22.17.0,npm 10.9.2。由于文件系统沙箱会阻断 localhost DNS 解析,路由测试在沙箱外执行。
风险与范围
insteadOf规则也可能在 daemon 完成校验后改写 URL,因此落盘记录的 source 并不能权威代表实际网络目的地。现有的严格 daemon 鉴权、显式安装同意、workspace trust 校验、带凭据 HTTPS URL 校验、URL 凭据剥离、按凭据作用域生成的 Git header、带凭据 Git 的隔离环境,以及字面量 localhost/私网 IP 源拒绝仍然保留。qwen serve,opencode 无需修改代码。已经安装且 metadata 中仍含networkPolicy: "public"的 Extension 会保留该值,因此在私网主机上执行更新前可能需要重新安装。一次性凭据安装仍是 snapshot,不会获得自动更新能力。关联 Issue
N/A