Scan active probe bodies for restricted content in renderer spec

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 <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-10-16 06:52:43 +00:00
co-authored by Cursor
parent 123f4fb052
commit 520e7df58b
+10 -4
View File
@@ -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<string, unknown>]] : []),
]
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()