Files
Arcrun/mcp/src/types.ts
T
uncle6me-web 10d150ac2b fix(mcp): MCP 用登入者的身分查詢,不再去找一把服務內部金鑰
leo 2026-08-12:「人類進 Portal 輸入帳密表示你是主人,可以查到你權限所有東西;
AI 透過輸入帳密的 MCP 查詢表示是授權的 AI,可以查到主人允許查的任何東西。」
「掛上 MCP 並輸入帳密,那個動作本身就是授權」⇒ 下游不得再要求第二次認證。

病根(不是金鑰沒同步,是身分沒接住):
  oauth/routes.ts 驗完 Portal 帳密只留下 `loginOk = res.ok` 一個布林值,身分當場丟棄,
  namespace 改從 `MCP_OWNER_NAMESPACE || "leo"` 拿。於是查詢時手上沒有身分可帶,
  只好用 KBDB_INTERNAL_TOKEN 直打 KBDB——那條路繞過 portal 所有庫過濾,
  而且不管誰登入都看到同一格、看到全部。CLI 也從不注入 MCP_OWNER_NAMESPACE,
  所以那個 "leo" 預設值是每台實例的實際行為,不是理論上的邊角。

修法(走既有那條路,不發明新的):
1. 接住身分:/authorize 解析 /portal/login 回應,把 portal session token +
   display_name/role/libraries 存進 authorization code → access token。
   /portal/login 補回 session_expires_in,access_token TTL 夾成
   min(自己的 TTL, portal session TTL)——不讓「MCP 還連著、底下 session 早死」。
   cypher 回 200 但沒給 session_token(舊版)→ 不發碼,不簽一張沒有身分的 token。
2. 攜帶身分:kbdb_* 全部改走 cypher `/portal/data/*`,Authorization 帶登入者的
   session。庫過濾/租戶注入/停用即時生效全在 server 側,與人類走 portal 網頁同一道閘。
   kbdb_graph_neighbors 因此不再需要 kbdb_base(server 自己知道查哪個庫)。
   藏書地圖(含連線時注入 instructions 的那份)同樣只回有權限的庫,快取改 per-session
   分格——地圖本身就是情報,不能讓先連上的人把視野留給下一個。
3. fail-closed:舊 token 沒有身分 → 誠實要求重新連線,不偷偷退回服務金鑰那條老路。
   服務級憑據(static token / partner key)維持既有 KBDB 直連,arcrun_* 零回歸。

新增 cypher portal 資料面端點(能力長在 API,MCP 只暴露;rule 07):
  GET  /portal/data/map、/portal/data/map/:library
  GET  /portal/data/templates、POST /portal/data/templates
  GET  /portal/data/records/by-template/:t、GET /portal/data/records/:id
  POST /portal/data/records
全部:呼叫端自帶 owner_id 一律不生效;越權與不存在同回 404;寫入 owner_id 由 server 定死。

KBDB base:`GET /records/:id` 與 by-template 補回 owner_id 欄位——原本不回,
呼叫端無從判斷「這筆是不是我的」,按 id 直讀等於沒有租戶邊界。

沒動:KBDB fail-closed 閘、任何金鑰、租戶字串仍不下發給呼叫端。

驗證:
  mcp        tsc 綠;vitest 113/113 綠(改前 48 綠 29 紅)
  cypher     vitest 400 綠 / 14 紅,14 紅與 base commit a24f291 逐條相同(既有)
  kbdb       vitest 208 綠 / 5 紅,5 紅同為既有(migrations/*.sql 被 gitignore)
  端到端     ◐ 未驗:需部署到 leo21c,那道閘要 leo 親手解(見 PR)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:33:12 +08:00

64 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface Env {
COMPONENT_REGISTRY: Fetcher;
CYPHER_EXECUTOR: Fetcher;
KBDB: Fetcher;
/**
* KBDB 的服務內部金鑰。
*
* 2026-08-12 後**知識面(kbdb_*)以帳密連線時完全不用它**——那條路改走 cypher 的
* `/portal/data/*`,帶的是登入者自己的 portal session。它現在只剩兩個用途:
* ① 官方 SaaS 的 partner-key 驗證(middleware/partner-auth.ts 第 3 條)
* ② 服務級 tokenstatic token)連線時的既有 KBDB 直連(零回歸)
* 兩者都拆掉之後,這個 binding 才能從 MCP 移除。
*/
KBDB_INTERNAL_TOKEN: string;
API_KEY?: string;
// Platform telemetry / feedback aggregation key (optional)
// 對應 arcrun SDD .agents/specs/llm-interface/ M1.2-1.3
// 設了會把 agent-feedback / agent-telemetry block 都寫到 platform user_id 下;
// 沒設則 fallback 寫進 user 自己的 namespace
PLATFORM_API_KEY?: string;
// Self-hosted 單租戶模式旗標(與 cypher-executor 同名同義)。
// "false" = self-hostedBearer 帶的是 namespace 明碼(非平台 partner key),
// 不打 KBDB partner 驗證,直接當 org_namespace(對齊 cypher 的 opaque-key 模型)。
// 未設 / "true" = 官方 SaaS:維持 partner-key 驗證(行為完全不變)。
// SDD: sdk-and-website/mcp-account-source.mdHANDOFF §3b。
MULTI_TENANT?: string;
// ── OAuth 2.1 serverclaude.ai 遠端 connector 安全登入,見 mcp/OAUTH.md)──────────
// 短效認證儲存:authorization codeTTL ~600s+ access tokenTTL = MCP_TOKEN_TTL)。
// 只放「取得的暫時性認證」,key 用 SHA-256 hashKV list 不外洩可用 token)。長效機密不進 KV。
OAUTH_KV?: KVNamespace;
// 【已停用,2026-07-30】舊的 owner 祕密。把關改成「使用者自己的 Portal 帳密」——
// 沒人給得了封測者這把祕密(安裝器產生後從不顯示、CF secret 又讀不回),
// 而且全實例共用一把、分不出是誰連上來的。程式已不再讀它;欄位留著只為不讓舊 toml 炸掉。
MCP_OWNER_SECRET?: string;
// **工作流面**(arcrun_* 工具)的租戶代號,當 cypher 的 X-Arcrun-API-Key 用。預設 "leo"。
//
// ⚠️ 2026-08-12 起**知識面(kbdb_*)不再讀這個欄位**:那邊改成跟著登入者的 portal session
// 走(oauth/store.ts PortalIdentity)。此欄位曾被當成 KBDB 的 owner_id ⇒ 不管誰登入
// 都看到同一格、而且是全部——那個用法已經消滅。
// 要連工作流面也拆掉它,得先在 cypher 開一組吃 portal session 的 workflow 端點(下一步)。
MCP_OWNER_NAMESPACE?: string;
// access_token 存活秒數(同時是 KV TTL)。字串(toml var)。預設 259200030 天)。
// 過期後 claude.ai 重走 OAuthowner 重輸祕密)——刻意不做 refresh token 以免長效機密落地。
MCP_TOKEN_TTL?: string;
// 本機 CLI / GUI / 本機 Claude Code 的相容用「真祕密 token」(CF Secret)。
// 設了才啟用:Bearer 精確等於此值 → 解成 MCP_OWNER_NAMESPACE。取代舊「明碼 namespace 當 bearer」漏洞路徑。
MCP_STATIC_TOKEN?: string;
// 【不安全】相容逃生門:="true" 時恢復舊「Bearer 明碼即 namespace」行為(self-hosted)。
// 預設 undefined = 關(安全)。僅供遷移期,設了等於重新打開被修掉的漏洞——別在正式環境開。
ALLOW_PLAINTEXT_NAMESPACE?: string;
// 允許的 redirect_uri host 白名單(逗號分隔)。DCR 無狀態故靠此擋 open-redirect/釣魚。
// 未設 → 預設只允許 claude.ai / claude.com / anthropic.com(含子網域)+ localhost。
MCP_ALLOWED_REDIRECT_HOSTS?: string;
// 部署標記(非機密):GET /health 原樣回報,讓「這台跑的是哪一版」用一條 curl 看得到。
// 由各實例的 wrangler toml [vars] 帶入;沒帶 → /health 回 build:"unknown"(誠實,不假裝)。
MCP_BUILD?: string;
}
export interface ToolContext {
env: Env;
orgNamespace: string;
}