授權中心
自助帳號與 SMTP
在託管模式中傳入 --auth-config /etc/memories-center/auth.toml(或設定 MEMORIES_CENTER_AUTH_CONFIG)。請參閱 deploy/center/auth.example.toml。未提供驗證設定時,Center 會保留僅使用權杖的行為。提供設定後會啟用帳號登入;註冊預設為停用,註冊模式預設為電子郵件。
registration_enabled = true
registration_mode = "email" # or "username"
max_accounts = 10000
[smtp]
host = "smtp.example.com"
port = 587
tls = "starttls" # required STARTTLS; "tls" for implicit TLS, commonly port 465
from = "Memories <noreply@example.com>"
username = "smtp-user"
password_env = "MEMORIES_CENTER_SMTP_PASSWORD"
SMTP 密碼會從指定的環境變數讀取。請將該變數放在受保護的服務環境中,絕不可放在引數、URL、版本控制或瀏覽器設定中。不需要驗證的中繼服務可以省略 username;仍必須使用通過憑證驗證的 TLS。無效的設定會阻止啟動。缺少 SMTP 設定會停用電子郵件註冊;傳輸失敗會回傳 503,且不會建立帳號。公開設定的回應絕不包含 SMTP 設定。
設定 registration_enabled = false 可關閉新註冊,而不會停用既有帳號。設定 registration_mode = "username" 可使用不需要電子郵件的使用者名稱/密碼註冊;若不使用 SMTP,可省略 [smtp]。變更設定後請重新啟動。無論原本是哪一種帳號,在模式變更後都仍可登入。
公開端點(JSON,要求本文上限為 4 KiB):
| 端點 | 本文/行為 |
|---|---|
GET /v1/auth/config |
登入是否可用、註冊是否可用及模式、密碼最短長度、工作階段有效時間 |
POST /v1/auth/register |
電子郵件模式:{identity,locale?} 寄送驗證碼。使用者名稱模式:{identity,password} 建立帳號 |
POST /v1/auth/verify |
{identity,code,password} 證明信箱所有權並建立帳號 |
POST /v1/auth/login |
{identity,password} 回傳有效時間為一小時、具指定範圍的 bearer 授權 |
POST /v1/auth/logout |
使用 bearer 驗證;撤銷該具有期限的授權及其衍生授權 |
電子郵件地址與使用者名稱會去除前後空白,並以 ASCII 大小寫摺疊處理。電子郵件註冊只接受 ASCII 信箱地址。使用者名稱須為 3–64 個 ASCII 字母、數字、連字號或底線。密碼須為 12–256 個 UTF-8 位元組,並以加鹽的 Argon2id PHC 紀錄儲存(64 MiB、3 次迭代、平行度 1)。雜湊作業在保管庫鎖定範圍之外執行,最多同時執行兩個工作。使用者在註冊時不能選擇保管庫、範圍或角色:每位使用者都會取得隨機、獨立的託管保管庫。
驗證碼包含 256 個隨機位元,15 分鐘後到期,且只能使用一次。伺服器記憶體中只保留雜湊值;重新啟動會使尚待使用的驗證碼失效。在該期間內重複要求寄送時,會回傳相同的一般接受回應,不會再次寄送電子郵件;請在到期後重試。密碼是在驗證時選定,而非要求寄送電子郵件時。待處理註冊數上限為 1024。選用的 locale 可指定電子郵件的主旨與本文:en、zh-CN、zh-TW、ja、fr、de 或 es。區域變體使用其基礎語言;zh-Hant、zh-HK 與 zh-MO 選擇繁體中文,而明確指定的 zh-Hans 選擇簡體中文。對既有 API 用戶端而言,未提供或不支援的語系會回退至英文。語系依每次要求決定,不會儲存在帳號中。在驗證挑戰尚未完成時變更語言,不會再寄送電子郵件,也不會繞過速率限制。這也不會改變身分正規化或驗證行為。驗證嘗試的全域上限為每分鐘 60 次,而每個身分在每 15 分鐘內最多可提出 3 次註冊要求,或進行 10 次登入/驗證嘗試。這些僅作用於目前程序的限制會在重新啟動時重設,遭受攻擊時可能造成阻斷服務;較高流量的部署應使用上游濫用防護服務。這些限制不信任用戶端提供的 IP 標頭。
仍支援既有的持久成員權杖。members.json 格式現在包含選用的帳號憑證:請將其備份視為密碼資料庫加以保護。舊版 Center 組建無法讀取帳號紀錄;註冊後若要回復舊版,必須使用相容的執行檔,或相符的升級前資料備份(這會捨棄之後產生的資料)。目前尚未實作密碼重設、信箱變更或電子郵件復原。管理員刪除成員會停用登入並使工作階段失效。
Center 是外部授權控制平面,負責向 Memories 端點(CLI、桌面背景服務、行動裝置,以及其他共用設定的用戶端)發送註冊與能力票證。
託管 Center 也支援 Memories Web,這是用來操作記憶、卡片與檔案的瀏覽器用戶端。新組建會在 /app/ 提供此用戶端。若要從公開網站連線,請明確允許 --web-origin https://memories.ivjn.us(或 MEMORIES_CENTER_WEB_ORIGINS)。同源使用不需要 CORS 設定;仍適用既有的成員/授權驗證。設定方式與限制請參閱該指南。
規則
- 端點可以在
config.toml中設定一個或多個 Center([[centers]])。 - 只要設定了任何一個 Center,該端點就是由 Center 管理:
- 禁止在本機自行初始化(
memories init/restore)。 - 建立裝置須使用 Center 簽署的初始化票證(memories center enroll)。 - 禁止在本機發行 MCP 授權;應改為匯入 Center 簽署的授權票證。 - 裝置保管庫的密碼學解鎖仍在本機進行(使用裝置密碼)。僅提供票證的 Center 永遠不會收到裝置保管庫金鑰或明文機密。選用的託管模式則擁有另一個由營運者持有金鑰的伺服器保管庫(見下文)。選用的已簽署解鎖票證可授權本機密碼解鎖;它不包含密碼或裝置金鑰。
- 可以列出多個 Center;票證必須通過發行該票證的 Center
id與public_key驗證。 - 監聽權杖、Center 簽署機密與管理員權杖絕不儲存在 TOML 中。
授權範圍
MCP 授權票證包含明確的範圍(權限等級)。傳輸/設定識別名稱使用 snake_case;CLI 與 HTTP 也接受較易閱讀的別名。
| 範圍 | 別名(範例) | 允許的操作 |
|---|---|---|
allow_all |
allow all, all, full |
此授權介面可授權的完整操作集(目前為一般資產的讀取、建立/更新及刪除;保留供未來同一介面上由 Center 控管的提升權限能力使用) |
read |
r |
列出/取得/搜尋/檢索/RAG 讀取/一般圖譜,不允許修改 |
write |
w |
僅建立/更新一般記憶與卡片(不含讀取,不含刪除) |
read_write |
read+write, rw |
讀取加上建立/更新 |
read_write_delete |
read+write+delete, rwd |
讀取加上建立/更新與刪除一般記憶/卡片 |
注意事項:
write刻意設計為僅可寫入,讓 Center 能為代理程式發行範圍較窄的授權。若代理程式也需要搜尋或讀取,請優先使用read_write。- 刪除涵蓋對 MCP 呼叫端強制執行的一般(非私密、記憶/卡片)刪除;私密/憑證刪除仍需要本機提升權限,且不在 MCP 授權範圍內。
- 舊版票證只有舊的
write: bool欄位(沒有scope):write: false→read,write: true→read_write。 - 沒有明確範圍的新票證在發行時預設為
read(CLI/memories-centerHTTP)。 - 這些一般資產/提升權限限制描述的是背景服務 MCP,而非託管保管庫 RPC。託管授權也涵蓋私密卡片、附件與受保護設定檔;其確切權限會在下文說明。此功能不會提升背景服務 MCP 的權限,即使其範圍為
allow_all也一樣。
本機票證匯入現在會檢查 Admin/Owner 成員資格以及角色/範圍上限,而不只是寫入權限,且不需要終端機提升權限。本機委派、要求、存取與稽核行為,以及分階段推出的 Center/CLI/MCP 功能,請參閱保管庫授權。
威脅模型的適用性
| 關注事項 | 行為 |
|---|---|
| 裝置註冊 | Center 決定哪些裝置可以初始化儲存區 |
| MCP/代理程式授權 | Center 發行具有範圍與期限的授權票證,由端點匯入 |
| 裝置保管庫機密性 | 仍使用 Argon2 與本機儲存區加密;僅提供票證的 Center 是政策控制方,不是金鑰託管方 |
| 託管保管庫機密性 | 伺服器端加密的 Store;主機營運者持有密碼,可讀取所有託管明文;並非端對端加密 |
| Center 簽署金鑰遭入侵 | 攻擊者可為該 Center id 註冊裝置/發行授權;請輪替金鑰,並從端點移除過時的 public_key |
| Center 管理員遭入侵 | 可發行具有期限的裝置解鎖票證、鎖定託管 Store,以及發行/更新/移除託管成員;沒有本機密碼就無法解密僅使用票證的裝置 Store |
| 端點遭入侵 | 解鎖後持有保管庫資料;Center 無法撤銷已存於磁碟的密文(與目前行為相同) |
端點設定
[[centers]]
id = "corp"
url = "https://center.example.com"
public_key = "…" # from `memories center public-key` or memories-center /v1/public-key
mode = "ticket" # default: enrollment/grants/unlock tickets only
[[centers]]
id = "team"
url = "http://127.0.0.1:9191" # loopback http allowed for local Center
public_key = "…"
非回送位址的 Center URL 必須使用 https://。SaaS 附加連線/直接 CLI 使用的 URL 必須只有來源,不得包含使用者資訊、路徑、查詢或片段;票證模式設定則保留既有的 URL 相容性。
SaaS 用戶端附加連線
將已設定的 Center 中恰好一個設為 mode = "saas",即可使用其託管內容後端,取代並行的本機保管庫:
[[centers]]
id = "corp"
url = "https://center.example.com"
public_key = "…"
mode = "saas"
將 MEMORIES_CENTER_TOKEN="…" 注入背景服務環境:這應是託管成員、靜態授權或具有期限的存取 bearer,絕不可使用管理員權杖。使用該設定啟動 memories daemon。背景服務的內容要求會透過核心 TLS/HTTP 用戶端傳送至該 Center 的 /v1/rpc,並在本機寫入鎖定範圍之外執行。不支援的託管操作會採取封閉失敗,不會回退至本機儲存。請使用繫結的保管庫名稱(例如 default)以及託管 RPC 回傳的標準資產 ID。本機狀態/Center 清單仍在本機處理。仍禁止初始化、還原、本機 MCP 發行與本機註冊;舊的本機資料庫不是此附加連線的內容後端。內容與權限屬於託管 Center 的授權/成員。其他僅提供票證的 Center 可以並存。
已設定的背景服務 URL 是權威來源(不可透過環境變數覆寫 URL)。背景服務 bearer 由通過驗證的背景服務用戶端共用;請保護 IPC 存取及所有背景服務監聽/中繼權杖,將其視為該成員權限的委派。本機 MCP 能力不會隨背景服務 bearer 轉送;請改用具有獨立範圍的託管成員,以及直接 HTTP/MCP 配接器。此功能不會新增 Center 管理員 MCP 工具。
直接 CLI HTTP 呼叫的 URL 選擇順序為 --center-url、MEMORIES_CENTER_URL,最後是已設定的 SaaS Center。管理員呼叫需要 MEMORIES_CENTER_ADMIN_TOKEN;內容 RPC 需要 MEMORIES_CENTER_TOKEN。這些 bearer 權杖都不應放在 TOML 中。TLS 設定沿用 --ca-file/--insecure;正式環境中請勿停用 TLS 驗證。不會跟隨重新導向。直接呼叫輔助工具不需要正在執行的背景服務:
export MEMORIES_CENTER_URL="https://center.example.com"
export MEMORIES_CENTER_TOKEN="…"
# request.json contains an ordinary hosted Request, e.g. {"op":"list","vault":"default"}
memories center rpc --request request.json
CLI
# Admin: derive public key for config.toml
export MEMORIES_CENTER_SECRET="…" # 32-byte URL-safe base64 or 64 hex
memories center public-key
# Admin: issue tickets (or use memories-center HTTP service)
memories center issue-bootstrap --center corp --device-name laptop
memories center issue-grant --center corp --vault VAULT_ID --minutes 15
memories center issue-grant --center corp --vault VAULT_ID --scope read --minutes 15
memories center issue-grant --center corp --vault VAULT_ID --scope write --minutes 15
memories center issue-grant --center corp --vault VAULT_ID --scope read_write --minutes 15
memories center issue-grant --center corp --vault VAULT_ID --scope 'read+write+delete' --minutes 15
memories center issue-grant --center corp --vault VAULT_ID --scope allow_all --minutes 15
# Legacy: --write alone ⇒ read_write (when --scope omitted)
# Endpoint (daemon already running with centers in config)
memories center list
memories center enroll --ticket "$BOOTSTRAP_TICKET"
memories sudo
memories center import-grant --ticket "$GRANT_TICKET"
本機(非 Center)MCP 授權接受相同範圍:
memories mcp grant VAULT_ID ./grant.token --scope read --minutes 15
memories mcp grant VAULT_ID ./grant.token --scope read_write_delete --minutes 15
# Legacy: --write ⇒ read_write
Center HTTP 服務(memories-center)
export MEMORIES_CENTER_SECRET="…"
export MEMORIES_CENTER_ADMIN_TOKEN="…"
memories-center --listen http://127.0.0.1:9191 --center-id corp
Center 透過 TCP 提供純 HTTP。--listen https://HOST:PORT 只會選擇繫結位址,不會啟用原生 TLS。若要公開服務,請將 Center 繫結至回送位址或受保護的內部位址,放在 TLS 反向代理之後,並讓用戶端使用代理的公開 https:// URL。請保護管理員權杖與簽署機密;不要直接公開純 HTTP 監聽器。
GET /healthGET /v1/public-keyPOST /v1/tickets/bootstrap(Bearer 管理員權杖)POST /v1/tickets/mcp-grant(Bearer 管理員權杖)POST /v1/tickets/unlock(Bearer 管理員權杖;{"ttl":300},1–3600 秒)
授權本文(JSON):
{
"vault": "…",
"scope": "read_write",
"minutes": 30,
"ttl": 3600
}
scope 接受與 CLI 相同的傳輸名稱與別名。省略 scope 時,write: true 對應至 read_write;否則預設為 read。靜態託管授權可省略範圍(TOKEN 或 TOKEN::VAULT),而建立成員時若省略範圍,也預設為 read。請明確要求 allow_all,不要依賴過去較寬鬆的預設值。
完整託管保管庫(SaaS)
使用 --data DIR(或 MEMORIES_CENTER_DATA)啟用託管保管庫。透過服務管理員受保護的環境/機密注入提供非空的 MEMORIES_CENTER_VAULT_PASSWORD。此處刻意不提供密碼命令列旗標。只有使用 --data 時才需要密碼;遺失密碼就會失去 Store 存取權。請使用獨立產生的強式密碼,不要使用 bearer 權杖或簽署金鑰。
# Inject MEMORIES_CENTER_SECRET, MEMORIES_CENTER_ADMIN_TOKEN,
# MEMORIES_CENTER_VAULT_PASSWORD and MEMORIES_CENTER_GRANTS securely first.
memories-center --listen http://127.0.0.1:9191 --center-id corp --data ./center-data
若從原始碼簽出目錄執行,請在選項前加上 cargo run -p memories-center --。沒有 --data 時,程序仍是向下相容、僅提供票證的 Center。兩種模式都仍可使用票證與註冊。
託管模式重複使用 memories_core::service::Service、Request、Content,以及背景服務的加密 SQLite Store。針對託管保管庫內容,它可以取代 Center 加上背景服務的組合,包括:
- 完整的記憶/卡片欄位:本文、標籤、來源、別名、備註、分類、私密旗標、連結、允許的主機及機密參照。
- Brain、persona、workflow 與 workflow-template 卡片;受保護的 account、env、token、login、TOTP/HOTP 與
otpauth設定檔。 - 已簽署的修訂 DAG、歷程、差異、歷史讀取與重新套用;同一保管庫內的卡片連結與圖譜。
- Persona 世界快照、轉存、封存卡片、事物/屬性、聊天對話/讀取游標、自動化掛鉤、允許清單及觸發器。
- 加密的檔案與憑證附件、受保護機密替換、明確的憑證傳送及 OTP 產生。
儲存與移轉
DIR/
store/
memories.db # Same encrypted Store format as the daemon
memories.lock # Exclusive process lock
memories.json.bak # Only if imported from the old notes library
SQLite 也可能建立其一般交易日誌檔案。Store 儲存具有交易性;保管庫身分、已簽署修訂與稽核狀態會一起持久保存。同一時間只有一個 Center 程序擁有 Store。已設定或已發行授權使用的預設保管庫及具名保管庫會依需要建立。授權繫結在內部使用標準保管庫 ID,不能列舉/存取其他保管庫。Unix 目錄僅限擁有者存取(0700);Store 檔案與舊版備份也僅限擁有者存取(0600)。每個平台都應保護目錄與服務帳號。
若存在 DIR/memories.json,啟動時會在單一不可分割的內容交易中匯入其一般筆記,保留 ID 與保管庫名稱,接著將輸入檔重新命名為 memories.json.bak。無效紀錄會使啟動失敗,但不會捨棄來源或發布不完整的筆記批次。重新命名中斷後重試時,會略過完全相同的已匯入紀錄;若有衝突紀錄或已存在的備份,則會採取封閉失敗,交由營運者檢查,而不會覆寫資料。舊版備份仍是明文:確認匯入成功並安排備份後,請安全移除它,或另行加密。新寫入的內容絕不會以 JSON 儲存。
加密與威脅模型
Store 在程序啟動時使用營運者持有的密碼解鎖(使用 Argon2 密碼包裝,並使用與本機 Store 相同的驗證式加密)。每次已授權的內容操作中,託管派送都會在閒置到期檢查之前更新伺服器工作階段;不會在閒置十五分鐘後自動鎖定。關閉程序與管理員鎖定會釋放記憶體中的 Store 金鑰及快取內容。管理員鎖定/解鎖使用獨立的管理員 HTTP 路由,絕不透過授權內容 RPC。沒有託管 sudo 或密碼變更 RPC。
這是伺服器端加密,不是用戶端端對端加密。掌控主機/程序/密碼的營運者可以讀取每個託管保管庫,包括私密內容與憑證。任何同時持有密碼與磁碟副本的人都能解密 Store。主機記憶體、損毀傾印、環境檢查、惡意的已授權用戶端,以及遭入侵的 TLS 終止端,都不在此機密性邊界之內。private 仍控制安全摘要/檢索,但無法阻擋已獲授權讀取該保管庫的託管授權。
背景服務則使用裝置密碼,並且可能使用由本機作業系統封存的無人值守金鑰。託管模式沒有封存裝置金鑰或各裝置密碼操作介面。裝置保管庫金鑰不會自動上傳或與此託管 Store 共用。請使用上述 TLS 反向代理;對 Center 與 TLS 終止端而言,HTTP 要求與已授權的機密傳送都是明文。不要記錄 bearer 權杖、RPC 本文、憑證回應或密碼。
遠端鎖定/解鎖
託管路由需要管理員 bearer,且只有使用 --data 時才存在:
| 路由 | 行為 |
|---|---|
GET /v1/admin/status |
回傳 unlocked、託管模式、成員數量與 restart_required,不包含機密 |
POST /v1/admin/lock |
清除 Store 金鑰/狀態、清除快取,並暫停授權直到解鎖 |
POST /v1/admin/unlock |
接受 {"password":"…"},並使用營運者密碼重新開啟相同的既有 Store |
鎖定期間,內容路由與託管存取權杖發行會回傳 503;沒有任何授權可以提供明文。管理員狀態、成員列出/移除及裝置票證發行仍可使用。建立成員或更新範圍/保管庫需要已解鎖的 Store。錯誤的託管密碼會回傳 403;驗證失敗會採用核心指數退避(退避期間的後續嘗試會回傳 429)。成功解鎖會恢復靜態/成員存取,不會恢復已撤銷的發行權杖。在鎖定前已完成的要求,其回傳的明文無法收回。啟動時仍需要 MEMORIES_CENTER_VAULT_PASSWORD;遠端解鎖不會保存提供的密碼,也不會變更啟動設定。
export MEMORIES_CENTER_ADMIN_TOKEN="…"
memories center status --center-url https://center.example.com
memories center lock --center-url https://center.example.com
memories center unlock --center-url https://center.example.com # password prompt
裝置解鎖有所不同:POST /v1/tickets/unlock 在僅提供票證與託管 Center 中都可使用。管理員簽署短效的 unlock 票證;本機背景服務驗證其種類、簽章、已設定的 Center 與到期時間,再使用營運者透過本機提示輸入的裝置密碼解鎖。這不會授予 sudo/MCP 提升權限。memories center unlock --ticket 一律使用由作業系統驗證的本機 IPC,不會使用選定的遠端背景服務或 Center HTTP API。沒有自動裝置密碼傳送、託管或推播通道。
# Admin: save the returned ticket securely; do not put it in logs.
memories center issue-unlock --center-url https://center.example.com --ttl 300
# On the device (daemon running with the same --home/config):
memories center unlock --ticket "…" # local device password prompt
解鎖票證在到期前可重複使用,並可授權信任該 Center 的端點解鎖,但仍受各端點密碼限制;票證並非單次使用,也未繫結至特定裝置。僅使用票證的受管裝置仍可使用既有的本機密碼解鎖。SaaS 附加連線不能使用票證來註冊或解鎖並行的本機內容 Store。
密碼保管:託管營運者持有託管 Store 密碼,且只在託管解鎖時將其傳送至受信任的 Center/TLS 終止端。在票證模式中,裝置密碼與保管庫金鑰都留在裝置/本機背景服務上。遭入侵的 Center 可以發行解鎖票證,但仍需要裝置密碼才能解鎖其密文。遭入侵的託管管理員可以鎖定 Store,並在 Store 解鎖時發行具有明文存取權的成員。託管營運者本來就掌控程序,可以讀取所有已解鎖明文;管理員/成員區隔不構成對營運者的加密保護。
持久成員
託管成員是具名、持久的 bearer 主體,使用相同的五級 GrantScope,並非身分提供者帳號。選用的 role 為 reader、editor、admin 或 owner;若省略角色,會保留舊有對應,依序由 read、write/read_write、read_write_delete 與 allow_all 映射。明確指定的角色會限制成員範圍上限(Reader:read,Editor:read_write,Admin:read_write_delete,Owner:allow_all)。成員 CRUD 路由仍需要營運者管理員 bearer,而非成員的 Admin/Owner 角色:
| 路由 | 本文/回應 |
|---|---|
GET /v1/admin/members |
members 陣列,包含 id、vault、scope、role、created 與 updated;不含 bearer 權杖 |
POST /v1/admin/members |
{"id":"agent","vault":"default","scope":"read"};中繼資料加上產生的 token,只會在此回應中顯示 |
PUT /v1/admin/members/{id} |
選用的 scope、vault 及/或 role;使用相同 bearer,回應只有中繼資料 |
DELETE /v1/admin/members/{id} |
移除成員並立即使 bearer 失效 |
ID 是穩定、長度 1–64 個字元的名稱(ASCII 字母/數字、-、_);center-admin 保留給營運者 Owner。重複 ID 會回傳 409。權杖是隨機且唯一的,並與管理員/靜態權杖不同。建立成員時請使用受保護的終端機/輸出目的地;CLI 依設計只會輸出一次回傳的 bearer。絕不可將建立回應擷取至日誌。成員在 /v1/memories 與 /v1/rpc 透過相同授權表驗證;範圍/保管庫變更會套用至後續要求,不需要重新啟動。若要求完成驗證後發生成員資格變更,則會在內容派送前拒絕該要求。移除並重新建立成員是權杖輪替程序。
DIR/members.json 是位於僅限擁有者存取的託管目錄下、以不可分割方式替換的持久檔案,且僅限擁有者存取(Unix 上為 0600)。它包含明文 bearer 權杖,但不含密碼、金鑰或保管庫內容;請像保護憑證一樣保護它,將其納入受保護備份,且不要提交至版本控制。啟動時會重新載入/驗證成員,遇到損毀或衝突的 bearer 時會採取封閉失敗。持久成員儲存失敗會鎖定內容並設定 restart_required:在營運者修復檔案系統並重新啟動以重新載入持久授權之前,遠端解鎖會回傳 503。這可防止不確定的持久保存結果使已撤銷的 bearer 再度生效。
memories center member list --center-url https://center.example.com
memories center member add --center-url https://center.example.com --id agent --vault default --scope read
memories center member set --center-url https://center.example.com --id agent --scope read_write
memories center member remove --center-url https://center.example.com --id agent
這仍是單一由營運者持有金鑰的 Store,使用邏輯上的每個保管庫授權繫結,不是強式多租戶隔離、OIDC/SSO、使用者密碼、更新權杖、配額或自助復原。
授權與 API
靜態授權使用可重複指定的 --grant TOKEN[:SCOPE[:VAULT]] 及/或以逗號分隔的 MEMORIES_CENTER_GRANTS;省略範圍表示 read,省略保管庫表示 default(TOKEN::VAULT 會選擇保管庫並使用預設範圍)。其有效時間等於程序生命週期。經管理員驗證的 POST /v1/tickets/mcp-grant 會回傳未變更的已簽署票證,以及繫結至其保管庫/範圍的託管 access_token。該存取權杖在 minutes(1–60)指定的分鐘數後到期,且在託管服務重新啟動後仍保留。票證的 ttl 則獨立控制票證匯入有效時間。託管發行授權會以不可分割方式保存於僅限擁有者存取(0600)的 DIR/grants.json:只包含 SHA-256 bearer 雜湊與授權中繼資料,絕不包含明文發行權杖。Bearer 雜湊採固定時間比較。撤銷狀態會持久保存,重新啟動後移除成員仍會沿著還原的授權祖先鏈級聯撤銷。到期授權及其衍生授權會在載入時、每分鐘,以及驗證/發行期間清除。請將此由擁有者控制的授權檔案及其備份視為安全敏感資料;回復該檔案或 Store 的舊版本可能恢復舊狀態。持久保存失敗會鎖定服務,並要求修復/重新啟動。
鎖定託管 Store 會清除其金鑰,使授權無法使用,但不會撤銷授權;尚未到期的發行授權、靜態授權與持久成員會在解鎖後恢復。每個持久成員都有持久保存的世代計數器。發行者與接收者的世代在發行時固定,絕不會因驗證而更新;公開的 generation 欄位是接收者在發行時的世代。範圍/角色/保管庫更新或移除只會遞增該成員的世代;已移除成員的計數器會保留,因此重新建立相同 ID 不會使其重設。移除/更新會明確撤銷該成員發行或接收的授權,以及所有間接衍生授權;無關授權仍可使用,重新啟動後也是如此。靜態/持久憑證會在明確的政策轉換時重新建立,而非在驗證時。靜態主體與持久成員的父系 ID 在重新啟動後維持穩定,使還原的委派鏈保持連接。
在 /v1/memories 與 /v1/rpc 上,單獨使用管理員 bearer 會收到 401,即使只是讀取也一樣。它無法直接修改保管庫內容。管理員仍可透過票證或授權 API 發行獨立授權;該管理能力並不構成對管理員的機密性保護邊界。
授權 HTTP API
這些路由只存在於託管模式中。它們可驗證營運者管理員 bearer(在所有保管庫中皆為 Owner)或託管主體的 bearer。營運者控制伺服器鎖定/解鎖/成員 CRUD;託管 Admin/Owner 只能在 bearer 的保管庫與範圍內管理授權。不需要終端機提升權限。Reader 與 Editor 即使獲得較廣的內容存取權,也不能發行或委派。違反角色/範圍/保管庫/到期限制會回傳 403,並提供清楚的權限錯誤;處理常式驗證失敗回傳 400,物件不存在回傳 404,重複決定回傳 409,而到期/撤銷/世代過時的 bearer 回傳 401。格式不正確或型別錯誤的 JSON 可能由 Axum 的 JSON 擷取器以 400 或 422 拒絕。
| 路由 | 欄位與回應 |
|---|---|
GET /v1/access/{vault} |
Admin/Owner;vault、依 owner、admin、editor、reader 分組的 principals、包含到期時間的有效 grants,以及 pending_requests 與 approved_unclaimed_requests(id、requester、scope、approvers 清單或 "broadcast",以及以 Unix 秒數表示的 created 與 expires) |
POST /v1/grants |
Admin/Owner;vault、選用的 scope(預設 read)、duration_minutes(預設 30,別名 minutes)、grantee;授權中繼資料加上只顯示一次的 token |
GET /v1/grants |
選用的 vault;Admin/Owner 可看見保管庫中所有已發行授權,其他目前主體只能看見自己發行的授權;grants 陣列,不包含靜態/持久憑證及到期/過時授權 |
DELETE /v1/grants/{id} |
發行者或保管庫內的 Admin/Owner;撤銷發行授權及其衍生授權,回傳 revoked 與 count |
POST /v1/requests |
任何成員;vault、選用的 scope(預設 read)、reason、duration_minutes(預設 30,別名 duration)、approvers;待處理要求中繼資料 |
GET /v1/requests |
選用的 vault;屬於要求者或合格核准者的待處理要求,加上要求者已核准但未領取的要求;不含權杖 |
POST /v1/requests/{id}/approve |
合格的 Admin/Owner;不需本文;已核准但未領取的 request 中繼資料及已驗證的界限,不含授權或權杖 |
POST /v1/requests/{id}/claim |
只接受要求者的持久成員 bearer;不需本文;request 加上新發行的 grant,其中包含只顯示一次的 token |
POST /v1/requests/{id}/reject |
合格的 Admin/Owner;選用的 JSON reason;狀態為 rejected 的 request |
POST /v1/requests/{id}/cancel |
只限要求者;不需本文;狀態為 cancelled 的 request |
GET /v1/audit |
Admin/Owner;選用的 vault、format=json|jsonl(預設 json);已驗證的 entries/verified,或以換行分隔的匯出資料 |
所有發行/要求的有效時間皆為 1–60 分鐘。同時最多可有 10,000 個有效授權(memories_core::service::MAX_ACTIVE_GRANTS);本機背景服務使用相同上限。發行者不能超出角色上限或 bearer 範圍、離開其保管庫,或延長父授權的到期時間。接收者預設為成員發行者;明確指定的 grantee 名稱必須是選定保管庫中的既有主體。保留的 center-admin 接收者一律以 HTTP 403 拒絕;先前持久保存、使用該主體的授權也無法使用。營運者未指定接收者的發行(包括託管票證發行)會建立匿名主體,其 subject 為授權 ID,而非 center-admin。任何接收者的角色都不能高於發行者的有效角色,即使授權僅供讀取也一樣。
Center 與背景服務皆定義 pub const MAX_DELEGATION_DEPTH: u32 = 2。成員/營運者直接發行的深度為 1;使用授權發行時為 parent.depth + 1,深度 3 會回傳 HTTP 403。持久成員憑證屬於直接成員權限(內部深度 0);其發行的授權深度為 1,沒有父授權。沒有 depth 的持久舊版授權會依完整父系鏈重建,包括已設定的靜態與持久憑證。祖先鏈缺失、循環或超出上限的授權無法使用,且會在啟動時清除。
subject != issued_by 的授權權限屬於指定的授權呼叫端。這類呼叫端不能核准、拒絕、取消或領取任何權限要求,即使是自己的要求也不行(HTTP 403);它們也不是合格核准者。它們可以建立要求,但必須使用真正的成員憑證來進行權限工作流程。委派發行會保留真正的發行者,使指定 bearer 無法藉由委派發行將自己偽裝成自行發行的授權。自行發行授權仍沿用既有要求規則,領取仍需要要求者的持久成員 bearer。透過指定接收者授權作成的舊版延後核准,不能授權領取;請向合格的成員權限取得新核准。
重大變更:會拒絕較高角色與 center-admin 接收者、指定接收者的權限決定/取消/領取,以及超過兩層授權的委派。營運者發行的主體現在是匿名主體,而非 center-admin;成員發行的根授權不再將持久成員憑證列為委派父項。請重新發行適當的根授權或使用成員憑證,不要依賴這些過去的行為。
授權記錄 id(獨立的不透明 ID)、subject、scope、vault、以 Unix 秒數表示的 expiry、parent、issued_by、generation、depth 與 role。靜態主體 ID 為 static:OPAQUE_ID。GET /v1/grants 只回傳已發行且具有期限的授權,包含 id、parent、issued_by、grantee、scope、vault、以 Unix 秒數表示的 expiry、generation、depth,以及可用時以 Unix 秒數表示的 created。絕不回傳權杖或 bearer 雜湊。grantee 是接收者(在發行/存取回應中為 subject)。世代仍是發行時的世代。此已發行授權檢視不同於未變更的 GET /v1/access/{vault} 主體/存取檢視:靜態與持久憑證只會出現在後者,並透過設定/成員 CRUD 移除,而非 DELETE /v1/grants/{id}。發行者可列出自己的授權;非管理員接收者不會僅因收到授權就能在清單中看見它。託管 bearer 只能存取其保管庫;省略 vault 時會選擇該保管庫,而營運者可跨保管庫列出。memories center grant list [VAULT] 會回傳相同的已發行授權回應。
approvers 為空或省略的要求會透過合格的待處理清單廣播;非空清單包含具名 Admin/Owner 核准者 ID(或 center-admin)。核准與拒絕都受核准者的範圍、保管庫、角色及剩餘授權有效時間限制。要求者不能核准或拒絕自己的要求,即使具有 Admin/Owner 角色或被列為具名核准者也一樣;仍可取消自己的要求。決定在相同授權鎖下序列化處理,且只能作成一次。要求狀態為 pending、approved、rejected 或 cancelled。核准會記錄已核准的授權參數,以及核准者的範圍/保管庫/角色/到期界限,但不會發行任何東西,也不會回傳權杖。提出要求的成員使用持久成員憑證呼叫 POST /v1/requests/{id}/claim,或 memories center request claim ID。核准者、其他成員,以及具有相同要求者主體的發行授權都會遭到拒絕。領取會序列化處理:只有一次領取會成功,之後的嘗試會回傳 409,重新啟動後也是如此。授權的世代與建立/到期計時在領取時開始;其到期時間受已記錄核准權限的到期時間限制。到期/撤銷的父授權,以及已變更的發行者/要求者世代,都會阻止領取。只有權杖雜湊會與授權中繼資料一起持久保存。request.claim 會稽核要求者 actor 與授權 ID,絕不記錄權杖。若遺失只顯示一次的回應,請要求新的核准。已核准的內容授權不會變更要求者的成員角色或委派權限。
重大變更:核准回應不再包含 grant 或其權杖;用戶端必須使用要求者成員 bearer 明確領取。要求清單只顯示已核准但未領取的中繼資料,不顯示權杖。缺少延後核准界限的舊版已核准要求無法領取;請改為建立新要求。
Center 稽核與要求檔案
DIR/requests.json 以 0600 模式不可分割地替換,並跨重新啟動儲存要求中繼資料與已完成決定。DIR/audit.jsonl 以 0600 模式開啟並附加寫入,每筆項目後執行 fsync,Center 絕不重寫此檔案。兩者都位於模式為 0700 的託管目錄下。理由屬於稽核中繼資料:不要在其中放入密碼或其他機密。稽核紀錄不包含 bearer 權杖、密碼、金鑰或保管庫內容。
稽核項目公開 sequence、timestamp、prev_hash、hash、action,以及授權驗證操作的選用 issued_by/via_grant。前者是真正的發行者,後者是呼叫端的授權 ID;actor 仍為授權主體,但 grant.issue 例外,它會將真正發行者記為 actor,並將接收者記為 subject。這些欄位預設不存在,且不存在時會省略,因此舊稽核日誌保留原有雜湊。共用 Store 的已簽署託管內容稽核也會記錄這些欄位。既有欄位包括 actor、subject、scope、vault、expiry、parent、reason、request_id 與 object。第一筆 prev_hash 為 null;之後的值參照前一筆雜湊。SHA-256 涵蓋 JSON 元組 (sequence, timestamp, prev_hash, event_fields)。crates/center/src/saas/audit.rs 中的 verify_audit_chain(path) 驗證連續性、雜湊與完整 JSONL 行。啟動時會驗證檔案,並依它調和要求決定與領取狀態,因此即使在稽核決定或領取之後、替換要求中繼資料之前程序當機,也不會允許重複執行。附加與讀取會依目前程序內的鏈首驗證鏈;稽核讀取會先驗證整條鏈,再套用保管庫篩選。
操作包括 device.authorize(初始化票證發行)、grant.issue、grant.revoke、member.add、member.set、member.remove、session.lock、session.unlock、request.create、request.approve、request.claim、request.reject 與 request.cancel。鎖定與成員變更造成的撤銷也會稽核。JSONL 匯出使用 Content-Type: application/x-ndjson;篩選後的匯出會保留原始雜湊/序號,不是獨立完整的鏈。繫結至保管庫的 Admin/Owner 呼叫端只能看見其保管庫的項目;營運者也能看見全域事件,並可匯出完整日誌。任何稽核附加/要求持久保存失敗都會鎖定服務、設定 restart_required,並停用遠端解鎖直到修復及重新啟動。設定 restart_required 期間,授權修改與權限決定會回傳 503;營運者仍可進行唯讀檢查。請將這些檔案保存在受保護備份中,不要納入版本控制。
這是不使用金鑰的鏈,不是裝置簽章或外部錨點:遭入侵的營運者可以重寫/重新計算它、略去事件,或將所有檔案一起回復至舊版本。若沒有外部已知鏈首,無法偵測啟動前整段尾端被截除。不要將鏈驗證視為獨立安全稽核。
託管內容範圍與 API
託管內容範圍適用於操作,而不只是 HTTP 方法。授權管理路由還會依上述規則檢查主體角色;僅有內容 scope 絕不會授予主機管理權限:
| 範圍 | 在繫結保管庫中的託管權限 |
|---|---|
read |
安全列出/搜尋/圖譜;完整的已授權讀取、檔案讀取、歷程/差異/稽核,以及明確的 credential_copy |
write |
內容建立/更新、受保護/OTP 註冊、檔案/憑證、世界/聊天/自動化寫入;不含既有內容讀取或刪除 |
read_write |
兩者皆有;另含 otp_code(因 HOTP 會推進狀態,因此需要讀取加上寫入) |
read_write_delete |
兩者皆有,另加資產、事物、屬性與自動化掛鉤刪除 |
allow_all |
僅在託管內容介面內具有上述所有權限,不含伺服器/裝置管理 |
與背景服務 MCP 不同,託管派送只有在封閉的操作允許清單、範圍與保管庫繫結檢查通過後,才提供限於該操作的提升權限。背景服務的 check_mcp 與其本機提升權限限制維持不變。託管代理程式/MCP 配接器可以在完整 RPC 內容介面上使用具指定範圍的 bearer;不需要本機終端機提升權限或背景服務連線。跨保管庫 brain/archive 參照無法透過授權解析另一個保管庫的內容。
GET /v1/session→ 目前保管庫、角色、範圍、到期時間與讀取/寫入/刪除能力,不含 bearer 資料。僅供寫入的權杖可檢查其工作階段;營運者管理員權杖則不行。請參閱行動裝置託管模式。GET /v1/memories→{"memories":[...]}。一般非私密筆記紀錄保留id、vault、title、body;卡片/私密/檔案/憑證紀錄使用安全摘要,絕不包含憑證本文或已解析的機密。GET /v1/memories/{id}→ 完整的安全用戶端內容加上id、vault。憑證需要明確使用credential_copy,不能使用一般讀取。POST /v1/memories與PUT /v1/memories/{id}接受title/body加上完整Content中繼資料。PUT 保留省略的欄位。建立時加入卡片中繼資料,且未提供 kind,會推定為kind: "card"。建立結構化/受保護設定檔應使用其專用 RPC,而非一般 PUT。僅供寫入的 PUT 只回傳提交的欄位與紀錄身分,不回傳既有私密欄位。- 選用的 JSON
vault/?vault=…必須符合授權名稱或標準 ID;省略時選擇授權的保管庫。DELETE /v1/memories/{id}需要刪除權限。 POST /v1/rpc接受背景服務RequestJSON(op使用 snake_case),回傳其直接 JSON 結果,並獨立檢查每個操作。請使用回應中的標準資產/修訂 ID;vault 接受授權的名稱或標準 ID。未指定vault的list只會選擇授權的保管庫。錯誤使用 HTTP 401(未知/管理員/到期權杖)、403(範圍/介面/保管庫)、404(不存在)、409(衝突)、400(無效輸入),或 422(格式不正確的 Request JSON/結構描述)。
範例(使用授權 bearer 傳送,而非管理員 bearer):
{"op":"brain_card","vault":"default","title":"Research","abstract":"Summary","links":[]}
{"op":"history","vault":"default","asset":"CANONICAL_ASSET_ID"}
{"op":"persona_world_get","vault":"default","asset":"PERSONA_ASSET_ID"}
{"op":"put","vault":"default","content":{"title":"attachment.bin","kind":"file","private":true,"body":"BASE64URL_BYTES"}}
檔案/憑證使用既有的 URL 安全 base64 本文慣例,不會另設第二個二進位物件儲存區。核心的 16 MiB 內容本文上限仍適用;HTTP JSON 本文上限為 24 MiB。protect_secret 以不可分割方式維護憑證參照。一般讀取/清單/差異絕不解析儲存的憑證位元組;已授權的 credential_copy 是刻意要求明確操作的明文傳送端點。
託管 archive_history/archive_read 處理的是繫結中的現用保管庫已簽署修訂(包括歷史/墓碑修訂),而非背景服務復原封存。archive_audit 處理該保管庫的稽核。託管 reapply 將已簽署的歷史或隔離修訂附加為新鏈首,包括結構化/受保護中繼資料;重新套用墓碑也需要刪除權限。這是還原保管庫已簽署歷程中早已通過驗證的參照,與透過一般 PUT 捏造受保護參照不同。它不會自動復活已刪除的連結憑證;若有需要,請另行重新套用該憑證修訂。
與背景服務刻意保留的差異
POST /v1/bootstrap與/v1/restore一律回傳 403:這仍是由 Center 管理的資料庫,不是裝置自助註冊/還原介面。- 不提供 sudo、託管內容內的裝置註冊、保管庫政策成員管理、授權發行/撤銷內容 RPC、全 Store 備份/還原 RPC、遠端工作階段/代理、雲端/中繼同步、無人值守保管、執行或密碼學工作執行。這類要求即使使用
allow_all也會採取封閉失敗。 - 託管鎖定/解鎖/狀態及持久成員管理是獨立、經管理員授權的 HTTP 路由,不是
allow_all所賦予的能力。 - 不公開復原封存與跨保管庫內容解析。支援託管保管庫自身修訂的歷史讀取/重新套用。
- 語意/RAG 模型設定/產生及模型安裝不是託管端點。字詞
search與同步retrieve路徑重複使用核心檢索政策(不包含私密內容、檔案本文或憑證本文)。 - 自動化會透過明確 RPC 與既有內容觸發事件執行;沒有背景排程器。對外 webhook 使用核心的精確 URL 允許清單;請將可設定 webhook 的託管寫入者視為獲授權的對外網路存取,並對不受信任的租戶強制實施營運者出口限制。
- 設為 SaaS 的背景服務與輕量 CLI 輔助工具使用託管
/v1/rpc;原生行動端使用獨立的hosted_*FFI 路徑,其內容允許清單較小。僅提供票證的本機 IPC 保持完整。沒有 JSON-RPC MCP 管理員工具伺服器。
Store 持久保存仍使用完整狀態的交易式儲存與序列化服務操作;這不是各租戶獨立金鑰加密,也不代表宣稱具有正式環境多租戶隔離/安全認證。
行動裝置/FFI
隨附的 Flutter 應用程式在自己的介面中選擇 Standalone、Center 或 SaaS。它不會載入背景服務的 config.toml,也不會設定由票證管理的本機服務。其 Center 選項連接至具有託管儲存的自架伺服器,而 SaaS 連接至 https://memories.ivjn.us。兩者都使用下述託管 FFI 操作。
當呼叫端在共用服務上設定 Center 時,共用服務會強制實施票證管理的 Init/Restore 與授權限制。這與手機的模式選擇器分開。行動 FFI 仍拒絕 Run/CryptoJob/本機 MCP 授權管理/遠端/代理及本機授權/權限要求 RPC;選擇託管模式不會擴大該本機介面。
尚待完成的工作
遠端指令核准與其他提升權限政策介面,未來可能接受 Center 簽署的核准票證。在此之前,由 Center 管理的端點,除了 MCP 授權發行與初始化之外,其他需要保管庫提升權限的操作仍使用本機 sudo。allow_all 保留供這些介面擴充,無須重新命名範圍。
權限要求限制、到期與保留
適用於背景服務(要求儲存在加密 Store 中)與 Center(requests.json):
MAX_PENDING_PER_REQUESTER_PER_VAULT = 20與MAX_PENDING_PER_VAULT = 200只計算待處理要求。已決定、已取消、已到期,以及已核准但未領取的要求不計入這些限制。(這取代了舊的全域 1000 筆要求上限,舊上限會計算所有曾經提出的要求。)若超過限制,背景服務會回傳限制錯誤,Center 則回傳 HTTP 429。PENDING_REQUEST_TTL = 7 days:超過此時間的待處理要求會轉為expired狀態。這會以 actorsystem稽核為request.expire,且無法再核准。已核准的範圍要求若在核准後 7 天內尚未領取,也會以相同方式到期,之後無法領取。REQUEST_RETENTION = 30 days:已拒絕、已取消、已到期或已領取的要求,會在最後一次狀態轉換後經過此時間刪除(使用decided時間戳記;沒有該欄位的舊紀錄則使用created)。- 到期與清除採延遲執行:背景服務在任何授權要求開始時執行,Center 則在建立、列出、決定與領取要求時執行。啟動時不會執行。
- Center 啟動調和會忽略已清除要求的稽核項目,並接受
request.expire項目。expired要求必須有已稽核的到期紀錄。要求 ID 是永不重複使用的隨機 128 位元值,因此已清除的要求(包括已領取要求)不再存在,也無法領取。因此 Center 不另行保留墓碑集合。
行動裝置託管模式(手機用戶端)
行動應用程式的 Center 與 SaaS 模式會直接連線至使用 --data DIR 執行的 Center;手機不需要背景服務的 mode = "saas" 項目。首次連線與切換請參閱行動端使用方式,設定檔移轉請參閱設定。FFI memories_call API 接受以下 hosted_* 操作:
{"op":"hosted_connect","center_url":"https://…","bearer":"…","ca_file":null,"insecure":false}→{"session":N,"url":…,"server_readable":true}{"op":"hosted_rpc","session":N,"request":{…}}使用下方的內容允許清單{"op":"hosted_status","session":N}透過GET /v1/session驗證{"op":"hosted_requests","session":N}、hosted_approve/hosted_reject(id)、hosted_access(vault){"op":"hosted_disconnect","session":N};memories_lock_all也會移除所有託管工作階段
crates/core/src/center.rs 中的 HostedClient::rpc 允許 list、read、search、delete、type_list、type_show、type_create、type_update、type_delete、card_create、card_fields、card_field、credential_set、credential_show,以及在 content.kind 為 memory、card 或 credential 時的 put。伺服器仍會套用成員的保管庫、角色與範圍。這份 FFI 允許清單不代表每個操作都有 Flutter 畫面:託管知識支援清單、伺服器搜尋、詳細內容、一般記憶/卡片編輯、建立與經確認的刪除。結構化設定檔、受保護卡片、型別範本及密封卡片需要專用編輯器。授權要求與已配對裝置控制仍可使用。
GET /v1/session 回傳 vault、vault_id、scope、role、expires_at(Unix 秒數或 null)、can_read、can_write、can_delete 與 server_readable。它接受任何有效且具指定範圍的內容權杖,包括僅供寫入的權杖,但拒絕營運者管理員權杖。它不回傳任何 bearer 或機密資料。每次內容要求仍會重新檢查授權;此回應只是 UI 快照,不是授權。請先部署此端點,再升級應用程式:沒有此端點的舊伺服器無法驗證新應用程式連線。
搜尋使用共用檢索服務,排除私密內容、憑證與檔案。未篩選的知識清單可以為已授權讀取者包含私密卡片。儲存失敗會保留草稿;刪除需要確認,且只有伺服器成功後才會移除項目。更新使用既有修訂服務,沒有不可分割的預期修訂前置條件;同時編輯可能互相覆蓋最新欄位。
託管模式可由伺服器讀取:Store 靜態儲存時已加密,但營運者持有密碼,可以讀取託管內容。應用程式會標示「伺服器可以讀取」。Rust 將每個作用中用戶端的 bearer 保存在記憶體中,並在 Debug 與錯誤中遮蔽;Flutter 將各模式的 URL/權杖組合持久保存於平台安全儲存。切換模式會結束作用中的工作階段,但保留設定檔。中斷連線會移除該設定檔已儲存的 bearer;不會撤銷伺服器權杖。進入背景時會移除託管工作階段與可見內容;返回後須明確重新連線。
裝置對裝置 E2E 機密(託管 Center)
託管保管庫可由伺服器讀取。若有 Center 絕不可看見的機密,請配對裝置並傳送密封信封(crypto::Envelope:X25519 加上寄件者 Ed25519 金鑰簽署)。Center 只儲存公開金鑰與密文。
配對流程
- CLI:
memories device hosted init --backend <keychain|secret-service|dpapi|owner-file>建立裝置身分(由作業系統保管 32 位元組種子;衍生 X25519 與 Ed25519)。 - CLI(成員權杖放在
MEMORIES_CENTER_TOKEN):memories device hosted pair --vault V→POST /v1/devices/pairings,包含兩個公開金鑰與自身簽章。Center 建立帶有裝置指紋的待處理 PermissionRequest,並回傳{code, request_id, qr: {pairing_id, fingerprint, center}}(center是 Center 的--center-id)。配對在 5 分鐘後到期。CLI 會在終端機輸出(stderr)包含{"type":"memories-pair","v":1,"pairing_id","fingerprint","center","code"}的 QR 碼,其中包含一次性代碼,因此掃描即可完成輸入;stdout JSON 保持不變。重新配對已啟用的金鑰會回傳 409;撤銷後,相同金鑰可以再次配對。 - 手機(保管庫管理員)掃描 QR 碼(或輸入代碼)→
GET /v1/devices/pairings/{code}。手機會拒絕非 Memories 配對 QR 碼、center與已連線 Center 不同,或指紋與取得裝置不符的 QR 碼。未知、到期或已使用的代碼都回傳 404。代碼僅供單次使用且會到期,每分鐘 10 次失敗查詢 → 429。顯示裝置指紋。 - 手機使用
POST /v1/requests/{id}/approve核准,本文為{"attestation": DeviceAttestation},由手機 Ed25519 金鑰對裝置指紋、兩個裝置金鑰、配對 id、核准者、範圍與到期時間簽署。 - CLI:
memories device hosted claim --request-id ID(僅一次)。裝置範圍的授權權杖由作業系統保管;Center 只保留其雜湊。 - 比對 SAS:手機在核准前顯示它(分組為
123 456);核准後,memories device hosted sas --pairing-id P會在 CLI 輸出相同數字。若數字不同,請撤銷裝置並重新配對。
行動端權限:Android CAMERA(相機功能為選用)、iOS NSCameraUsageDescription。若拒絕相機存取,手機會說明如何在系統「設定」中啟用,並提供手動輸入代碼。CLI 與手機將 Center 狀態轉換為可採取行動的訊息(404 到期/已使用/其他 Center,409 已完成或已配對,403 不允許或裝置已撤銷,422 拒絕 e2e 內容,429 達速率限制)。
SAS = 下式的前 6 位十進位數字:
u64_be(SHA-256("memories/device-sas/v1\0" ‖ phone_ed25519_pub ‖ "\0" ‖ cli_ed25519_pub ‖ "\0" ‖ pairing_id)[0..8]) mod 10^6,
不足位數以零補齊。手機從取得結果獲得 CLI 金鑰;CLI 從證明取得手機金鑰。不符表示金鑰遭替換:請立即撤銷。
規則
- 裝置授權繫結至裝置(未撤銷且已證明)與成員世代;委派深度 ≤ 2。裝置不能發行授權或決定要求。
- 遞增或移除成員會使裝置失效,並刪除其信封。
PUT|GET|DELETE /v1/sealed/{vault}/{card}/{device}、GET /v1/sealed?device=:信封僅供已證明的裝置使用,以裝置 X25519 金鑰為收件位址,且來自已知簽署者;版本必須遞增。稽核只記錄指紋。- 帶有
e2e: true的卡片會在託管 Center 上拒絕明文機密內容。 /v1/access/{vault}列出裝置。MCP:device_list、sealed_reveal(需要intent: "reveal_plaintext"與理由)。
威脅模型
Center 營運者無法讀取密封機密,但可以扣留或重播舊信封(每個裝置的版本都單調遞增),且能看見中繼資料(誰、哪張卡片、何時)。配對信任 SAS 比對與核准者的手機金鑰。撤銷無法收回已傳送至裝置的明文:撤銷後,請在上游輪替機密。
E2E 強化(P2)
- 不可任意移除的
e2e旗標。卡片一旦設為e2e: true,移除該旗標的寫入就會遭拒絕(422)。只有 Owner 授權(不能是裝置授權)可清除它,而且寫入必須不帶任何機密資料;之後的寫入才會遵循一般規則。 - OTP 參數。密封 OTP 機密可以是單純的 Base32 種子(SHA1、6 位數、30 秒),或完整的
otpauth://totp/...URI;memories device hosted sealed-get --otp會遵循 URI 中的algorithm(SHA1/SHA256/SHA512)、digits(6/8)與period(30/60)。device hosted pair也會將配對酬載以終端機 QR 碼輸出(stderr)。 - 撤銷後重新密封。撤銷(或使裝置失效)會刪除其信封,並將每張受影響的卡片標記為
needs_rotation(顯示於GET /v1/sealed?device=、GET /v1/sealed/rotation?vault=、存取清單與手機重新密封頁面)。撤銷無法收回已傳送的明文:請先在上游輪替機密,再使用手機的為所有裝置重新密封(hosted_reseal_all),它會以新版本密封給目前所有已核准裝置,並清除旗標。絕不包含已撤銷裝置。 - 手機復原。Center 永遠不持有手機身分。建立時,手機會顯示一次性復原碼(32 位元組種子加上 2 位元組檢查碼的 Base32,每四個字元一組),供使用者抄寫保存;從復原碼還原(
hosted_phone_restore)會重建相同身分。 - 身分輪替。 移至新的手機金鑰(
hosted_identity_rotate)會建立新身分,並向POST /v1/devices/identity-rotations提交由舊金鑰與新金鑰共同簽署的紀錄;只有管理員成員(不能是裝置)可以提交,而且舊金鑰必須已受某個裝置信任。CLI 會固定首次核准的手機金鑰(hosted-device-trust.json,首次使用即信任),並且只接受可透過已驗證輪替從該根金鑰到達的信封簽署者。 - 硬體支援的儲存。手機種子使用
flutter_secure_storage,搭配最強的內建選項儲存:iOS/macOS Keychain 的passcode可存取性(僅限此裝置、不同步、需要裝置密碼);Android 使用 Keystore 包裝儲存,搭配生物辨識或裝置憑證存取控制,以及resetOnError: false。限制:種子仍會匯出至應用程式記憶體以進行簽署/密封(Secure Enclave/StrongBox 無法原生執行 X25519/Ed25519),因此這保護的是靜態儲存,無法防禦已遭入侵的執行中應用程式。 - 透明度日誌。Center 在
GET /v1/devices/log維護僅可附加、以雜湊串鏈的裝置日誌(device.pair、device.approve、identity.rotate、device.revoke)。每個項目內含 CLI 配對自身簽章、手機證明或輪替紀錄。memories device hosted log --verify檢查鏈連接、每個簽章,以及日誌是否延伸自上次驗證的鏈首(hosted-device-log.json);重寫歷程會回報為錯誤。