From eeafd5c0943d8750497a9091566648cef8201136 Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Sun, 14 Jun 2026 22:18:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(kbdb):=20searchByTemplate=20=E7=9C=9F?= =?UTF-8?q?=E6=8C=89=20owner=5Fid=20=E9=81=8E=E6=BF=BE=EF=BC=88=E4=BF=AE?= =?UTF-8?q?=E7=A7=9F=E6=88=B6=E9=9A=94=E9=9B=A2=E6=B4=A9=E6=BC=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 煙霧測試發現 searchByTemplate 的 `if (rec && (!owner_id || true))` 是 stub, `|| true` 讓 owner_id 過濾失效 → 任何租戶查同 template 看得到別人的 record。 改:給 owner_id 時 JOIN entries 在 SQL 限定 e.owner_id(record 歸屬存底層 entries.owner_id,createRecord 寫入時帶);沒給才不限(內部/全域查詢)。 cypher KBDB proxy 強制注入 owner_id,故端到端隔離靠這條 SQL 落地。 searchEntries 早已正確按 owner_id 過濾,無此 bug。kbdb tsc exit 0。 Co-Authored-By: Claude Opus 4.8 (1M context) --- kbdb/src/actions/record-crud.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/kbdb/src/actions/record-crud.ts b/kbdb/src/actions/record-crud.ts index 4e9664c..f0c7589 100644 --- a/kbdb/src/actions/record-crud.ts +++ b/kbdb/src/actions/record-crud.ts @@ -107,14 +107,28 @@ export async function getRecord(db: D1Database, recordId: string): Promise { const tpl = await getTemplate(db, template); if (!tpl) return []; - const res = await db - .prepare(`SELECT DISTINCT record_id FROM entry_values WHERE template_id = ? ORDER BY created_at DESC LIMIT ?`) - .bind(tpl.id, Math.min(limit, 500)) - .all<{ record_id: string }>(); + // owner_id 過濾在 SQL 做:record 的歸屬存在底層 entries.owner_id(createRecord 寫入時帶)。 + // 給了 owner_id → JOIN entries 限定該 owner(租戶隔離,cypher proxy 強制注入); + // 沒給 → 不限(內部/全域查詢)。先前 `|| true` 是 stub,會洩漏跨租戶資料(2026-06-14 修)。 + const cap = Math.min(limit, 500); + const res = owner_id + ? await db + .prepare( + `SELECT DISTINCT ev.record_id as record_id FROM entry_values ev + JOIN entries e ON ev.entry_id = e.id + WHERE ev.template_id = ? AND e.owner_id = ? + ORDER BY ev.created_at DESC LIMIT ?`, + ) + .bind(tpl.id, owner_id, cap) + .all<{ record_id: string }>() + : await db + .prepare(`SELECT DISTINCT record_id FROM entry_values WHERE template_id = ? ORDER BY created_at DESC LIMIT ?`) + .bind(tpl.id, cap) + .all<{ record_id: string }>(); const out: RecordResult[] = []; for (const { record_id } of res.results ?? []) { const rec = await getRecord(db, record_id); - if (rec && (!owner_id || true)) out.push(rec); + if (rec) out.push(rec); } return out; }