From c0aeefc9365eff3a41c6cfd5138c51fe23635ce0 Mon Sep 17 00:00:00 2001 From: Menghuan1918 Date: Fri, 14 Aug 2026 22:59:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9B=9E=E5=BA=94=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=84=8F=E8=A7=81=20=E2=80=94=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E9=92=89=E4=BD=8F=E8=B7=A8=E7=AB=99=E5=88=86=E6=94=AF?= =?UTF-8?q?=E3=80=81=E5=88=A0=E6=AD=BB=E6=8E=A5=E5=8F=A3=E3=80=81=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E6=B3=A8=E9=87=8A=E6=8E=AA=E8=BE=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tests/trusted-hosts.spec.ts:用例 4 改为受信 origin + cross-site marker 以隔离拒绝分支(原同时踩 marker 与 origin 两条路径,删除 marker 检查 测试依旧通过——变异验证现仅该用例失败);接受侧断言从 not.toBe(403) 收紧为 toBe(200),请求体补 sessionId(session.cwd 的真实契约) - src/context-types.ts:删除已无引用的 SidebarLoader/SidebarLoaderEntry 死接口(loader 成员移除后残留) - src/index.ts:修正 "same list / 无需重启" 过承诺注释,改为与 /api 网关 同源的准确表述(boot 采样快照 + --trusted-host 权威,读 live 服务值) --- src/context-types.ts | 10 ---------- src/index.ts | 14 ++++++++------ tests/trusted-hosts.spec.ts | 20 +++++++++++--------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/context-types.ts b/src/context-types.ts index a6d60f4..1f84a2a 100644 --- a/src/context-types.ts +++ b/src/context-types.ts @@ -92,16 +92,6 @@ export interface SidebarSessionStore { } | undefined } -/** One loader entry's options slice (the connection row's resolved config). */ -export interface SidebarLoaderEntry { - options: { name: string; config?: unknown } -} - -/** The loader face used to read the connection row's trustedHosts config. */ -export interface SidebarLoader { - entries(): Iterable -} - /** * The web runtime service face (mirror of @deepseek-ai/dsh-web-app's * WebRuntimeValues): the bind-derived trust list the /api gateway's fence diff --git a/src/index.ts b/src/index.ts index ca7f557..f9e0622 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,9 +4,10 @@ * preview route, the /sidebar/bundle lazy-chunk route (client code splits), * and the terminal WebSocket upgrade. Every route passes the same * browser-trust fence as the /api gateway — Host-header loopback or the - * web runtime's `trustedHosts` (LAN IP literals derived per boot plus - * `--trusted-host` authorities) — read live per request so the fence never - * drifts from the deployment's. + * web runtime's `trustedHosts` (LAN IP literals sampled at boot plus + * `--trusted-host` authorities), read per request from the live service + * value so the fence tracks the same trust source the /api gateway derives + * its list from. * * All operations are conversation-scoped: requests carry a sessionId, the * session's authoritative cwd comes from the session store, and terminal @@ -425,9 +426,10 @@ export function apply(ctx: Context, config?: SidebarConfig): void { // restore it before any terminal can spawn (idempotent). ensureSpawnHelper() const resolved = resolveSidebarConfig(config) - // The web runtime's bind-derived trust list (LAN literals plus - // --trusted-host authorities) — the same list the /api gateway fence - // accepts. Read per request so config reloads apply without a restart. + // The web runtime's bind-derived trust list (boot-sampled LAN literals + // plus --trusted-host authorities) — the authoritative source the /api + // gateway fence derives its list from. Read per request from the live + // service value; a replaced list takes effect without a plugin restart. const fence = (req: SidebarHttpRequest): boolean => isTrustedApiRequest(req, ctx.webRuntime.trustedHosts) const ptyManager = new PtyManager(defaultShell(), resolved.terminalsPerSession) // The agent-owned terminal registry: parallel to the UI-tab ptyManager, diff --git a/tests/trusted-hosts.spec.ts b/tests/trusted-hosts.spec.ts index af78f57..3bc7432 100644 --- a/tests/trusted-hosts.spec.ts +++ b/tests/trusted-hosts.spec.ts @@ -95,7 +95,7 @@ function remoteDomainRequest(): IncomingMessage { host: 'example.com', 'sec-fetch-site': 'same-origin', origin: 'https://example.com', - }) + }, '{"sessionId":"test-session"}') } /** A LAN-IP request with the browser markers a same-origin fetch sends. */ @@ -104,7 +104,7 @@ function lanRequest(): IncomingMessage { host: '192.0.2.1:3080', 'sec-fetch-site': 'same-origin', origin: 'http://192.0.2.1:3080', - }) + }, '{"sessionId":"test-session"}') } describe('remote-access trust (webRuntime.trustedHosts)', () => { @@ -113,7 +113,7 @@ describe('remote-access trust (webRuntime.trustedHosts)', () => { try { const res = fakeRes() await api(remoteDomainRequest(), res as unknown as ServerResponse) - expect(res.status).not.toBe(403) + expect(res.status).toBe(200) } finally { cleanup() } @@ -124,7 +124,7 @@ describe('remote-access trust (webRuntime.trustedHosts)', () => { try { const res = fakeRes() await api(lanRequest(), res as unknown as ServerResponse) - expect(res.status).not.toBe(403) + expect(res.status).toBe(200) } finally { cleanup() } @@ -140,8 +140,8 @@ describe('remote-access trust (webRuntime.trustedHosts)', () => { await api(req('POST', '/sidebar/api/session.cwd', { host: '127.0.0.1:3080', 'sec-fetch-site': 'same-origin', - }), loopback as unknown as ServerResponse) - expect(loopback.status).not.toBe(403) + }, '{"sessionId":"test-session"}'), loopback as unknown as ServerResponse) + expect(loopback.status).toBe(200) } finally { cleanup() } @@ -151,11 +151,13 @@ describe('remote-access trust (webRuntime.trustedHosts)', () => { const { api, cleanup } = mount(['example.com']) try { const res = fakeRes() + // Same-origin origin: only the explicit cross-site marker can reject — + // this pins the marker branch (a mismatched origin would also 403). await api(req('POST', '/sidebar/api/session.cwd', { host: 'example.com', 'sec-fetch-site': 'cross-site', - origin: 'https://evil.example.net', - }), res as unknown as ServerResponse) + origin: 'https://example.com', + }, '{"sessionId":"test-session"}'), res as unknown as ServerResponse) expect(res.status).toBe(403) } finally { cleanup() @@ -173,7 +175,7 @@ describe('remote-access trust (webRuntime.trustedHosts)', () => { setTrustedHosts(['example.com']) const after = fakeRes() await api(remoteDomainRequest(), after as unknown as ServerResponse) - expect(after.status).not.toBe(403) + expect(after.status).toBe(200) } finally { cleanup() }