10d150ac2b
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>
64 lines
4.1 KiB
TypeScript
64 lines
4.1 KiB
TypeScript
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 條)
|
||
* ② 服務級 token(static 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-hosted:Bearer 帶的是 namespace 明碼(非平台 partner key),
|
||
// 不打 KBDB partner 驗證,直接當 org_namespace(對齊 cypher 的 opaque-key 模型)。
|
||
// 未設 / "true" = 官方 SaaS:維持 partner-key 驗證(行為完全不變)。
|
||
// SDD: sdk-and-website/mcp-account-source.md;HANDOFF §3b。
|
||
MULTI_TENANT?: string;
|
||
|
||
// ── OAuth 2.1 server(claude.ai 遠端 connector 安全登入,見 mcp/OAUTH.md)──────────
|
||
// 短效認證儲存:authorization code(TTL ~600s)+ access token(TTL = MCP_TOKEN_TTL)。
|
||
// 只放「取得的暫時性認證」,key 用 SHA-256 hash(KV 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)。預設 2592000(30 天)。
|
||
// 過期後 claude.ai 重走 OAuth(owner 重輸祕密)——刻意不做 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;
|
||
}
|