From 520e7df58bc26f6256184f00efe3974433535f98 Mon Sep 17 00:00:00 2001 From: weichen Date: Fri, 16 Oct 2026 06:52:43 +0000 Subject: [PATCH] Scan active probe bodies for restricted content in renderer spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: Probes sent with page.request do not raise page response events, so the §11-8 leak scan only covered passive page responses. Evidence: Final run 20261016T065123Z item8-scan.json counted 18 responses, all passive; probe bodies were checked only for rejection and payload. Impact: Every probe and host API body is scanned for project A names, the controlled and uncontrolled document titles and review markers. Refs: unified renderer plan Task 13 §11 Co-authored-by: Cursor --- e2e/document-renderer.playwright.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/e2e/document-renderer.playwright.ts b/e2e/document-renderer.playwright.ts index 45d987e..01bfb0b 100644 --- a/e2e/document-renderer.playwright.ts +++ b/e2e/document-renderer.playwright.ts @@ -550,7 +550,7 @@ test('§11-8 无权用户的页面与网络响应不含受限内容', async ({ b await page.waitForTimeout(1_500) await shot(page, 'item8-outsider-audit') const pageText = (await page.locator('body').textContent()) ?? '' - for (const m of [A.name, A.title, ANNOTATION, REVISION_AFTER]) expect(pageText, `页面不得出现受限内容「${m}」`).not.toContain(m) + for (const m of [A.name, A.title, UNCTRL.title, ANNOTATION, REVISION_AFTER]) expect(pageText, `页面不得出现受限内容「${m}」`).not.toContain(m) const passiveCount = s.net.length // 主动探测:以无权用户会话直调 A 的读取与下载命令(请求中自带的 id 不算泄露) @@ -565,8 +565,10 @@ test('§11-8 无权用户的页面与网络响应不含受限内容', async ({ b ...(st.submissionId ? [['rdm.render.review.view', { projectId: A.pid, submissionId: st.submissionId }] as [string, Record]] : []), ] const probeResults: { id: string; status: number; state: string; hasPayload: boolean }[] = [] + const probeBodies: { url: string; body: string }[] = [] for (const [id, params] of probes) { const r = await command(page, id, params) + probeBodies.push({ url: `probe:${id}`, body: r.text }) const state = r.body?.receipt?.state ?? '' const payload = r.body?.payload const hasPayload = payload !== undefined && payload !== null && !(typeof payload === 'object' && Object.keys(payload).length === 0) @@ -576,6 +578,7 @@ test('§11-8 无权用户的页面与网络响应不含受限内容', async ({ b } for (const kind of ['markdown', 'docx', 'manifest']) { const r = await command(page, 'rdm.render.download', { projectId: A.pid, deliveryId: 'probe', kind }) + probeBodies.push({ url: `probe:rdm.render.download:${kind}`, body: r.text }) probeResults.push({ id: `rdm.render.download:${kind}`, status: r.status, state: r.body?.receipt?.state ?? '', hasPayload: !!r.body?.payload }) expect.soft(!r.body?.payload?.dataBase64, 'rdm.render.download 不得返回文件内容').toBe(true) } @@ -583,22 +586,25 @@ test('§11-8 无权用户的页面与网络响应不含受限内容', async ({ b const hostText = await hostProjects.text() expect.soft(hostText.includes(A.host), '宿主项目列表不得含项目 A').toBe(false) const hostConv = await page.request.get(`/api/projects/${A.host}/conversations`) + const hostConvText = await hostConv.text() + probeBodies.push({ url: '/api/projects', body: hostText }, { url: `/api/projects/${A.host}/conversations`, body: hostConvText }) note(`宿主 A 会话列表 HTTP ${hostConv.status()}`) - expect.soft(hostConv.status() >= 400 || !(await hostConv.text()).includes('§11'), '宿主 A 会话不得可见').toBe(true) + expect.soft(hostConv.status() >= 400 || !hostConvText.includes('§11'), '宿主 A 会话不得可见').toBe(true) note(`主动探测:${JSON.stringify(probeResults)}`) await Promise.all(s.pending) // 全部网络响应体扫描:内容标记任何响应都不得出现;A 的标识只在被动浏览响应中检查 - const contentMarkers = [A.name, A.title, ANNOTATION, REVISION_AFTER, ...(st.downloads ?? [])] + const contentMarkers = [A.name, A.title, UNCTRL.title, ANNOTATION, REVISION_AFTER, ...(st.downloads ?? [])] const idMarkers = [A.pid, A.host, st.submissionId].filter(Boolean) const leaks: string[] = [] s.net.forEach((n, i) => { for (const m of contentMarkers) if (m && n.body.includes(m)) leaks.push(`${n.url} 含「${m}」`) if (i < passiveCount) for (const m of idMarkers) if (n.body.includes(m)) leaks.push(`${n.url} 含标识 ${m}`) }) + for (const p of probeBodies) for (const m of contentMarkers) if (m && p.body.includes(m)) leaks.push(`${p.url} 含「${m}」`) const scanned = path.join(EVID, 'network', 'item8-scan.json') fs.mkdirSync(path.dirname(scanned), { recursive: true }) - fs.writeFileSync(scanned, JSON.stringify({ responses: s.net.length, passive: passiveCount, contentMarkers, idMarkers, leaks, probeResults }, null, 2)) + fs.writeFileSync(scanned, JSON.stringify({ pageResponses: s.net.length, passive: passiveCount, probeResponses: probeBodies.length, contentMarkers, idMarkers, leaks, probeResults }, null, 2)) evidence.push(scanned) expect(leaks, `受限内容泄露:${leaks.join(';')}`).toEqual([]) await s.close()