fix: 回应审查保留意见 — 测试钉住跨站分支、删死接口、修正注释措辞

- 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 服务值)
This commit is contained in:
Menghuan1918
2026-08-14 23:03:26 +08:00
committed by Atlas Zeng
parent ea250c9e87
commit c0aeefc936
3 changed files with 19 additions and 25 deletions
-10
View File
@@ -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<SidebarLoaderEntry>
}
/**
* The web runtime service face (mirror of @deepseek-ai/dsh-web-app's
* WebRuntimeValues): the bind-derived trust list the /api gateway's fence
+8 -6
View File
@@ -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,
+11 -9
View File
@@ -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()
}