visionA/docs/autoflow/04-architecture/adr/adr-009-token-storage.md
jim800121chen fb7da5d180 chore(autoflow): migrate .autoflow/ 共享層文件至 docs/autoflow/
依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類
共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git),
讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等
per-branch 筆記。

- 02-prd/        21 個檔(PRD、features、market-analysis 等)
- 03-design/     18 個檔(design-spec、wireframes、flows 等)
- 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等)
- 07-delivery/   3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup)

合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv,
但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
2026-05-04 16:55:55 +08:00

137 lines
6.1 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.

# ADR-009visionA Agent 的 Pairing / Session Token 儲存策略
## 狀態
Accepted — 2026-04-22
## 背景 (Context)
visionA Agent 需要持久化存放兩種 token
- **Pairing Token**`vAc_` + 32 hex使用者貼上的配對憑證15 分鐘內有效、一次性,僅短暫存在
- **Session Token**`vAs_` + 64 hex長期 90 天agent 日後重連都用它,**核心安全資產**
儲存要求:
- **機密性**token 等同連線權限,若明文保存被任何能讀檔的 malware 拿走 = 裝置被劫持
- **跨平台**macOS / Windows / Linux 都要能用
- **不依賴 root / sudo**:安裝必須是使用者權限
- **不依賴雲端**:離線時也要能讀 token 來嘗試重連
- **易於清除**:「重置所有設定」要能乾淨清除
### OS 原生方案
| 平台 | 原生方案 | 安全強度 |
|------|---------|---------|
| macOS | Keychain (Security Framework) | 高;解鎖鑰匙圈後僅當前使用者可讀 |
| Windows | Credential Manager (Wincred) / DPAPI | 高DPAPI 綁定使用者帳號 |
| Linux | Secret Service / libsecretGNOME Keyring / KWallet | 中;需要使用者 session 解鎖SSH 登入可能沒有 |
### Go 生態 keyring library
- `github.com/99designs/keyring` — 抽象 3 平台API 清爽,但有 CGO 依賴
- `github.com/zalando/go-keyring` — 純 Go但 Linux 只支援 Secret Service無 KWallet
- `github.com/keybase/go-keychain` — 只 macOS不跨平台
## 決策 (Decision)
### 雛形階段Phase 0
**採 AES-GCM encrypted file + OS machine ID 衍生 passphrase。不接任何 OS keychain**
設計:
```go
// internal/tokenstore/encrypted_file.go
type EncryptedFileStore struct {
path string // ~/Library/Application Support/visionA Agent/tokens.enc 等
passphrase []byte // sha256(machineID || app_salt)
}
// 用 AES-256-GCMnonce 隨機,認證標籤保護完整性
// 明文結構:
type tokenBlob struct {
SchemaVersion int `json:"v"`
SessionToken string `json:"session_token,omitempty"`
PairingToken string `json:"pairing_token,omitempty"`
AccountEmail string `json:"account_email,omitempty"`
LastPairedAt time.Time `json:"last_paired_at,omitempty"`
}
```
`machineID` 來源:
| 平台 | 來源 |
|------|------|
| macOS | `ioreg -rd1 -c IOPlatformExpertDevice``IOPlatformUUID` |
| Windows | Registry `HKLM\SOFTWARE\Microsoft\Cryptography``MachineGuid` |
| Linux | `/etc/machine-id``/var/lib/dbus/machine-id` |
`app_salt` 是 build 時寫進的常數(非秘密,只是讓 passphrase 不只依賴 machineID
### Phase 1
切換到 OS 原生 keychain`github.com/99designs/keyring`CGO 可接受,因為 visionA Agent 本來就要編 Wails = CGO 大戶)。
`TokenStore` interface 設計讓 Phase 1 只需換實作:
```go
type Store interface {
GetSession() (string, error)
SetSession(token string) error
GetPairing() (string, error)
SetPairing(token string) error
Delete(kind string) error // "session" | "pairing" | "all"
SetMetadata(m Metadata) error
GetMetadata() (Metadata, error)
}
```
## 考慮過的替代方案
| 方案 | 優點 | 缺點 | 排除原因 |
|------|------|------|---------|
| **明文 JSON file** | 零成本 | malware / 同機其他使用者可讀 | 不可接受 |
| **一次做完 OS keychain 3 平台(雛形)** | 最終狀態 | 跨平台測試成本高Linux Secret Service 在 headless session 可能沒 daemonCGO 編譯複雜度 | 雛形先簡化,分階段 |
| **只做 macOS keychain**(因為雛形開發環境多半是 macOS | 開發順 | Windows / Linux 仍需 fallback等於還是要 encrypted file | 不如雛形就統一 encrypted file |
| **讓使用者輸入 passphrase** | 真正的 Zero-Knowledge | UX 慘 — 每次重啟 agent 都要輸入 | 違反「開了就忘掉它」定位 |
| **雲端存 token只讓 agent 本機用 refresh token 換)** | 洩漏立刻可撤銷 | 需要 agent 本地仍存 refresh token — 問題沒消失;離線情境壞掉 | 沒解決根本問題 |
## 後果 (Consequences)
### 正面影響
- **雛形開發快**encrypted file 純 Go沒有 CGO / OS API 依賴,所有平台一視同仁
- **抽象清楚**`TokenStore` interface 讓雛形 / Phase 1 切換零程式碼影響
- **完整性保護**AES-GCM 自帶 auth tag檔案被竄改會 decrypt 失敗
- **合理的威脅模型**malware 拿到檔案還得拿到 machineID且不同機器的檔案不互通跨機器複製檔案會解不開
### 負面影響(接受的取捨)
- **machineID 可被同機 malware 讀取**:已經 escalate 到能讀 user home 的 malware 幾乎等同於整台淪陷,此時 token 其實是次要損失
- **備份難題**:使用者備份了 `~/Library/Application Support/visionA Agent/` 到新機器 → token 無法 decrypt必須重新配對這其實是**安全特性**,不是 bug
- **Phase 1 遷移需要一次性資料遷移**:升級後第一次啟動時,讀舊 encrypted file + decrypt + 寫入 keychain + 刪舊檔
### 風險
- **machineID 取得失敗**:極端情況(沙盒 / 非標準 OS→ fallback 到「隨機產生一次、寫在明文 checksum file」此時檔案被竊 + passphrase 檔被竊才破(仍比單純明文好)
- **Linux 多使用者共用機器**machineID 相同但 file 在各自 `~/.config/`OK
- **跨 Linux distroWSL2 / FlatpakmachineID 取得差異**TODO 在 Phase 0 測試三大 distroUbuntu / Fedora / Arch
## 雛形的明確限制(寫給未來)
- 雛形不做 token rotationSession Token 90 天內一直用同一個;過期才換)
- 雛形不做「kill switch」強制撤銷所有 agent— 需 Phase 1 backend 支援
- 雛形不做使用者提示「你的 token 在這個檔案裡」— 太技術、嚇人Phase 1 再評估
## 合規性
- [x] 雛形不弱於明文檔案
- [ ] Security reviewPhase 1 上線前必做
- [ ] Phase 1 遷移工具TODO
## 相關文件
- `.autoflow/04-architecture/security.md` §9Secret 管理)
- `.autoflow/04-architecture/visiona-agent-tdd.md` §9Token 儲存策略)
- ADR-003Pairing Token 兩階段)