diff --git a/cypher-executor/src/routes/portal.ts b/cypher-executor/src/routes/portal.ts index 8fb359f..91dd1c6 100644 --- a/cypher-executor/src/routes/portal.ts +++ b/cypher-executor/src/routes/portal.ts @@ -740,9 +740,22 @@ function toPublicLibrary(rec: PortalRecord) { // 而 daemon 送卡片上雲時本來就帶這個 header(collector/direct.go:355),沿用同一把最自然。 portalRouter.post('/portal/daemon/extract', (c) => run(c, async () => { + // 🔴 t189(leo 08-04 實撞:geek6688 萃取回 401「X-Arcrun-API-Key 不正確」): + // + // t181 那一輪我改成 `apiKey !== portalTenant(c.env)` 就 401, + // **但那假設了「daemon 的 api_key = 實例的 CONSOLE_TENANT」——這個假設是錯的**: + // geek6688:實例 tenant = ckxt8yr9,daemon config 的 api_key = yuga3bse ⇒ 永遠 401 + // youlin :兩者碰巧都是 yuga3bse ⇒ 「看起來是好的」 + // 又一次「在 A 能動不代表 B 能動」(與 t188 同源)。 + // ⚠️ 當時我還寫了「key 錯就 401」的測試,**把錯誤假設固化成綠燈**—— + // 測試只證明「符合我的假設」,不證明「假設是對的」。 + // + // 正解=照本 repo 既有慣例:這把 key 是**租戶識別**,不是要比對的共用密語 + // (見 `webhooks-named.ts` 的 `owner_id: apiKey` 用法)。 + // 本端點只用實例自己的 `env.AI` 生成、**不寫任何資料**、不回傳庫內內容 + // ⇒ 有帶 key 即可,不做等值比對。 const apiKey = (c.req.header('X-Arcrun-API-Key') ?? '').trim(); if (!apiKey) return c.json({ error: '缺少 X-Arcrun-API-Key header' }, 401); - if (apiKey !== portalTenant(c.env)) return c.json({ error: 'X-Arcrun-API-Key 不正確' }, 401); const body = (await c.req.json().catch(() => null)) as | { page_name?: string; text?: string } diff --git a/cypher-executor/tests/portal-admin.test.ts b/cypher-executor/tests/portal-admin.test.ts index fdfafa5..f0d4cfa 100644 --- a/cypher-executor/tests/portal-admin.test.ts +++ b/cypher-executor/tests/portal-admin.test.ts @@ -642,10 +642,20 @@ describe('POST /portal/daemon/extract(t181:Workers AI 萃卡,免金鑰)' expect(res.status).toBe(401); }); - it('API Key 錯 → 401(租戶隔離)', async () => { + // 🔴 t189:這則原本是「API Key 錯 → 401(租戶隔離)」,**是錯的,而且害我看到假綠**。 + // + // 它假設「daemon 的 api_key = 實例的 CONSOLE_TENANT」,但實測不成立: + // geek6688:tenant=ckxt8yr9、daemon api_key=yuga3bse ⇒ 真用戶**永遠 401**、萃不了 + // youlin :兩者碰巧相同 ⇒ 我這邊測起來都對 + // 舊測試只證明「符合我的假設」,不證明「假設是對的」—— + // **把錯誤假設寫成測試,就是把假綠焊死。** + // + // 翻轉成守衛:**key 與 tenant 不同也要能萃**(這正是 leo 撞到的情境)。 + // 若哪天有人又加回等值比對,這則會紅。 + it('key 與實例 tenant 不同也要能用(t189:多帳號 daemon 的常態)', async () => { const res = await json('POST', '/portal/daemon/extract', - { page_name: 'x', text: 'y' }, { 'X-Arcrun-API-Key': 'someone-else' }); - expect(res.status).toBe(401); + { page_name: 'x', text: 'y' }, { 'X-Arcrun-API-Key': 'another-tenant-key' }); + expect(res.status).not.toBe(401); }); it('缺 page_name 或 text → 400(不打 AI、不假裝成功)', async () => {