Files
Leo 20c7610371 refactor: 移除已廢棄的自管加密金鑰機制(credential 全面託管 CF Workers Secrets)
leo 2026-07-20 明令:「已經改用 cf 自己的 secrets,不要再說它了」
「我希望以後再也看不到這個詞再出現」

背景:credential 早已遷移至 CF Workers per-script Secrets + D1 目錄,
舊的自管金鑰(client 端 AES-GCM + KV 密文 + crypto_decrypt)是遷移期遺留。
本次連根移除,含一併作廢的死 SaaS 碼。

移除:
- 舊 KV 密文解密路徑(credential-injector.ts 整檔、dual-read fallback)
  前置驗證:leo21c / youlin 兩帳號 CREDENTIALS_KV 實測 *:cred:* 皆 0 筆
- migrate-to-workers-secrets 搬家端點(回填已完成,無可回填)
- /register 路由與 generateApiKey(HMAC 產 ak_ key 是 SaaS 遺物;
  self-hosted 走 namespace 明碼 D21,已無人使用)
- platform_crypto component(三帳號實測 404 已退役,無 workflow 引用)

保留(附理由):
- crypto_decrypt 保留為永遠回失敗的 stub——現役三個 auth .wasm 仍宣告該
  import,缺項會讓 WASM instantiate 直接失敗。待零件重編後可真正刪除。

順帶修復(原不在範圍,但會實際壞事):
- /auth/callback 有 `if (!key) redirect(server_error)` 閘,未設該 secret 的
  實例會登入直接失敗 → 已移除
- OAuth 兩處把 provider token 寫進舊加密 KV(租戶鍵與實際 api_key 在 rotate
  後必然分歧,已失效)→ 改導向 Workers Secrets,包 try/catch 不影響登入
- acr init Standard 模式呼叫已刪除的 /register → 改引導 OAuth 取 key
- .claude/rules 與 system-dev/docs 是同一規範的兩份鏡像,先前只改 rules
  導致鏡像仍在教舊做法 → 已同步(此類雙檔同步應納入檢查)

新用戶安裝從此零 secret 前置。
測試 187/188(唯一 fail 為 pre-existing,stash 驗證與本次無關);
cypher-executor 與 cli typecheck 全綠。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:32:48 +08:00

84 lines
4.7 KiB
Markdown
Raw Permalink 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.
# 技術棧硬限制
## 三層語言對應(絕對不可混用)
| 層級 | 語言 | 位置 | 職責 |
|-----|------|------|------|
| 零件(Component | **TinyGo 或 AssemblyScript → WASM** | `registry/components/{name}/` | 所有業務邏輯 |
| 零件 Worker 包裝 | TypeScript(固定模板,不寫業務邏輯) | `.component-builds/{name}/` | WASI shimstdin/stdout JSON |
| Orchestration Worker | TypeScript + Hono | `cypher-executor/` | HTTP routing、workflow 執行排程、host functions |
| CLI | TypeScript + Node.js | `cli/` | `acr` 指令 |
| Python SDK | Python | `python-sdk/` | HTTP thin wrapper + client 端加密 |
| JS SDK | TypeScript + Web Crypto | `js-sdk/` | HTTP thin wrapper + client 端加密 |
| Frontend | React 19 + Vite + Tailwind v4 | `landing/` | Cloudflare Pages |
## 零件實作規範
### 只有兩種合法語言
- **TinyGo**`tinygo build -target=wasi -o {name}.wasm main.go`
- **AssemblyScript**`asc main.ts --target release -o {name}.wasm`
### I/O 模型
- **stdin**JSON input
- **stdout**JSON output
- 不用 HTTP server,不監聽 socketWASI preview1 沒 socket
### Host Functions(零件呼叫外部能力的唯一管道)
`u6u` namespace 下:
| Host Function | 用途 |
|---|---|
| `u6u.http_request` | 發 HTTP 請求 |
| `u6u.kv_get` | 讀 Cloudflare KVWorker 側依 key 前綴路由到正確 KV) |
| `u6u.secret_get` | 讀 CF Workers Secrets(只放行 `CRED_` 前綴) |
| `u6u.crypto_decrypt` | ⚠️ 已廢除,保留成永遠回失敗的 stub(現役 wasm 仍宣告此 import |
| `u6u.crypto_sign_rs256` | RSA-SHA256 簽章(PKCS8 bytes 傳入) |
**所有 host function 在 `cypher-executor/src/lib/wasi-shim.ts` 實作**。零件透過 WASI import 使用。
## 資料儲存
| 儲存 | 用途 | Key 格式 |
|-----|------|---------|
| Cloudflare KV `WEBHOOKS` | workflow 定義(cypher binding YAML | `webhook:{api_key}:{name}` |
| Cloudflare KV `CREDENTIALS_KV` | 加密 credential | `{api_key}:cred:{name}` |
| Cloudflare KV `RECIPES` | auth recipe / 動態 API recipe | `auth_recipe:{service}`, `rec_{hash}` |
| Cloudflare KV `USERS_KV` | 用戶資料 | `user:{api_key}` |
| Cloudflare KV `SESSIONS_KV` | session | `session:{token}` |
| Cloudflare KV `ANALYTICS_KV` | 執行分析 | `execution:{timestamp}:{id}` |
| Cloudflare KV `EXEC_CONTEXT` | workflow 執行中的 context | `ctx:{execution_id}:{node_id}` |
| Cloudflare R2 `WASM_BUCKET` | **只用於用戶自製零件**Phase 5 以後啟用) | `{api_key}:cmp:{hash}` |
**警告:R2 不存平台內建零件的 WASM**。平台零件已 bundle 進各自的 Worker binary`[[wasm_modules]]``import ... assert { type: 'webassembly' }`)。
## Credential 儲存規範
**arcrun 不自管加密金鑰。** credential 明文由 **Cloudflare Workers Secrets** 託管
per-script secret,掛在用戶自己的 cypher worker 上):
- secret 名稱 = `CRED_{NAME}_{sha256(api_key)[:8]}`(跨租戶命名隔離)
- D1 `credentials` 表只存**目錄**api_key / name / service / secret_ref),不存值
- D19:擁有目錄,不擁有內容物——連 owner 都讀不回,只能覆寫/刪除
- 需要 worker 設 `CF_SECRETS_API_TOKEN`(機密,用戶手動 put+ `CF_ACCOUNT_ID`(自動注入)
> 註:`crypto_decrypt` host function 是**永遠回失敗的 stub**,因為現役三個 `auth_*`
> `.wasm` 仍宣告該 import(缺項會讓 WASM instantiate 失敗)。三個零件重編後即可刪除。
- **取用位置**Server 端 **WASM primitive**(透過 host function `secret_get(ref)`
- cypher-executor TS 不碰業務邏輯,只提供 host function
- `secret_get` 只放行 `CRED_` 前綴,WASM 讀不到 worker 本身的其他機密
- **傳輸格式**:明文值走 TLS`POST /credentials`),不做 client 端加密
## 網路部署
- **平台 API(對外)**`cypher.arcrun.dev`cypher-executor
- **Landing**`arcrun.dev`
- **每個零件 Worker**
- **對內(cypher-executor 用來 fetch component,避開同 zone 死鎖)**`arcrun-{kebab}.{WORKER_SUBDOMAIN}.workers.dev`
- 例:`arcrun-kbdb-get.uncle6-me.workers.dev`
- cypher-executor 從 `wrangler.toml [vars] WORKER_SUBDOMAIN` 組出此 URL
- **對外(可選,零件對全網開放被 curl 用)**:`{kebab}.arcrun.dev`
- 例:`gmail.arcrun.dev``kbdb-get.arcrun.dev`
- 仍允許保留,但**禁止 cypher-executor 透過此 URL fetch**(會撞同 zone 自循環,見 [docs/incidents/2026-05-13-cypher-outbound-522.md](../../docs/incidents/2026-05-13-cypher-outbound-522.md)
- **新增 component worker 部署清單**`name = "arcrun-{kebab}"` + `[[routes]]` 對外(可選)+ dashboard 啟用 workers.dev(必須)
- **部署工具**Wrangler