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

6.1 KiB
Raw Permalink Blame History

ADR-009visionA Agent 的 Pairing / Session Token 儲存策略

狀態

Accepted — 2026-04-22

背景 (Context)

visionA Agent 需要持久化存放兩種 token

  • Pairing TokenvAc_ + 32 hex使用者貼上的配對憑證15 分鐘內有效、一次性,僅短暫存在
  • Session TokenvAs_ + 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

設計:

// 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 IOPlatformExpertDeviceIOPlatformUUID
Windows Registry HKLM\SOFTWARE\Microsoft\CryptographyMachineGuid
Linux /etc/machine-id/var/lib/dbus/machine-id

app_salt 是 build 時寫進的常數(非秘密,只是讓 passphrase 不只依賴 machineID

Phase 1

切換到 OS 原生 keychaingithub.com/99designs/keyringCGO 可接受,因為 visionA Agent 本來就要編 Wails = CGO 大戶)。

TokenStore interface 設計讓 Phase 1 只需換實作:

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 再評估

合規性

  • 雛形不弱於明文檔案
  • Security reviewPhase 1 上線前必做
  • Phase 1 遷移工具TODO

相關文件

  • .autoflow/04-architecture/security.md §9Secret 管理)
  • .autoflow/04-architecture/visiona-agent-tdd.md §9Token 儲存策略)
  • ADR-003Pairing Token 兩階段)