t189:/portal/daemon/extract 拿掉 tenant 等值比對——geek6688 萃取永遠 401

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 對照確認),與本次無關。
This commit is contained in:
uncle6me-web
2026-08-04 20:39:40 +08:00
parent b3a57d95f6
commit 23f2fd499a
2 changed files with 27 additions and 4 deletions
+14 -1
View File
@@ -740,9 +740,22 @@ function toPublicLibrary(rec: PortalRecord) {
// 而 daemon 送卡片上雲時本來就帶這個 headercollector/direct.go:355),沿用同一把最自然。
portalRouter.post('/portal/daemon/extract', (c) =>
run(c, async () => {
// 🔴 t189leo 08-04 實撞:geek6688 萃取回 401「X-Arcrun-API-Key 不正確」):
//
// t181 那一輪我改成 `apiKey !== portalTenant(c.env)` 就 401
// **但那假設了「daemon 的 api_key 實例的 CONSOLE_TENANT」——這個假設是錯的**:
// geek6688:實例 tenant = ckxt8yr9daemon 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 }
+13 -3
View File
@@ -642,10 +642,20 @@ describe('POST /portal/daemon/extractt181Workers AI 萃卡,免金鑰)'
expect(res.status).toBe(401);
});
it('API Key 錯 → 401(租戶隔離)', async () => {
// 🔴 t189:這則原本是「API Key 錯 → 401(租戶隔離)」,**是錯的,而且害我看到假綠**。
//
// 它假設「daemon 的 api_key 實例的 CONSOLE_TENANT」,但實測不成立:
// geek6688tenant=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 () => {