Memories / ドキュメント

このページの内容

認可 Center

セルフサービスアカウントと 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] を省略します。設定変更後は再起動してください。モードを変更しても、どちらの種類の既存アカウントもログインを続けられます。

公開エンドポイント(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} で有効期間 1 時間のスコープ付き Bearer 許可を返す
POST /v1/auth/logout Bearer 認証。この期限付き許可とその子孫を失効させる

メールアドレスとユーザー名は前後の空白を除去し、ASCII の大文字小文字を正規化します。メール登録は ASCII のメールボックスアドレスのみ受け付けます。ユーザー名は 3-64 文字の ASCII 英数字、ハイフン、アンダースコアです。パスワードは UTF-8 で 12-256 バイトで、ソルト付き Argon2id PHC レコード(64 MiB、3 パス、並列度 1)として保存します。ハッシュ処理は vault のロック外で実行し、同時実行ワーカー数は最大 2 です。登録時にユーザーが vault、スコープ、ロールを選ぶことはできません。それぞれにランダムで独立したホスト型 vault が割り当てられます。

確認コードは 256 ビットの乱数を含み、有効期間は 15 分、使い捨てです。サーバーメモリに保持するのはハッシュのみで、再起動すると保留中のコードは無効になります。この期間内に送信を繰り返しても、追加のメールを送らず同じ汎用的な受付結果を返します。期限切れ後に再試行してください。パスワードはメール要求時ではなく確認時に選びます。保留中の登録の上限は 1024 です。 任意の locale はメールの件名と本文を選択します。対応値は en、zh-CN、zh-TW、ja、fr、de、es です。地域別の値は基本言語を使用します。zh-Hant、zh-HK、zh-MO は繁体字中国語を選び、明示的な zh-Hans は簡体字中国語を選びます。指定がない場合や非対応のロケールの場合は、既存 API クライアント向けに英語へフォールバックします。ロケールはリクエストごとの指定で、アカウントには保存しません。確認待ちの間に言語を変えても追加メールは送らず、レート制限も回避できません。ID の正規化や確認処理も変わりません。 認証試行には全体で 60 回/分の上限があり、ID ごとに 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 設定は不要です。既存のメンバー/許可による認可は引き続き適用されます。設定と制限についてはガイドを参照してください。

ルール

  1. エンドポイントは config.toml([[centers]])で 1 つ以上の Center を設定できます。
  2. Center が 1 つでも設定されている場合、そのエンドポイントは Center 管理下になります。 - ローカルでの自己ブートストラップは禁止です(memories init / restore)。 - デバイスの作成には Center が署名したブートストラップチケットを使用します(memories center enroll)。 - ローカルでの MCP 許可の発行は禁止です。代わりに Center 署名付き許可チケットをインポートします。
  3. デバイス vault の暗号ロック解除は引き続きローカルで行います(デバイスパスワード)。チケット専用 Center がデバイス vault の鍵や平文のシークレットを受け取ることはありません。任意のホストモードでは、代わりに運用者が鍵を管理する別個のサーバー vault を所有します(後述)。任意の署名付きロック解除チケットは、ローカルパスワードによるロック解除を認可します。チケットにパスワードやデバイス鍵は含まれません。
  4. 複数の Center を列挙できます。チケットは発行元 Center の id と public_key に照らして検証する必要があります。
  5. リッスントークン、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 読み取り + 作成/更新 + 通常のメモリー/カードの削除

注意事項:

  • Center が狭いスコープのエージェントを発行できるよう、write は意図的に書き込み専用です。エージェントに検索や読み取りも必要な場合は read_write を推奨します。
  • 削除は、MCP 呼び出し元に対して適用される通常の削除(非プライベートのメモリー/カード)を対象とします。プライベート項目/資格情報の削除には引き続きローカルでの昇格が必要で、MCP 許可の対象外です。
  • 古い write: bool フィールドしか持たない 旧形式チケット(scope なし)では、write: false → read、write: true → read_write になります。
  • 明示的なスコープがない新しいチケットは、発行時に既定で read になります(CLI / memories-center HTTP)。
  • これらの通常アセット/昇格の制限は デーモン MCP に関するもので、ホスト型 vault RPC の説明ではありません。ホスト型の許可はプライベートカード、添付ファイル、保護されたプロファイルも対象とします。正確な権限は後述します。この機能によりデーモン MCP が昇格することは、スコープが allow_all の場合も含めてありません。

ローカルチケットのインポートでは、書き込み権限だけでなく Admin/Owner メンバーシップとロール/スコープ上限を確認するようになり、ターミナルでの昇格も不要になりました。ローカル委任、要求、アクセス、監査の動作と Center/CLI/MCP の段階的な展開については、vault の認可を参照してください。

脅威モデルとの対応

懸念 動作
デバイス登録 Store を初期化できるデバイスを Center が決定
MCP / エージェントの許可 Center がスコープ付きの期限付き許可チケットを発行し、エンドポイントがインポート
デバイス vault の機密性 引き続き Argon2 + ローカル Store 暗号化。チケット専用 Center はポリシーを担い、鍵のエスクローは行わない
ホスト型 vault の機密性 サーバー側で暗号化した Store。ホスト運用者がパスワードを保持し、ホストされた全平文を読める。エンドツーエンド暗号化ではない
Center 署名鍵の侵害 攻撃者はその Center id でデバイス登録/許可発行が可能。鍵をローテーションし、エンドポイントから古い public_key を削除する
Center 管理者の侵害 期限付きデバイスロック解除チケットの発行、ホスト型 Store のロック、ホスト型メンバーの発行/更新/削除が可能。ローカルパスワードなしにチケット専用デバイス Store を復号することはできない
エンドポイントの侵害 ロック解除後は vault のデータを保持。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 のうち正確に 1 つを mode = "saas" にすると、並行するローカル vault の代わりに、そのホスト型コンテンツバックエンドを使えます。

[[centers]]
id = "corp"
url = "https://center.example.com"
public_key = "…"
mode = "saas"

デーモンの環境に MEMORIES_CENTER_TOKEN="…" を注入します。これはホスト型メンバー、静的許可、期限付きアクセスの Bearer であり、管理者トークンではありません。この設定で memories daemon を起動します。デーモンのコンテンツ要求は、ローカル書き込みロックの外で、コアの TLS/HTTP クライアントによりその Center の /v1/rpc に送信されます。非対応のホスト型操作は安全側に失敗し、ローカルストレージへフォールバックしません。バインドされた vault 名(例: 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 を有効にはしません。公開する場合は、TLS リバースプロキシの背後で Center をループバックまたは保護された内部アドレスにバインドし、クライアントにはプロキシの公開 https:// URL を設定してください。管理者トークンと署名シークレットを保護し、平文 HTTP リスナーを直接公開しないでください。

  • GET /health
  • GET /v1/public-key
  • POST /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 を明示的に要求してください。

完全なホスト型 vault(SaaS)

--data DIR(または MEMORIES_CENTER_DATA)でホスト型 vault を有効にします。サービスマネージャーの保護された環境/シークレット注入を通じて、空でない 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 を再利用します。次を含むホスト型 vault のコンテンツ用に Center + デーモンを置き換えられます。

  • メモリー/カードの全フィールド: 本文、タグ、ソース、別名、メモ、カテゴリ、プライベートフラグ、リンク、許可ホスト、シークレット参照。
  • Brain、persona、workflow、workflow-template カード。保護された account、env、token、login、TOTP/HOTP、otpauth プロファイル。
  • 署名付きリビジョン DAG、履歴、差分、過去の読み取りと再適用。同一 vault 内のカードリンクとグラフ。
  • Persona のワールドスナップショット、ロールオーバー、アーカイブカード、thing/属性、チャット会話/既読カーソル、自動化フック、許可リスト、トリガー。
  • 暗号化されたファイルと資格情報の添付、保護されたシークレットの置換、明示的な資格情報配信、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 の保存はトランザクションとして行い、vault の識別情報、署名付きリビジョン、監査状態を一緒に永続化します。同時に Store を所有できる Center プロセスは 1 つです。設定済みまたは発行済みの許可が使う既定 vault と名前付き vault は必要に応じて作成します。許可のバインドには内部的に正規 vault ID を使い、他の vault の列挙/アクセスはできません。Unix のディレクトリは所有者専用(0700)、Store ファイルと旧形式バックアップは所有者専用(0600)です。どのプラットフォームでもディレクトリとサービスアカウントを保護してください。

DIR/memories.json が存在する場合、起動時に通常のノートを ID と vault 名を維持して単一のアトミックなコンテンツトランザクションでインポートし、入力を memories.json.bak に改名します。不正なレコードがあると、ソースを破棄したり一部のノートを公開したりせず、起動に失敗します。改名が中断した後の再試行では同一のインポート済みレコードをスキップします。競合するレコードや既存バックアップがある場合は、データを上書きせず運用者が確認できるよう安全側に失敗します。旧形式バックアップは依然として平文です。インポートを確認してバックアップを手配した後、安全に削除するか独立して暗号化してください。新たに書き込むコンテンツを JSON で保存することはありません。

暗号化と脅威モデル

Store はプロセス起動時に運用者が保持するパスワードでロック解除します(Argon2 によるパスワードラッピングと、ローカル Store と同じ認証付き暗号)。認可されたコンテンツ操作ごとに、ホスト型ディスパッチはアイドル期限を確認する前にサーバーセッションを更新するため、15 分間操作がなくてもアイドルロックしません。終了と管理者によるロックは、メモリ内の Store 鍵とキャッシュされたコンテンツを解放します。管理者のロック/解除は独立した管理用 HTTP ルートにあり、許可を使うコンテンツ RPC にはありません。ホスト型の sudo やパスワード変更 RPC はありません。

これはサーバー側の暗号化であり、クライアント間のエンドツーエンド暗号化ではありません。 ホスト/プロセス/パスワードを管理する運用者は、プライベートコンテンツや資格情報を含むすべてのホスト型 vault を読み取れます。パスワードとディスクコピーの両方を持つ人は Store を復号できます。ホストメモリ、クラッシュダンプ、環境の検査、悪意ある認可済みクライアント、侵害された TLS 終端は、この機密性の境界の外です。private は引き続き安全な要約/取得処理を制御しますが、その vault の読み取りを認可されたホスト型許可に対する防壁にはなりません。

一方、デーモンはデバイスパスワードを使い、ローカル OS で封印された無人運用鍵を使う場合があります。ホストモードには封印されたデバイス鍵やデバイスごとのパスワード UI はありません。デバイス vault の鍵がこのホスト型 Store に自動的にアップロードされたり共有されたりすることはありません。前述の TLS リバースプロキシを使ってください。HTTP 要求と認可されたシークレット配信は、Center と TLS 終端では平文です。Bearer トークン、RPC 本文、資格情報レスポンス、パスワードをログに記録しないでください。

リモートのロック/解除

ホスト型ルートには管理者 Bearer が必要で、--data がある場合にのみ存在します。

ルート 動作
GET /v1/admin/status シークレットを含めず、unlocked、ホストモード、メンバー数、restart_required を返す
POST /v1/admin/lock Store の鍵/状態を破棄し、キャッシュを消去して解除まで許可を停止
POST /v1/admin/unlock {"password":"…"} を受け付け、運用者パスワードで同じ既存 Store を再度開く

ロック中はコンテンツルートとホスト型アクセストークン発行が 503 を返し、どの許可も平文を提供できません。管理ステータス、メンバーの一覧/削除、デバイスチケット発行は利用できます。メンバー作成/スコープ/vault 更新にはロック解除された 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 は選択されたリモートデーモンや Center HTTP API ではなく、OS 認証付きのローカル IPC を常に使います。デバイスパスワードの自動配信、エスクロー、プッシュチャネルはありません。

# 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 終端へ送ります。チケットモードではデバイスパスワードと vault 鍵はデバイス/ローカルデーモン内に留まります。侵害された Center は解除チケットを発行できますが、暗号文を解除するには依然としてデバイスパスワードが必要です。侵害されたホスト型管理者は Store をロックでき、解除中には平文への権限を持つメンバーを発行できます。ホスト運用者はもともとプロセスを制御し、解除されたすべての平文を読めます。管理者/メンバーの分離は運用者に対する暗号化ではありません。

永続メンバー

ホスト型メンバーは、同じ 5 段階の GrantScope を持つ、名前付きの永続 Bearer プリンシパルであり、ID プロバイダーのアカウントではありません。任意の 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 id、vault、scope、role、created、updated を持つ members 配列。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 で同じ許可テーブルを通じて認証されます。スコープ/vault の変更は再起動なしで後続要求に適用されます。認証後にメンバーシップが変更された要求は、コンテンツのディスパッチ前に拒否されます。メンバーの削除/再作成がトークンローテーションの手順です。

DIR/members.json は所有者専用のホスト型ディレクトリ内にある、アトミックに置換される所有者専用(Unix では 0600)の永続ファイルです。Bearer トークンが平文で含まれますが、パスワード、鍵、vault コンテンツは含みません。資格情報として保護し、保護されたバックアップに含め、コミットしないでください。起動時にメンバーを再ロード/検証し、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

これは論理的な vault ごとの許可バインドを持つ、運用者が単一の鍵を管理する Store のままです。厳密なマルチテナント分離、OIDC/SSO、ユーザーパスワード、リフレッシュトークン、クォータ、セルフサービス復旧ではありません。

許可と API

静的許可には、繰り返し指定できる --grant TOKEN[:SCOPE[:VAULT]] またはカンマ区切りの MEMORIES_CENTER_GRANTS、あるいは両方を使います。スコープ省略時は read、vault 省略時は default です(TOKEN::VAULT は既定スコープで vault を選びます)。有効期間はプロセスの存続期間です。管理者認証済みの POST /v1/tickets/mcp-grant は、変更のない署名付きチケットに加えて、その vault/スコープにバインドしたホスト型 access_token を返します。このアクセストークンは minutes(1–60)後に期限切れとなり、ホスト型プロセスの再起動後も存続します。チケットの ttl は別途、チケットのインポート有効期間を制御します。発行されたホスト型許可は所有者専用(0600)の DIR/grants.json にアトミックに永続化します。保存するのは SHA-256 Bearer ハッシュと認可メタデータのみで、発行トークンの平文は保存しません。Bearer ハッシュは定数時間で比較します。失効状態は永続化され、再起動後もメンバー削除は復元された許可の祖先関係を通じて連鎖します。期限切れの許可とその子孫は、ロード時、毎分、認証/発行時に除去します。所有者が管理するこの認可ファイルとバックアップはセキュリティ上重要なものとして扱ってください。このファイルまたは Store をロールバックすると古い状態が復元される可能性があります。永続化に失敗するとサービスをロックし、修復/再起動が必要になります。

ホスト型 Store をロックすると鍵を破棄して許可を利用不能にしますが、失効はさせません。期限内の発行済み許可、静的許可、永続メンバーは解除後に再開します。各永続メンバーには永続化された世代カウンターがあります。発行者と受領者の世代は発行時に固定され、認証によって更新されることはありません。公開 generation フィールドは受領者の発行時の世代です。スコープ/ロール/vault の更新や削除ではそのメンバーの世代のみ増やします。削除済みメンバーのカウンターも保持するため、ID を再作成してもリセットされません。削除/更新は、そのメンバーが発行した許可または受け取った許可と、推移的にすべての子孫を明示的に失効させます。無関係の許可は再起動後も利用できます。静的/永続資格情報は認証時ではなく明示的なポリシー遷移時に再確立します。静的プリンシパルと永続メンバーの親 ID は再起動をまたいで安定しており、復元された委任チェーンの接続を維持します。

管理者 Bearer 単独では、読み取りも含め /v1/memories と /v1/rpc で 401 になります。vault コンテンツを直接変更できません。管理者はチケットまたは認可 API を通じて別の許可を発行できます。その管理機能は、管理者に対する機密性の防壁ではありません。

認可 HTTP API

これらのルートはホストモードにのみ存在します。運用者の管理者 Bearer(すべての vault の Owner)またはホスト型プリンシパルの Bearer で認証します。運用者はサーバーのロック/解除/メンバー CRUD を制御します。ホスト型 Admin/Owner が認可を管理できるのは Bearer の vault とスコープ内だけです。ターミナルでの昇格は不要です。Reader と Editor は、より広いコンテンツアクセスが付与されていても発行や委任はできません。ロール/スコープ/vault/期限への違反は明確な権限エラーを伴う 403、ハンドラーの検証失敗は 400、対象がない場合は 404、繰り返しの決定は 409、期限切れ/失効/古い世代の Bearer は 401 を返します。不正な JSON や型が異なる 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 は vault 内の発行済み許可をすべて、他の現行プリンシパルは自身が発行した許可のみ参照可能。静的/永続資格情報と期限切れ/古い許可を除いた grants 配列
DELETE /v1/grants/{id} 発行者または vault 内の 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 と、一度限りの token を持つ新規発行 grant
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 スコープを超えたり、自分の vault を離れたり、親の期限を延長したりできません。受領者の既定値は発行したメンバーです。明示する grantee 名は選択 vault の既存プリンシパルである必要があります。予約済みの center-admin 受領者は常に HTTP 403 で拒否し、その主体を持つ過去の永続化済み許可も利用できません。受領者なしの運用者発行(ホスト型チケット発行を含む)は、主体が center-admin ではなく許可 ID である匿名プリンシパルを作成します。読み取り専用許可でも、受領者に発行者の実効ロールを超えるロールを付与できません。

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 の受領者、指定受領者による権限の決定/取消/受領、2 段の許可を超える委任は拒否します。運用者発行の主体は 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} のプリンシパル/アクセス表示とは異なります。静的/永続資格情報は後者にのみ表示され、削除には DELETE /v1/grants/{id} ではなく設定/メンバー CRUD を使います。発行者は自身の許可を列挙できます。許可を受け取るだけでは、管理者でない受領者から参照可能にはなりません。ホスト型 Bearer は自分の vault に制限されます。vault の省略時はその vault を選びますが、運用者は vault をまたいで列挙できます。memories center grant list [VAULT] も同じ発行済み許可レスポンスを返します。

approvers が空または省略された要求は、資格のある人の保留一覧を通じてブロードキャストします。空でないリストには、指定する Admin/Owner 承認者 ID(または center-admin)を含めます。承認と拒否の両方が、承認者のスコープ、vault、ロール、残り許可期間に制限されます。要求者は Admin/Owner や指定承認者でも自身の要求を承認/拒否できませんが、取り消すことはできます。決定は同じ認可ロックの下で直列化し、一度しか行えません。要求状態は pending、approved、rejected、cancelled です。 承認は承認済み許可パラメーターと承認者のスコープ/vault/ロール/期限の範囲を記録しますが、何も発行せずトークンも返しません。要求メンバーは永続メンバー資格情報を使って POST /v1/requests/{id}/claim または memories center request claim ID を呼び出します。承認者、他のメンバー、同じ要求者主体用の発行済み許可は拒否します。受領は直列化され、成功するのは 1 回のみで、以後の試行は再起動後も含めて 409 を返します。 許可の世代と作成/期限の時計は受領時に始まり、有効期限は記録された承認権限の期限を上限とします。期限切れ/失効した親や、発行者/要求者の世代変更は受領を妨げます。許可メタデータとともに永続化するのはトークンハッシュのみです。request.claim は要求者アクターと許可 ID を監査記録し、トークンは記録しません。一度限りのレスポンスを失った場合は新たな承認を要求してください。承認済みコンテンツ許可は、要求者のメンバーロールや委任権限を変更しません。

破壊的変更: 承認レスポンスには grant やそのトークンが含まれなくなりました。クライアントは要求者メンバー Bearer で明示的に受領する必要があります。要求一覧にはトークンではなく承認済み未受領のメタデータを表示します。遅延承認の範囲情報がない旧形式の承認済み要求は受領できません。新しい要求を作成してください。

Center の監査ファイルと要求ファイル

DIR/requests.json はモード 0600 でアトミックに置換し、要求メタデータと完了した決定を再起動をまたいで保存します。DIR/audit.jsonl はモード 0600 の追記モードで開き、各エントリー後に fsync し、Center が書き換えることはありません。両方ともモード 0700 のホスト型ディレクトリ内に置きます。理由は監査メタデータです。パスワードなどのシークレットを含めないでください。監査レコードには Bearer トークン、パスワード、鍵、vault コンテンツを含めません。

監査エントリーは 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 行を検証します。起動時にファイルを検証して要求の決定と受領を照合するため、決定や受領の監査後、要求メタデータ置換前にクラッシュしても繰り返しは許可されません。追記と読み取りは、現在のプロセス内の先頭に対してチェーンを検証します。監査の読み取りは vault フィルターを適用する前に全チェーンを検証します。

アクションは 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 を使います。フィルター済みエクスポートは元のハッシュ/シーケンスを維持し、独立した完全なチェーンではありません。vault にバインドされた Admin/Owner 呼び出し元に見えるのは自分の vault のエントリーのみです。運用者はグローバルイベントも参照でき、完全なログをエクスポートできます。監査追記/要求永続化の失敗はサービスをロックし、restart_required を設定して、修復と再起動までリモート解除を無効にします。restart_required の設定中、認可の変更と権限の決定は 503 を返しますが、運用者の読み取り専用検査は利用できます。これらのファイルはソース管理ではなく保護されたバックアップに保存してください。

これは鍵のないチェーンであり、デバイス署名や外部アンカーではありません。侵害された運用者は書き換え/再計算、イベントの省略、全ファイルの一括ロールバックが可能です。起動前に末尾を完全に切り詰めた場合、外部の既知の先頭がなければ検出できません。チェーン検証を独立したセキュリティ監査として扱わないでください。

ホスト型コンテンツのスコープと API

ホスト型コンテンツのスコープは単なる HTTP メソッドではなく操作に適用されます。認可管理ルートでは、前述のとおりプリンシパルのロールも確認します。コンテンツの scope だけでホスト管理権限を得ることはありません。

スコープ バインドされた vault 内のホスト型権限
read 安全な一覧/検索/グラフ、認可された完全な読み取り、ファイル読み取り、履歴/差分/監査、明示的な credential_copy
write コンテンツ作成/更新、保護されたプロファイル/OTP 登録、ファイル/資格情報、ワールド/チャット/自動化の書き込み。既存コンテンツの読み取りと削除は不可
read_write 両方。さらに otp_code(HOTP は状態を進めるため読み取り + 書き込みが必要)
read_write_delete 両方に加えてアセット、thing、属性、自動化フックの削除
allow_all コンテンツ専用のホスト型インターフェース内で上記すべて。サーバー/デバイス管理ではない

デーモン MCP とは異なり、ホスト型ディスパッチは、閉じた操作許可リスト、スコープ、vault バインドの検査に合格した後にのみ操作内限定の昇格を提供します。デーモンの check_mcp とローカル昇格制限は変わりません。ホスト型エージェント/MCP アダプターはスコープ付き Bearer を RPC コンテンツ全体に使え、ローカルターミナルの昇格やデーモン接続は不要です。vault をまたぐ brain/archive 参照が許可を通じて他の vault のコンテンツを解決することはできません。

  • GET /v1/session → 現在の vault、ロール、スコープ、期限、読み取り/書き込み/削除機能。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: "card" を推論します。構造化/保護されたプロファイルの作成には汎用 PUT ではなく専用 RPC を使います。書き込み専用 PUT が返すのは送信されたフィールドとレコード識別情報のみで、既存のプライベートフィールドは返しません。
  • 任意の JSON vault / ?vault=… は許可の名前または正規 ID と一致する必要があります。省略時は許可の vault を選択します。DELETE /v1/memories/{id} には削除権限が必要です。
  • POST /v1/rpc はデーモンの Request JSON(op は snake_case)を受け付け、その直接の JSON 結果を返し、操作ごとに独立して検査します。レスポンスの正規アセット/リビジョン ID を使ってください。vault は許可の名前または正規 ID を受け付けます。vault なしの list は許可の vault のみを選びます。エラーは HTTP 401(不明/管理者/期限切れトークン)、403(スコープ/インターフェース/vault)、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 本文の規約を使い、第 2 の blob Store は使いません。コアのコンテンツ本文上限 16 MiB は引き続き適用され、HTTP JSON 本文の上限は 24 MiB です。protect_secret は資格情報参照をアトミックに維持します。汎用の読み取り/一覧/差分は保存された資格情報バイトを解決しません。認可された credential_copy が、意図的で明示的な平文配信エンドポイントです。

ホスト型 archive_history/archive_read は、デーモン復旧アーカイブではなく、バインドされた稼働中 vault の署名付きリビジョン(過去/削除済みリビジョンを含む)を対象にします。archive_audit はその vault の監査を対象にします。ホスト型 reapply は構造化/保護メタデータを含め、署名付きの過去または隔離されたリビジョンを新たな先頭として追加します。削除マーカーの再適用には削除権限も必要です。汎用 PUT で保護参照を新しく作るのとは異なり、vault の署名付き履歴で既に認証された参照を復元します。削除されたリンク先資格情報を自動的に復活させることはありません。必要なら、その資格情報のリビジョンを別途再適用してください。

デーモンとの意図的な違い

  • POST /v1/bootstrap と /v1/restore は常に 403 を返します。これは引き続き Center 管理下のライブラリであり、デバイスのセルフサービス登録/復元ではありません。
  • sudo、ホスト型コンテンツ内でのデバイス登録、vault ポリシーのメンバーシップ管理、許可の発行/失効コンテンツ RPC、Store 全体のバックアップ/復元 RPC、リモートセッション/プロキシ、クラウド/リレー同期、無人運用の鍵管理、実行、暗号ジョブ実行はありません。allow_all でもこれらの要求は安全側に失敗します。
  • ホスト型のロック/解除/ステータスと永続メンバー管理は、allow_all による権限ではなく、独立した管理者認可 HTTP ルートです。
  • 復旧アーカイブや vault をまたぐコンテンツ解決は公開しません。ホスト型 vault 自身のリビジョンの過去の読み取り/再適用には対応します。
  • セマンティック/RAG モデルの設定/生成とモデルインストールはホスト型エンドポイントではありません。字句 search と同期 retrieve 経路は、コアの取得ポリシー(プライベートコンテンツ、ファイル本文、資格情報本文を除外)を再利用します。
  • 自動化は明示的な RPC と既存のコンテンツトリガーイベントで実行し、バックグラウンドスケジューラーはありません。送信 Webhook はコアの完全一致 URL 許可リストを使います。Webhook を設定できるホスト型書き込み者は送信ネットワークアクセスを認可されたものとして扱い、信頼できないテナントには運用者の送信制限を適用してください。
  • SaaS 設定のデーモンと軽量 CLI ヘルパーはホスト型 /v1/rpc を使います。ネイティブモバイルは、より小さなコンテンツ許可リストを持つ別個の hosted_* FFI 経路を使います。チケット専用のローカル IPC は維持します。JSON-RPC MCP 管理ツールサーバーはありません。

Store の永続化は引き続き状態全体のトランザクション保存と直列化されたサービス操作を使います。独立したテナント鍵暗号化ではなく、本番マルチテナント分離/セキュリティ認証を主張するものでもありません。

モバイル / FFI

配布される Flutter アプリは、独自の UI で Standalone、Center、SaaS を選択します。デーモンの config.toml をロードしたり、チケット管理のローカルサービスを設定したりはしません。Center の選択はホスト型ストレージを持つセルフホストサーバーへ、SaaS は https://memories.ivjn.us へ接続します。どちらも後述のホスト型 FFI 操作を使います。

呼び出し元が共有サービスに Center を設定した場合、そのサービスはチケット管理の Init/Restore と許可制限を適用します。これはスマートフォンのモード選択とは別です。モバイル FFI は引き続き Run/CryptoJob/ローカル MCP 許可管理/remote/proxy とローカルの許可/権限要求 RPC を拒否します。ホストモードを選んでもこのローカルインターフェースは拡張されません。

残りの作業

リモート指示の承認などの昇格ポリシーインターフェースは、将来 Center 署名付き承認チケットを受け付ける可能性があります。それまでは Center 管理下のエンドポイントでも、MCP 許可発行とブートストラップ以外の vault 昇格操作にはローカル 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 状態になります。アクター system の 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 を許可します。サーバーは引き続きメンバーの vault、ロール、スコープを適用します。この 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)

ホスト型 vault はサーバーが読み取り可能です。Center が決して見るべきでないシークレットには、デバイスをペアリングして封印エンベロープ(crypto::Envelope: X25519 + 送信者の Ed25519 鍵による署名)を配信します。Center が保存するのは公開鍵と暗号文のみです。

ペアリングの流れ

  1. CLI: memories device hosted init --backend <keychain|secret-service|dpapi|owner-file> はデバイス ID を作成します(32 バイトのシードは OS 管理下に保持し、X25519 + Ed25519 を導出)。
  2. 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 は {"type":"memories-pair","v":1,"pairing_id","fingerprint","center","code"} のターミナル QR を stderr に出力します。ワンタイムコードを含むためスキャンだけで十分で、stdout JSON は変わりません。既に有効な鍵の再ペアリングは 409 を返します。失効後は同じ鍵を再びペアリングできます。
  3. スマートフォン(vault の管理者)が QR をスキャン(またはコードを入力)→ GET /v1/devices/pairings/{code}。Memories ペアリング QR ではない場合、center が接続先 Center と異なる場合、フィンガープリントが取得したデバイスと異なる場合は拒否します。不明、期限切れ、使用済みのコードはすべて 404 を返します。コードは使い捨てで期限があり、検索失敗が 10 回/分になると 429 です。デバイスフィンガープリントを表示します。
  4. スマートフォンは POST /v1/requests/{id}/approve と本文 {"attestation": DeviceAttestation} で承認します。スマートフォンの Ed25519 鍵で、デバイスフィンガープリント、両デバイス鍵、ペアリング ID、承認者、スコープ、期限に署名します。
  5. CLI: memories device hosted claim --request-id ID(一度のみ)。デバイススコープの許可トークンは OS 管理下に保存し、Center はハッシュのみを保持します。
  6. 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 = u64_be(SHA-256("memories/device-sas/v1\0" ‖ phone_ed25519_pub ‖ "\0" ‖ cli_ed25519_pub ‖ "\0" ‖ pairing_id)[0..8]) mod 10^6 の先頭 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 はスマートフォン ID を保持しません。作成時、スマートフォンは書き留めるための一度限りの復旧コード(32 バイトのシード + 2 バイトのチェックサムの Base32、4 文字ごとに区切る)を表示します。復旧コードから復元(hosted_phone_restore)は同じ ID を再構築します。
  • ID のローテーション。 新しいスマートフォン鍵へ移行(hosted_identity_rotate)は新しい ID を作成し、旧鍵と新鍵の両方で署名したレコードを 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)からログが伸びていることを検査します。書き換えられた履歴はエラーとして報告します。