From 23f2fd499afbd75b738c2ed77783f783fe8b5634 Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Tue, 4 Aug 2026 20:39:40 +0800 Subject: [PATCH] =?UTF-8?q?t189=EF=BC=9A/portal/daemon/extract=20=E6=8B=BF?= =?UTF-8?q?=E6=8E=89=20tenant=20=E7=AD=89=E5=80=BC=E6=AF=94=E5=B0=8D?= =?UTF-8?q?=E2=80=94=E2=80=94geek6688=20=E8=90=83=E5=8F=96=E6=B0=B8?= =?UTF-8?q?=E9=81=A0=20401?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit leo 實測(拔掉所有 Gemini 金鑰後跑 daemon): 深海熱泉.md → failed 雲端萃取失敗(HTTP 401):X-Arcrun-API-Key 不正確 ## 真兇=我在 t181 自己加的錯誤假設 t181 那輪改成 apiKey !== portalTenant(env) 就 401, 假設「daemon 的 api_key = 實例的 CONSOLE_TENANT」。實測不成立: geek6688:tenant=ckxt8yr9、daemon api_key=yuga3bse ⇒ 真用戶**永遠 401** youlin :兩者碰巧相同 ⇒ 我這邊怎麼測都對 又一次「在 A 能動不代表 B 能動」(與 t188 同源)。 ## 更糟的是:我把錯誤假設寫成了測試 t181 附的「API Key 錯 → 401(租戶隔離)」只證明「符合我的假設」, 不證明假設是對的 ⇒ **綠燈把錯誤焊死**。 本次翻轉成守衛:「key 與實例 tenant 不同**也要能萃**」, 若有人再加回等值比對,該則會紅。 ## 正解 照本 repo 既有慣例——這把 key 是租戶識別,不是共用密語 (webhooks-named.ts 用 owner_id: apiKey)。 本端點只用實例自己的 env.AI 生成、不寫任何資料、不回傳庫內內容 ⇒ 有帶 key 即可。 測試:portal-admin 31 passed(含四則 extract 全過); 唯一 failed 的 v-admin 為基準線既有(stash 對照確認),與本次無關。 --- cypher-executor/src/routes/portal.ts | 15 ++++++++++++++- cypher-executor/tests/portal-admin.test.ts | 16 +++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) 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 () => {