Centre d'autorisation
Comptes en libre-service et SMTP
Passez --auth-config /etc/memories-center/auth.toml (ou définissez
MEMORIES_CENTER_AUTH_CONFIG) en mode hébergé. Consultez
deploy/center/auth.example.toml.
Sans configuration d'authentification, Center conserve son fonctionnement fondé uniquement sur les jetons. Avec une configuration,
la connexion aux comptes est activée ; par défaut, l'inscription est désactivée et utilise le mode e-mail.
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"
Les mots de passe SMTP sont lus dans la variable d'environnement nommée. Conservez cette variable
dans l'environnement protégé du service, jamais dans les arguments, les URL, le contrôle de version
ou la configuration du navigateur. Un relais sans authentification peut omettre username ;
TLS avec vérification du certificat reste obligatoire. Une configuration invalide empêche
le démarrage. L'absence de SMTP désactive l'inscription par e-mail ; un échec du transport renvoie 503
et ne crée aucun compte. La réponse de configuration publique ne contient jamais les paramètres SMTP.
Définissez registration_enabled = false pour fermer les nouvelles inscriptions sans désactiver
les comptes existants. Définissez registration_mode = "username" pour une inscription par nom d'utilisateur/mot de passe
sans e-mail ; omettez [smtp] s'il n'est pas utilisé. Redémarrez après avoir modifié les paramètres.
Les comptes existants des deux types continuent à pouvoir se connecter après un changement de mode.
Points de terminaison publics (JSON, corps de requête maximal de 4 KiB) :
| Point de terminaison | Corps / comportement |
|---|---|
GET /v1/auth/config |
Disponibilité de la connexion, disponibilité/mode de l'inscription, minimum du mot de passe, durée de session |
POST /v1/auth/register |
Mode e-mail : {identity,locale?} envoie un code. Mode nom d'utilisateur : {identity,password} crée un compte |
POST /v1/auth/verify |
{identity,code,password} prouve la propriété de la boîte aux lettres et crée un compte |
POST /v1/auth/login |
{identity,password} renvoie une autorisation bearer à portée définie d'une heure |
POST /v1/auth/logout |
Autorisation bearer ; révoque cette autorisation à expiration et ses descendants |
Les espaces aux extrémités des adresses e-mail et des noms d'utilisateur sont supprimés, et leur casse ASCII est normalisée. L'inscription par e-mail accepte uniquement les adresses de boîte aux lettres ASCII. Les noms d'utilisateur comportent 3-64 lettres ASCII, chiffres, tirets ou traits de soulignement. Les mots de passe comportent 12-256 octets UTF-8 et sont stockés sous forme d'enregistrements PHC Argon2id salés (64 MiB, 3 passes, parallélisme 1). Le calcul des hachages s'exécute en dehors du verrou du coffre, avec au plus deux tâches concurrentes. Les utilisateurs ne peuvent pas choisir un coffre, une portée ou un rôle lors de l'inscription : chacun reçoit un coffre hébergé indépendant aléatoire.
Les codes de vérification contiennent 256 bits aléatoires, expirent après 15 minutes et sont
à usage unique. Seuls les hachages sont conservés dans la mémoire du serveur ; un redémarrage invalide les codes
en attente. Les envois répétés pendant cette période renvoient la même acceptation générique
sans nouvel e-mail ; réessayez après expiration. Les mots de passe sont choisis lors de la vérification,
pas lors de la demande d'e-mail. Les inscriptions en attente sont plafonnées à 1024.
Le paramètre facultatif locale sélectionne l'objet et le corps de l'e-mail : en, zh-CN, zh-TW,
ja, fr, de ou es. Les variantes régionales utilisent leur langue de base ; zh-Hant,
zh-HK et zh-MO sélectionnent le chinois traditionnel, tandis que zh-Hans explicite sélectionne
le chinois simplifié. Les paramètres régionaux absents ou non pris en charge utilisent l'anglais par défaut pour
les clients API existants. Le paramètre régional est propre à chaque requête et n'est pas stocké dans le compte.
Changer de langue pendant une vérification en cours n'envoie pas de nouvel e-mail
et ne contourne pas sa limite de fréquence. Cela ne modifie ni la normalisation de l'identité ni la vérification.
Les tentatives d'authentification ont un plafond global de 60/minute et des limites par identité de 3
demandes d'inscription ou 10 tentatives de connexion/vérification par 15 minutes. Ces
limites locales au processus sont réinitialisées au redémarrage et peuvent provoquer un déni de service en cas
d'attaque ; utilisez un service de prévention des abus en amont pour les déploiements à plus fort volume.
Elles ne font pas confiance aux en-têtes IP fournis par le client.
Les jetons de membres persistants existants restent pris en charge. Le format members.json contient désormais
des identifiants de compte facultatifs : protégez ses sauvegardes comme des bases de données de mots de passe.
Les anciennes versions de Center ne peuvent pas lire les enregistrements de compte ; un retour à une version antérieure après inscription
nécessite un binaire compatible ou une sauvegarde correspondante des données antérieures à la mise à niveau (qui
supprimerait les données ultérieures). La réinitialisation du mot de passe, les changements de boîte aux lettres et la récupération par e-mail ne sont pas
implémentés. La suppression d'un membre par un administrateur désactive la connexion et invalide les sessions.
Un Center est un plan de contrôle d'autorisation externe qui distribue des tickets d'inscription et de capacité aux points de terminaison Memories (CLI, daemon de bureau, mobile et autres clients partageant la configuration).
Les Centers hébergés prennent également en charge Memories Web, un client navigateur pour
les mémoires, les cartes et les fichiers. Les nouvelles versions le servent à /app/. Pour se connecter depuis le
site public, autorisez explicitement --web-origin https://memories.ivjn.us (ou
MEMORIES_CENTER_WEB_ORIGINS). Une utilisation de même origine ne nécessite aucun paramètre CORS ; l'autorisation
existante des membres/autorisations reste applicable. Consultez le guide pour la configuration et les limites.
Règles
- Les points de terminaison peuvent configurer un ou plusieurs Centers dans
config.toml([[centers]]). - Si un quelconque Center est configuré, le point de terminaison est géré par Center :
- L'auto-initialisation locale est interdite (
memories init/restore). - La création d'un appareil utilise un ticket d'initialisation signé par Center (memories center enroll). - L'émission locale d'autorisations MCP est interdite ; importez à la place des tickets d'autorisation signés par Center. - Le déverrouillage cryptographique du coffre de l'appareil reste local (mot de passe de l'appareil). Un Center fonctionnant uniquement par tickets ne reçoit jamais les clés du coffre de l'appareil ni les secrets en clair. Le mode hébergé facultatif possède à la place un coffre serveur distinct dont l'opérateur détient la clé (voir ci-dessous). Un ticket de déverrouillage signé facultatif autorise un déverrouillage local par mot de passe ; il ne transporte ni le mot de passe ni les clés de l'appareil.
- Plusieurs Centers peuvent être répertoriés ; un ticket doit être vérifié avec l'
idet lapublic_keydu Center qui l'a émis. - Les jetons d'écoute, secrets de signature de Center et jetons administrateur ne sont jamais stockés dans TOML.
Portées des autorisations
Les tickets d'autorisation MCP transportent une portée explicite (niveau de permission). Les identifiants du protocole / de configuration utilisent snake_case ; le CLI et HTTP acceptent également des alias plus lisibles.
| Portée | Alias (exemples) | Autorise |
|---|---|---|
allow_all |
allow all, all, full |
Ensemble complet des opérations que cette interface d'autorisation peut autoriser (aujourd'hui : lecture + création/mise à jour + suppression des ressources ordinaires ; réservé aux futures capacités élevées contrôlées par Center sur la même interface) |
read |
r |
Lister / obtenir / rechercher / récupérer / lecture RAG / graphe ordinaire — aucune mutation |
write |
w |
Création/mise à jour des mémoires et cartes ordinaires uniquement (pas de lecture, pas de suppression) |
read_write |
read+write, rw |
Lecture + création/mise à jour |
read_write_delete |
read+write+delete, rwd |
Lecture + création/mise à jour + suppression des mémoires/cartes ordinaires |
Remarques :
writeest volontairement limité à l'écriture afin que les Centers puissent émettre des autorisations d'agents à portée restreinte. Préférezread_writelorsque l'agent doit aussi rechercher ou lire.- La suppression couvre les suppressions ordinaires (non privées, mémoires/cartes) appliquées aux appelants MCP ; les suppressions de contenu privé/d'identifiants nécessitent toujours une élévation locale et restent en dehors des autorisations MCP.
- Anciens tickets ne possédant que l'ancien champ
write: bool(sansscope) :write: false→read,write: true→read_write. - Nouveaux tickets sans portée explicite : la valeur par défaut est
readà l'émission (CLI / HTTPmemories-center). - Ces restrictions relatives aux ressources ordinaires/à l'élévation décrivent le MCP du daemon, pas le RPC du
coffre hébergé. Les autorisations hébergées couvrent aussi les cartes privées, les pièces jointes et les profils protégés ;
leur autorité exacte est décrite ci-dessous. Cette fonctionnalité n'élève pas les privilèges du MCP du daemon,
même lorsque sa portée est
allow_all.
L'importation locale de tickets vérifie désormais l'appartenance Admin/Owner et le plafond de rôle/portée, pas seulement l'accès en écriture, et ne nécessite pas d'élévation du terminal. Consultez l'autorisation du coffre pour la délégation locale, les demandes, l'accès et le comportement d'audit, ainsi que le déploiement progressif Center/CLI/MCP.
Adéquation au modèle de menace
| Préoccupation | Comportement |
|---|---|
| Inscription des appareils | Center décide quels appareils peuvent initialiser un stockage |
| Autorisations MCP / agents | Center émet des tickets d'autorisation à durée limitée avec des portées ; les points de terminaison les importent |
| Confidentialité du coffre de l'appareil | Toujours Argon2 + chiffrement du stockage local ; Center fonctionnant uniquement par tickets gère la politique, pas le séquestre des clés |
| Confidentialité du coffre hébergé | Store chiffré côté serveur ; les opérateurs de l'hôte détiennent le mot de passe et peuvent lire tout le contenu hébergé en clair ; ce n'est pas du chiffrement de bout en bout |
| Clé de signature de Center compromise | L'attaquant peut inscrire des appareils / émettre des autorisations pour cet identifiant de Center — renouvelez la clé et retirez l'ancienne public_key des points de terminaison |
| Administrateur de Center compromis | Peut émettre des tickets de déverrouillage d'appareil à durée limitée, verrouiller le Store hébergé et créer/modifier/supprimer des membres hébergés ; ne peut pas déchiffrer un Store d'appareil fonctionnant uniquement par tickets sans son mot de passe local |
| Point de terminaison compromis | Détient les données du coffre après déverrouillage ; Center ne peut pas révoquer le contenu chiffré déjà sur disque (comme aujourd'hui) |
Configuration des points de terminaison
[[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 = "…"
Les URL de Center hors boucle locale doivent utiliser https://.
Les URL de rattachement SaaS/d'accès CLI direct doivent être uniquement des origines, sans informations utilisateur, chemins,
requêtes ni fragments ; la configuration en mode tickets conserve la compatibilité existante des URL.
Rattachement du client SaaS
Définissez exactement un Center configuré sur mode = "saas" pour utiliser son backend de contenu
hébergé au lieu d'un coffre local parallèle :
[[centers]]
id = "corp"
url = "https://center.example.com"
public_key = "…"
mode = "saas"
Injectez MEMORIES_CENTER_TOKEN="…" dans l'environnement du daemon : il s'agit d'un
membre hébergé, d'une autorisation statique ou d'un bearer d'accès à durée limitée, jamais du jeton administrateur.
Démarrez memories daemon avec cette configuration. Les requêtes de contenu du daemon sont envoyées au
/v1/rpc de ce Center au moyen du client TLS/HTTP du core, en dehors du verrou local
d'écriture. Les opérations hébergées non prises en charge sont refusées par défaut ; elles ne se rabattent pas sur le
stockage local. Utilisez le nom du coffre lié (par exemple default) et les identifiants canoniques des ressources
renvoyés par le RPC hébergé. L'état local/la liste des Centers restent locaux. Init, restore,
l'émission locale d'autorisations MCP et l'inscription locale restent interdits ; une ancienne base de données locale
ne constitue pas le backend de contenu de ce rattachement. Le contenu et les permissions appartiennent aux
autorisations/membres du Center hébergé. D'autres Centers fonctionnant uniquement par tickets peuvent coexister.
L'URL configurée du daemon fait autorité (aucune substitution d'URL par l'environnement). Le bearer du daemon est partagé par les clients authentifiés du daemon ; protégez l'accès IPC et tout jeton d'écoute/de relais du daemon comme une délégation de l'autorité de ce membre. Les capacités MCP locales ne sont pas transmises avec le bearer du daemon ; utilisez plutôt un membre hébergé à portée distincte avec un adaptateur HTTP/MCP direct. Aucun outil MCP d'administration de Center n'est ajouté.
Pour les appels HTTP directs du CLI, la sélection de l'URL suit --center-url, puis
MEMORIES_CENTER_URL, puis le Center SaaS configuré. Les appels administrateur nécessitent
MEMORIES_CENTER_ADMIN_TOKEN ; le RPC de contenu nécessite MEMORIES_CENTER_TOKEN.
Aucun de ces jetons bearer n'a sa place dans TOML. Les paramètres TLS réutilisent --ca-file /
--insecure ; ne désactivez pas la vérification TLS en production. Les redirections ne sont pas
suivies. L'assistant direct ne nécessite pas de daemon en cours d'exécution :
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"
Les autorisations MCP locales (sans Center) acceptent les mêmes portées :
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
Service HTTP de Center (memories-center)
export MEMORIES_CENTER_SECRET="…"
export MEMORIES_CENTER_ADMIN_TOKEN="…"
memories-center --listen http://127.0.0.1:9191 --center-id corp
Center sert du HTTP en clair sur TCP. --listen https://HOST:PORT sélectionne uniquement
l'adresse d'écoute ; cela n'active pas TLS natif. Pour une exposition publique, faites écouter
Center sur la boucle locale ou une adresse interne protégée derrière un proxy inverse TLS,
et configurez les clients avec l'URL publique https:// du proxy. Protégez le jeton
administrateur et le secret de signature ; n'exposez pas directement le point d'écoute HTTP en clair.
GET /healthGET /v1/public-keyPOST /v1/tickets/bootstrap(jeton bearer administrateur)POST /v1/tickets/mcp-grant(jeton bearer administrateur)POST /v1/tickets/unlock(jeton bearer administrateur ;{"ttl":300}, 1–3600 secondes)
Corps de l'autorisation (JSON) :
{
"vault": "…",
"scope": "read_write",
"minutes": 30,
"ttl": 3600
}
scope accepte les mêmes noms de protocole et alias que le CLI. Lorsque scope est
omis, write: true correspond à read_write, sinon la valeur par défaut est
read. Les autorisations hébergées statiques peuvent omettre la portée (TOKEN ou TOKEN::VAULT), et
la création de membre utilise read par défaut lorsque la portée est omise. Demandez allow_all
explicitement au lieu de vous appuyer sur l'ancienne valeur par défaut permissive.
Coffre hébergé complet (SaaS)
Activez le coffre hébergé avec --data DIR (ou MEMORIES_CENTER_DATA). Fournissez un
MEMORIES_CENTER_VAULT_PASSWORD non vide au moyen de l'environnement protégé/de l'injection de secrets
de votre gestionnaire de services. Il n'existe volontairement aucun argument de mot de passe en ligne de commande.
Le mot de passe n'est requis qu'avec --data ; le perdre fait perdre l'accès au Store.
Utilisez un mot de passe fort généré indépendamment, pas un jeton bearer ni une clé de signature.
# 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
Depuis une copie de travail du dépôt, préfixez les options par cargo run -p memories-center --.
Sans --data, le processus reste un Center rétrocompatible fonctionnant uniquement par tickets.
Les tickets et l'inscription continuent à fonctionner dans les deux modes.
Le mode hébergé réutilise memories_core::service::Service, Request, Content et le
Store SQLite chiffré du daemon. Il peut remplacer Center + daemon pour le contenu
du coffre hébergé, notamment :
- Tous les champs des mémoires/cartes : corps, étiquettes, sources, alias, notes, catégories, indicateur privé, liens, hôtes autorisés et références de secrets.
- Cartes brain, persona, workflow et modèles de workflow ; profils protégés account, env,
token, login, TOTP/HOTP et
otpauth. - DAG de révisions signées, historique, différences, lectures historiques et réapplication ; liens et graphes entre cartes du même coffre.
- Instantanés du monde des personas, basculement, cartes d'archives, objets/attributs, conversations de chat/curseurs de lecture, hooks d'automatisation, listes d'autorisation et déclencheurs.
- Pièces jointes chiffrées de fichiers et d'identifiants, remplacement de secrets protégés, transmission explicite d'identifiants et génération OTP.
Stockage et migration
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 peut aussi créer ses fichiers journaux transactionnels habituels. Les sauvegardes du Store sont
transactionnelles ; l'identité du coffre, les révisions signées et l'état d'audit persistent ensemble.
Un seul processus Center possède le Store à la fois. Le coffre par défaut et les coffres nommés
utilisés par les autorisations configurées ou émises sont créés au besoin. Les liaisons d'autorisation utilisent
en interne les identifiants canoniques des coffres et ne peuvent pas énumérer d'autres coffres ni y accéder.
Les répertoires Unix sont réservés au propriétaire (0700) ; les fichiers du Store et l'ancienne sauvegarde sont
réservés au propriétaire (0600). Protégez le répertoire et le compte de service sur chaque plateforme.
Si DIR/memories.json existe, le démarrage importe ses notes ordinaires, en conservant les identifiants
et les noms des coffres, dans une unique transaction de contenu atomique, puis renomme l'entrée en
memories.json.bak. Des enregistrements invalides font échouer le démarrage sans supprimer la source
ni publier un lot partiel de notes. Une nouvelle tentative après un renommage interrompu ignore
les enregistrements importés identiques ; des enregistrements contradictoires ou une sauvegarde déjà existante
provoquent un refus par défaut pour examen par l'opérateur au lieu d'écraser les données. L'ancienne sauvegarde
reste en clair : supprimez-la de manière sécurisée ou chiffrez-la indépendamment après avoir vérifié
l'importation et organisé une sauvegarde. Le nouveau contenu écrit n'est jamais enregistré en JSON.
Chiffrement et modèle de menace
Le Store est déverrouillé au démarrage du processus avec le mot de passe détenu par l'opérateur (enveloppement par mot de passe Argon2 et même chiffrement authentifié que le Store local). À chaque opération de contenu autorisée, le dispatch hébergé actualise la session du serveur avant les contrôles d'expiration pour inactivité ; il ne se verrouille pas après quinze minutes sans activité. L'arrêt et le verrouillage administrateur libèrent les clés du Store en mémoire et le contenu en cache. Le verrouillage/déverrouillage administrateur utilise des routes HTTP administrateur distinctes, jamais le RPC de contenu des autorisations. Il n'existe aucun RPC hébergé sudo ou de changement de mot de passe.
Il s'agit de chiffrement côté serveur, pas de chiffrement client de bout en bout. Les opérateurs
qui contrôlent l'hôte/le processus/le mot de passe peuvent lire tous les coffres hébergés, y compris
le contenu privé et les identifiants. Toute personne détenant à la fois le mot de passe et une copie
du disque peut déchiffrer le Store. La mémoire de l'hôte, les vidages après incident, l'inspection de l'environnement,
les clients autorisés malveillants et la terminaison TLS compromise se trouvent hors de cette
frontière de confidentialité. private contrôle toujours les résumés/la récupération sécurisés, mais
ne constitue pas une barrière contre une autorisation hébergée autorisée à lire ce coffre.
Le daemon utilise quant à lui un mot de passe d'appareil et peut utiliser des clés locales scellées par le système d'exploitation pour un fonctionnement sans surveillance. Le mode hébergé n'a ni clés d'appareil scellées ni interface de mot de passe par appareil. Les clés des coffres d'appareils ne sont pas automatiquement téléversées ni partagées avec ce Store hébergé. Utilisez le proxy inverse TLS décrit ci-dessus ; les requêtes HTTP et la transmission autorisée de secrets sont en clair pour Center et le terminateur TLS. Ne journalisez pas les jetons bearer, les corps RPC, les réponses contenant des identifiants ni les mots de passe.
Verrouillage/déverrouillage à distance
Les routes hébergées nécessitent le bearer administrateur et n'existent qu'avec --data :
| Route | Comportement |
|---|---|
GET /v1/admin/status |
Renvoie unlocked, le mode hébergé, le nombre de membres et restart_required, sans secrets |
POST /v1/admin/lock |
Libère les clés/l'état du Store, vide les caches et suspend les autorisations jusqu'au déverrouillage |
POST /v1/admin/unlock |
Accepte {"password":"…"} et rouvre le même Store existant avec le mot de passe de l'opérateur |
Pendant le verrouillage, les routes de contenu et l'émission de jetons d'accès hébergés renvoient 503 ;
aucune autorisation ne peut servir de contenu en clair. L'état administrateur, la liste/suppression des membres et l'émission
de tickets d'appareil restent disponibles. La création/la modification de portée/de coffre des membres nécessite un
Store déverrouillé. Les mots de passe hébergés incorrects renvoient 403 ; l'authentification échouée utilise
l'attente exponentielle du core (les tentatives suivantes pendant l'attente renvoient 429). Un
déverrouillage réussi restaure l'accès statique/des membres, pas les jetons émis révoqués.
Les requêtes déjà terminées avant le verrouillage ne peuvent pas voir leur contenu en clair
renvoyé retiré. Le démarrage nécessite toujours MEMORIES_CENTER_VAULT_PASSWORD ; le déverrouillage distant
ne conserve pas le mot de passe fourni et ne modifie pas la configuration de démarrage.
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
Le déverrouillage d'un appareil est différent : POST /v1/tickets/unlock est disponible sur les Centers
fonctionnant uniquement par tickets comme sur les Centers hébergés. L'administrateur signe un ticket unlock de courte durée ;
le daemon local vérifie son type, sa signature, le Center configuré et son expiration,
puis déverrouille avec le mot de passe de l'appareil demandé localement à l'opérateur. Il n'accorde
aucune élévation sudo/MCP. memories center unlock --ticket utilise toujours l'IPC local,
authentifié par le système d'exploitation, pas le daemon distant sélectionné ni l'API HTTP de Center.
Il n'existe aucune transmission automatique du mot de passe de l'appareil, aucun séquestre ni canal push.
# 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
Les tickets de déverrouillage sont réutilisables jusqu'à expiration et autorisent le déverrouillage des points de terminaison qui font confiance à ce Center, sous réserve du mot de passe de chacun ; ils ne sont ni à usage unique ni liés à un appareil. Le déverrouillage local existant par mot de passe reste disponible sur les appareils gérés uniquement par tickets. Les rattachements SaaS ne peuvent pas utiliser un ticket pour inscrire ou déverrouiller un Store local parallèle de contenu.
Garde du mot de passe : l'opérateur hébergé détient le mot de passe du Store hébergé et l'envoie au Center/terminateur TLS de confiance uniquement pour le déverrouillage hébergé. Les mots de passe des appareils et les clés des coffres restent sur l'appareil/le daemon local en mode tickets. Un Center compromis peut émettre des tickets de déverrouillage, mais a toujours besoin d'un mot de passe d'appareil pour déverrouiller son contenu chiffré. Un administrateur hébergé compromis peut verrouiller le Store et créer des membres disposant d'une autorité sur le contenu en clair dès qu'il est déverrouillé. Les opérateurs hébergés contrôlent déjà le processus et peuvent lire tout le contenu déverrouillé en clair ; la séparation administrateur/membre n'est pas un chiffrement contre l'opérateur.
Membres persistants
Les membres hébergés sont des principaux bearer nommés et persistants avec les mêmes cinq
niveaux GrantScope, pas des comptes de fournisseur d'identité. Un role facultatif vaut
reader, editor, admin ou owner ; les rôles omis conservent l'ancienne correspondance
avec read, write/read_write, read_write_delete et allow_all, respectivement.
Un rôle explicite plafonne la portée du membre (Reader : read, Editor : read_write,
Admin : read_write_delete, Owner : allow_all). Les routes CRUD des membres nécessitent toujours
le bearer administrateur de l'opérateur, pas le rôle Admin/Owner d'un membre :
| Route | Corps / réponse |
|---|---|
GET /v1/admin/members |
Tableau members avec id, vault, scope, role, created et updated ; aucun jeton bearer |
POST /v1/admin/members |
{"id":"agent","vault":"default","scope":"read"} ; métadonnées et token généré, affiché uniquement dans cette réponse |
PUT /v1/admin/members/{id} |
Portée, coffre et/ou rôle facultatifs ; même bearer, réponse limitée aux métadonnées |
DELETE /v1/admin/members/{id} |
Supprime le membre et invalide immédiatement le bearer |
Les identifiants sont des noms stables de 1–64 caractères (lettres/chiffres ASCII, -, _) ; center-admin
est réservé à l'opérateur Owner. Les identifiants
en double renvoient 409. Les jetons sont aléatoires, uniques et distincts des jetons administrateur/statiques.
Utilisez un terminal/une destination de sortie protégé pour créer des membres ; le CLI affiche
volontairement une seule fois le bearer renvoyé. Ne capturez jamais les réponses de création dans des journaux.
Les membres s'authentifient via la même table d'autorisations sur /v1/memories et /v1/rpc ;
les changements de portée/coffre s'appliquent aux requêtes suivantes sans redémarrage. Les requêtes
authentifiées avant une mutation intermédiaire d'appartenance sont rejetées avant le
dispatch de contenu. Supprimer/recréer un membre constitue la procédure de renouvellement du jeton.
DIR/members.json est un fichier persistant remplacé atomiquement et réservé au propriétaire (0600 sous Unix)
dans le répertoire hébergé réservé au propriétaire. Il contient les jetons bearer en
clair, mais aucun mot de passe, clé ou contenu du coffre ; protégez-le comme des identifiants,
incluez-le dans les sauvegardes protégées et ne le commitez pas. Le démarrage recharge/valide
les membres et refuse par défaut les bearers corrompus ou en collision. Un échec de sauvegarde persistante des membres
verrouille le contenu et définit restart_required : le déverrouillage distant renvoie 503 jusqu'à
ce que l'opérateur répare le système de fichiers et redémarre pour recharger l'autorisation persistante.
Cela empêche des résultats de persistance incertains de réactiver des bearers révoqués.
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
Il s'agit toujours d'un Store unique dont l'opérateur détient la clé, avec une liaison logique des autorisations par coffre, pas d'une isolation forte entre locataires, d'OIDC/SSO, de mots de passe utilisateur, de jetons de renouvellement, de quotas ou de récupération en libre-service.
Autorisations et API
Les autorisations statiques utilisent --grant TOKEN[:SCOPE[:VAULT]] répétable et/ou
MEMORIES_CENTER_GRANTS séparé par des virgules ; une portée omise signifie read, et un coffre omis
signifie default (TOKEN::VAULT choisit un coffre avec la portée par défaut). Leur durée de vie est celle
du processus. POST /v1/tickets/mcp-grant authentifié par l'administrateur renvoie le
ticket signé inchangé ainsi qu'un access_token hébergé lié à son coffre/sa portée.
Ce jeton d'accès expire après minutes (1–60) et survit au redémarrage hébergé.
Le ttl du ticket régit séparément la validité d'importation du ticket. Les autorisations hébergées émises
sont persistées atomiquement dans DIR/grants.json, réservé au propriétaire (0600) : uniquement des hachages SHA-256
de bearers et des métadonnées d'autorisation, jamais de jetons émis en clair. Les hachages
de bearers sont comparés en temps constant. Les révocations persistent et la suppression de membre
se propage toujours dans l'ascendance des autorisations restaurées après redémarrage. Les autorisations expirées
et leurs descendants sont purgés au chargement, chaque minute et pendant
l'authentification/l'émission. Considérez ce fichier d'autorisation contrôlé par le propriétaire et ses
sauvegardes comme sensibles pour la sécurité ; revenir à une version antérieure de ce fichier ou du Store peut restaurer un ancien état.
Un échec de persistance verrouille le service et nécessite une réparation/un redémarrage.
Verrouiller le Store hébergé libère ses clés et rend les autorisations inutilisables, sans
les révoquer ; les autorisations émises non expirées, les autorisations statiques et les membres persistants reprennent
après déverrouillage. Chaque membre persistant possède un compteur de génération persisté. Les générations de l'émetteur et
du destinataire sont figées à l'émission et ne sont jamais actualisées par
l'authentification ; le champ public generation est la génération du destinataire
à l'émission. Une modification de portée/rôle/coffre ou une suppression incrémente uniquement la génération de ce membre ;
les compteurs des membres supprimés sont conservés afin que recréer un identifiant ne les réinitialise pas.
La suppression/modification révoque explicitement les autorisations émises par ou pour ce membre et tous leurs
descendants de manière transitive ; les autorisations sans rapport restent utilisables, y compris après
redémarrage. Les identifiants statiques/persistants sont rétablis lors de la transition explicite de
politique, pas lors de l'authentification. Les identifiants des principaux statiques et des parents membres persistants
sont stables entre les redémarrages afin que les chaînes de délégation restaurées restent reliées.
Le bearer administrateur seul reçoit 401 sur /v1/memories et /v1/rpc, même pour
les lectures. Il ne peut pas modifier directement le contenu du coffre. Les administrateurs peuvent toujours émettre
des autorisations distinctes via l'API de tickets ou d'autorisation ; cette capacité administrative ne constitue pas
une barrière de confidentialité contre l'administrateur.
API HTTP d'autorisation
Ces routes n'existent qu'en mode hébergé. Elles authentifient soit le bearer administrateur de l'opérateur (un Owner sur tous les coffres), soit le bearer d'un principal hébergé. L'opérateur contrôle le verrouillage/déverrouillage du serveur et le CRUD des membres ; un Admin/Owner hébergé peut gérer l'autorisation uniquement dans les limites du coffre et de la portée du bearer. Aucune élévation du terminal n'est nécessaire. Les Readers et Editors ne peuvent ni émettre ni déléguer, même lorsqu'ils disposent d'un accès au contenu plus large. Les violations de rôle/portée/coffre/expiration renvoient 403 avec une erreur d'autorité claire ; les échecs de validation du gestionnaire renvoient 400, les objets absents 404, les décisions répétées 409 et les bearers expirés/révoqués/de génération obsolète 401. Un JSON mal formé ou présentant des types incorrects peut être rejeté par l'extracteur JSON d'Axum avec 400 ou 422.
| Route | Champs et réponse |
|---|---|
GET /v1/access/{vault} |
Admin/Owner ; vault, principals regroupés sous owner, admin, editor, reader, grants actifs avec leur expiration, plus pending_requests et approved_unclaimed_requests (id, requester, scope, liste approvers ou "broadcast", created et expires en secondes Unix) |
POST /v1/grants |
Admin/Owner ; vault, scope facultatif (par défaut read), duration_minutes (par défaut 30, alias minutes), grantee ; métadonnées de l'autorisation et token à affichage unique |
GET /v1/grants |
vault facultatif ; Admin/Owner voient toutes les autorisations émises dans le coffre, les autres principaux actuels ne voient que leurs propres autorisations émises ; tableau grants, excluant les identifiants statiques/persistants et les autorisations expirées/obsolètes |
DELETE /v1/grants/{id} |
Émetteur ou Admin/Owner dans le coffre ; révoque l'autorisation émise et ses descendants, renvoie revoked et count |
POST /v1/requests |
Tout membre ; vault, scope facultatif (par défaut read), reason, duration_minutes (par défaut 30, alias duration), approvers ; métadonnées de la demande en attente |
GET /v1/requests |
vault facultatif ; demandes en attente appartenant au demandeur ou à un approbateur admissible, plus les demandes approuvées non récupérées du demandeur ; aucun jeton |
POST /v1/requests/{id}/approve |
Admin/Owner admissible ; aucun corps ; uniquement les métadonnées request approuvées non récupérées et les limites validées, aucune autorisation ni jeton |
POST /v1/requests/{id}/claim |
Uniquement le bearer de membre persistant du demandeur ; aucun corps ; request et grant nouvellement émise avec token à affichage unique |
POST /v1/requests/{id}/reject |
Admin/Owner admissible ; JSON reason facultatif ; request avec état rejected |
POST /v1/requests/{id}/cancel |
Demandeur uniquement ; aucun corps ; request avec état cancelled |
GET /v1/audit |
Admin/Owner ; vault facultatif, format=json|jsonl (par défaut json) ; entries/verified vérifiés, ou export délimité par des sauts de ligne |
Toutes les durées d'émission/de demande sont de 1–60 minutes. Au plus 10,000 autorisations peuvent être
actives simultanément (memories_core::service::MAX_ACTIVE_GRANTS) ; le daemon local
utilise le même plafond. Les émetteurs ne peuvent pas dépasser le plafond
de leur rôle ou la portée de leur bearer, sortir de leur coffre ni prolonger l'expiration de leur parent.
Les destinataires sont par défaut le membre émetteur ; les noms grantee explicites doivent correspondre à des principaux
existants du coffre sélectionné. Le destinataire réservé center-admin est toujours
rejeté avec HTTP 403 ; les autorisations précédemment persistées avec ce sujet sont également
inutilisables. L'émission par l'opérateur sans destinataire (y compris l'émission de
tickets hébergés) crée un principal anonyme dont le sujet est l'identifiant de son autorisation,
pas center-admin. Aucun destinataire ne peut avoir un rôle supérieur au rôle
effectif de l'émetteur, même pour une autorisation en lecture seule.
Center et le daemon définissent tous deux pub const MAX_DELEGATION_DEPTH: u32 = 2.
L'émission directe par un membre/l'opérateur est de profondeur 1 ; utiliser une autorisation émet à
parent.depth + 1, et la profondeur 3 renvoie HTTP 403. Les identifiants des membres persistants représentent
une autorité directe de membre (profondeur interne 0) ; les autorisations qu'ils émettent sont de profondeur 1 sans
autorisation parente. Les anciennes autorisations persistées sans depth sont reconstruites à partir
de leur chaîne parente complète, y compris les identifiants statiques et persistants configurés.
Une ascendance absente/cyclique ou dépassant la limite est inutilisable et purgée au démarrage.
Une autorité d'autorisation avec subject != issued_by est un appelant d'autorisation désigné.
Ces appelants ne peuvent approuver, rejeter, annuler ni récupérer aucune demande de permission,
même la leur (HTTP 403) ; ils ne sont pas des approbateurs admissibles. Ils peuvent créer
des demandes, mais doivent utiliser leur véritable identifiant de membre pour les workflows de permission.
L'émission déléguée conserve le véritable émetteur afin qu'un bearer désigné ne puisse pas
se transformer abusivement en autorisation auto-émise. Les autorisations auto-émises conservent leurs règles
de demande existantes, et la récupération nécessite toujours le bearer de membre persistant du demandeur.
Les anciennes approbations différées faites avec une autorisation à destinataire désigné ne peuvent pas autoriser
une récupération ; obtenez une nouvelle approbation auprès d'une autorité de membre admissible.
Changements incompatibles : les destinataires de rôle supérieur et center-admin, les
décisions/annulations/récupérations de permissions par destinataire désigné et les délégations dépassant deux autorisations
sont rejetés. Les sujets émis par l'opérateur sont désormais anonymes plutôt que
center-admin ; les racines émises par un membre ne répertorient plus l'identifiant du membre persistant
comme parent de délégation. Réémettez une racine appropriée ou utilisez un identifiant de membre
au lieu de vous appuyer sur ces anciens comportements.
Les autorisations enregistrent id (un identifiant opaque indépendant),
subject, scope, vault, expiry en secondes Unix, parent, issued_by,
generation, depth et role. Les identifiants de principaux statiques sont static:OPAQUE_ID.
GET /v1/grants renvoie uniquement les autorisations émises à durée limitée avec id, parent,
issued_by, grantee, scope, vault, expiry en secondes Unix, generation,
depth et created en secondes Unix lorsque disponible. Il ne renvoie jamais de jeton ni de
hachage de bearer.
grantee est le destinataire (subject dans les réponses d'émission/d'accès).
La génération reste celle de l'émission. Cette vue des autorisations émises diffère
de la vue principal/accès inchangée de GET /v1/access/{vault} : les identifiants statiques et
persistants apparaissent uniquement dans cette dernière et sont supprimés via la
configuration/le CRUD des membres, pas DELETE /v1/grants/{id}. Les émetteurs peuvent lister leurs
propres autorisations ; recevoir une autorisation ne suffit pas à la rendre visible à un destinataire
non administrateur. Les bearers hébergés sont limités à leur coffre ; un vault omis
sélectionne ce coffre, tandis que l'opérateur peut lister plusieurs coffres.
memories center grant list [VAULT] renvoie cette même réponse d'autorisations émises.
Les demandes dont approvers est vide/omis sont diffusées via les listes de demandes en attente admissibles ;
les listes non vides contiennent les identifiants d'approbateurs Admin/Owner nommés (ou center-admin).
L'approbation comme le rejet sont limités par la portée, le coffre, le rôle et la
durée de vie restante de l'autorisation de l'approbateur. Le demandeur ne peut pas approuver ni rejeter sa propre
demande, même en tant qu'Admin/Owner ou approbateur nommé ; il peut toujours l'annuler.
Les décisions sont sérialisées sous le même verrou d'autorisation et ne peuvent avoir lieu
qu'une seule fois. Les états de demande sont pending, approved, rejected ou cancelled.
L'approbation enregistre les paramètres d'autorisation approuvés et les limites de portée/coffre/rôle/
expiration de l'approbateur, mais n'émet rien et ne renvoie aucun jeton. Le membre demandeur
utilise son identifiant de membre persistant pour appeler POST /v1/requests/{id}/claim, ou
memories center request claim ID. Les approbateurs, les autres membres et les autorisations émises
pour le même sujet demandeur sont rejetés. Les récupérations sont sérialisées : une seule
réussit, et les tentatives suivantes renvoient 409, y compris après redémarrage.
La génération de l'autorisation et son horloge de création/d'expiration commencent à la récupération ; son expiration est
plafonnée par l'expiration de l'autorité d'approbation enregistrée. Les parents expirés/révoqués et
les changements de génération de l'émetteur/du demandeur empêchent la récupération. Seul le hachage du jeton est
persisté avec les métadonnées d'autorisation. request.claim audite l'acteur demandeur et
l'identifiant de l'autorisation, jamais le jeton. Si la réponse à affichage unique est perdue, demandez une nouvelle approbation.
Une autorisation de contenu approuvée ne modifie ni le rôle de membre du demandeur ni son
autorité de délégation.
Changement incompatible : les réponses d'approbation ne contiennent plus grant ni son jeton ;
les clients doivent explicitement récupérer l'autorisation avec le bearer de membre demandeur. Les listes de demandes
affichent les métadonnées approuvées non récupérées, pas les jetons. Les anciennes demandes approuvées dépourvues
de limites d'approbation différée ne peuvent pas être récupérées ; créez plutôt une nouvelle demande.
Fichiers d'audit et de demandes de Center
DIR/requests.json est remplacé atomiquement avec le mode 0600 et conserve les métadonnées
des demandes et les décisions terminées entre les redémarrages. DIR/audit.jsonl est ouvert en
mode ajout avec le mode 0600, synchronisé par fsync après chaque entrée et jamais réécrit par
Center. Les deux se trouvent dans le répertoire hébergé en mode 0700. Les motifs sont des métadonnées
d'audit : n'y mettez pas de mots de passe ni d'autres secrets. Les enregistrements d'audit ne
contiennent ni jetons bearer, ni mots de passe, ni clés, ni contenu de coffre.
Les entrées d'audit exposent sequence, timestamp, prev_hash, hash, action,
et facultativement issued_by / via_grant pour les actions autorisées par une autorisation. Le premier
est le véritable émetteur, le second l'identifiant de l'autorisation de l'appelant ; actor reste le sujet
de l'autorisation, sauf que grant.issue enregistre le véritable émetteur comme acteur et le
destinataire comme sujet. Les champs sont absents par défaut et omis lorsqu'ils sont absents,
afin que les anciens journaux d'audit conservent leurs hachages existants. Les audits signés de contenu
hébergé du Store partagé enregistrent également ces champs. Les champs existants comprennent
actor, subject, scope, vault, expiry, parent, reason, request_id et
object. Le premier prev_hash vaut null ; les valeurs suivantes référencent le hachage
précédent. SHA-256 couvre le tuple JSON (sequence, timestamp, prev_hash, event_fields).
verify_audit_chain(path) dans crates/center/src/saas/audit.rs vérifie la continuité,
les hachages et l'intégralité des lignes JSONL. Le démarrage vérifie le fichier et rapproche les décisions
et récupérations de demandes de celui-ci, afin qu'un incident après l'audit d'une décision ou récupération
mais avant le remplacement des métadonnées de demande ne puisse pas permettre une répétition. Les ajouts et les lectures
vérifient la chaîne par rapport à la tête actuelle en mémoire du processus ; les lectures d'audit vérifient toute la chaîne
avant d'appliquer un filtre de coffre.
Les actions sont device.authorize (émission de ticket d'initialisation), grant.issue,
grant.revoke, member.add, member.set, member.remove, session.lock,
session.unlock, request.create, request.approve, request.claim,
request.reject et request.cancel. Les révocations dues au verrouillage et aux changements de membres sont
également auditées. Les exports JSONL
utilisent Content-Type: application/x-ndjson ; les exports filtrés conservent les hachages/séquences
d'origine et ne sont pas des chaînes complètes indépendantes. Les appelants Admin/Owner liés à un coffre
ne voient que les entrées de leur coffre ; l'opérateur voit également les événements globaux et
peut exporter le journal complet. Tout échec d'ajout à l'audit/de persistance des demandes verrouille
le service, définit restart_required et désactive le déverrouillage distant jusqu'à réparation et
redémarrage. Les mutations d'autorisation et les décisions de permission renvoient 503 lorsque
restart_required est défini ; l'inspection en lecture seule par l'opérateur reste disponible.
Conservez ces fichiers dans des sauvegardes protégées, pas dans le contrôle de version.
Il s'agit d'une chaîne sans clé, pas d'une signature d'appareil ni d'un ancrage externe : un opérateur compromis peut la réécrire/recalculer, omettre des événements ou revenir à une version antérieure de tous les fichiers ensemble. La troncature complète de la fin avant le démarrage ne peut pas être détectée sans une tête connue externe. Ne considérez pas la vérification de chaîne comme un audit de sécurité indépendant.
Portée et API du contenu hébergé
Les portées du contenu hébergé s'appliquent à l'opération, pas simplement à la méthode HTTP.
Les routes de gestion d'autorisation vérifient en plus le rôle du principal comme
décrit ci-dessus ; la scope du contenu seule n'accorde jamais l'administration de l'hôte :
| Portée | Autorité hébergée dans le coffre lié |
|---|---|
read |
Liste/recherche/graphe sécurisés ; lectures complètes autorisées, lectures de fichiers, historique/différences/audit et credential_copy explicite |
write |
Création/mise à jour de contenu, inscription de contenu protégé/OTP, fichiers/identifiants, écritures monde/chat/automatisation ; aucune lecture de contenu antérieur ni suppression |
read_write |
Les deux ; également otp_code (nécessite lecture + écriture car HOTP fait avancer l'état) |
read_write_delete |
Les deux plus suppression de ressources, objets, attributs et hooks d'automatisation |
allow_all |
Tout ce qui précède dans l'interface hébergée limitée au contenu, pas l'administration du serveur/des appareils |
Contrairement au MCP du daemon, le dispatch hébergé fournit une élévation locale à l'opération uniquement après
validation de la liste fermée des opérations autorisées, de la portée et de la liaison au coffre. Le check_mcp du daemon
et ses restrictions d'élévation locale restent inchangés. Les agents/adaptateurs MCP hébergés
peuvent utiliser leur bearer à portée définie sur toute l'interface RPC de contenu ; aucune élévation locale
du terminal ni connexion au daemon n'est nécessaire. Les références brain/archive inter-coffres
ne peuvent pas résoudre le contenu d'un autre coffre au moyen d'une autorisation.
GET /v1/session→ coffre actuel, rôle, portée, expiration et capacités de lecture/écriture/suppression, sans données bearer. Les jetons limités à l'écriture peuvent inspecter leur session ; les jetons administrateur de l'opérateur ne le peuvent pas. Consultez le mode mobile hébergé.GET /v1/memories→{"memories":[...]}. Les enregistrements de notes ordinaires non privées conserventid,vault,title,body; les enregistrements de cartes/contenus privés/fichiers/identifiants utilisent des résumés sécurisés, jamais les corps d'identifiants ni les secrets résolus.GET /v1/memories/{id}→ contenu client complet sécurisé +id,vault. Les identifiants nécessitent uncredential_copyexplicite plutôt que des lectures génériques.POST /v1/memoriesetPUT /v1/memories/{id}acceptenttitle/bodyet toutes les métadonnéesContent. PUT conserve les champs omis. Ajouter des métadonnées de carte à la création infèrekind: "card"si aucun type n'est fourni. La création de profils structurés/protégés utilise son RPC dédié, pas un PUT générique. Un PUT limité à l'écriture renvoie uniquement les champs soumis et l'identité de l'enregistrement, pas les champs privés existants.- Le JSON facultatif
vault/?vault=…doit correspondre au nom ou à l'identifiant canonique de l'autorisation ; l'omission sélectionne le coffre de l'autorisation.DELETE /v1/memories/{id}nécessite la suppression. POST /v1/rpcaccepte le JSONRequestdu daemon (open snake_case), renvoie son résultat JSON direct et vérifie chaque opération indépendamment. Utilisez les identifiants canoniques de ressources/révisions des réponses ; vault accepte le nom ou l'identifiant canonique de l'autorisation.listsansvaultsélectionne uniquement le coffre de l'autorisation. Les erreurs utilisent HTTP 401 (jeton inconnu/administrateur/expiré), 403 (portée/interface/coffre), 404 (absent), 409 (conflit), 400 (entrée invalide) ou 422 (JSON/schéma Request mal formé).
Exemples (envoyez avec le bearer de l'autorisation, pas le bearer administrateur) :
{"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"}}
Les fichiers/identifiants utilisent la convention existante de corps en base64 compatible URL, pas un second
stockage de blobs. La limite de corps de contenu de 16 MiB du core s'applique toujours ; la limite du corps
JSON HTTP est de 24 MiB. protect_secret maintient atomiquement les références d'identifiants.
Les lectures/listes/différences génériques ne résolvent jamais les octets d'identifiants stockés ; le
credential_copy autorisé est le point de terminaison intentionnel de transmission explicite en clair.
Les opérations hébergées archive_history/archive_read ciblent les révisions signées du coffre
actif lié (y compris les révisions historiques/marquées supprimées), pas les archives de récupération
du daemon. archive_audit cible l'audit de ce coffre. Le reapply hébergé ajoute une
révision historique ou mise en quarantaine signée comme nouvelle tête, y compris les métadonnées structurées/
protégées ; réappliquer un marqueur de suppression nécessite aussi la suppression. Contrairement à l'invention
de références protégées via un PUT générique, cela restaure des références déjà
authentifiées dans l'historique signé du coffre. Cela ne ressuscite pas automatiquement un identifiant
lié supprimé ; réappliquez séparément la révision de cet identifiant si
nécessaire.
Différences intentionnelles avec le daemon
POST /v1/bootstrapet/v1/restorerenvoient toujours 403 : cela reste une bibliothèque gérée par Center, pas une inscription/restauration d'appareil en libre-service.- Aucun sudo, inscription d'appareil dans le contenu hébergé, administration des appartenances à la
politique du coffre, RPC de contenu d'émission/révocation d'autorisation, RPC de sauvegarde/restauration complète du stockage, sessions/
proxys distants, synchronisation cloud/relais, garde sans surveillance, exécution ni exécution de tâches
cryptographiques. De telles requêtes sont refusées par défaut même avec
allow_all. - Le verrouillage/déverrouillage/l'état hébergés et l'administration des membres persistants sont des routes HTTP
distinctes autorisées par l'administrateur, pas des capacités conférées par
allow_all. - Les archives de récupération et la résolution de contenu inter-coffres ne sont pas exposées. Les lectures historiques/ la réapplication des propres révisions du coffre hébergé sont prises en charge.
- La configuration/génération de modèles sémantiques/RAG et l'installation de modèles ne sont pas des
points de terminaison hébergés. Le
searchlexical et le chemin synchroneretrieveréutilisent la politique de récupération du core (aucun contenu privé, corps de fichier ni corps d'identifiants). - L'automatisation s'exécute sur RPC explicite et sur les événements existants déclenchés par le contenu ; il n'y a aucun planificateur en arrière-plan. Les webhooks sortants utilisent la liste exacte des URL autorisées du core ; considérez un auteur hébergé pouvant configurer des webhooks comme autorisé à accéder au réseau sortant et appliquez les restrictions de sortie de l'opérateur aux locataires non fiables.
- Les daemons configurés pour SaaS et l'assistant CLI léger utilisent le
/v1/rpchébergé ; le mobile natif utilise son chemin FFI distincthosted_*avec une liste de contenu autorisé plus restreinte. L'IPC local fonctionnant uniquement par tickets reste intact. Il n'existe aucun serveur d'outils administrateur MCP JSON-RPC.
La persistance du Store utilise toujours des sauvegardes transactionnelles de l'état complet et des opérations de service sérialisées ; il ne s'agit ni de chiffrement par clé indépendante pour chaque locataire, ni d'une affirmation d'isolation entre locataires en production/de certification de sécurité.
Mobile / FFI
L'application Flutter livrée choisit Standalone, Center ou SaaS dans sa propre interface.
Elle ne charge pas le config.toml du daemon et ne configure pas de service local géré
par tickets. Son choix Center se connecte à un serveur auto-hébergé avec stockage
hébergé, tandis que SaaS se connecte à https://memories.ivjn.us. Les deux utilisent les
opérations FFI hébergées ci-dessous.
Le service partagé applique les restrictions Init/Restore et d'autorisation gérées par tickets lorsqu'un appelant configure des Centers sur ce service. Cela est distinct du sélecteur de mode du téléphone. Le FFI mobile continue de refuser Run/CryptoJob/la gestion locale des autorisations MCP/remote/proxy et les RPC locaux d'autorisations/de demandes de permission ; sélectionner un mode hébergé n'élargit pas cette interface locale.
Travail restant
Les approbations d'instructions distantes et d'autres interfaces de politique à privilèges élevés pourront ultérieurement accepter
des tickets d'approbation signés par Center. En attendant, les points de terminaison gérés par Center utilisent toujours
sudo local pour les opérations nécessitant une élévation du coffre autres que l'émission d'autorisations MCP et
l'initialisation. allow_all est réservé à l'évolution de ces interfaces sans renommer
les portées.
Limites, expiration et conservation des demandes de permission
S'applique au daemon (demandes dans le Store chiffré) et à Center (requests.json) :
MAX_PENDING_PER_REQUESTER_PER_VAULT = 20etMAX_PENDING_PER_VAULT = 200comptent uniquement les demandes en attente. Les demandes décidées, annulées, expirées et approuvées mais non récupérées ne sont pas comptées dans ces limites. (Cela remplace l'ancien plafond global de 1000 demandes, qui comptait chaque demande jamais effectuée.) Si vous dépassez une limite, le daemon renvoie une erreur de limite et Center renvoie HTTP 429.PENDING_REQUEST_TTL = 7 days: une demande en attente plus ancienne passe à l'étatexpired. Elle est auditée commerequest.expireavec l'acteursystemet ne peut plus être approuvée. Une demande de portée approuvée qui n'a pas été récupérée dans les 7 jours suivant son approbation expire également de la même manière et ne peut plus être récupérée ensuite.REQUEST_RETENTION = 30 days: les demandes rejetées, annulées, expirées ou récupérées sont supprimées après cette durée depuis leur dernière transition (l'horodatagedecided, oucreatedpour les anciens enregistrements qui n'en ont pas).- L'expiration et la purge sont effectuées à la demande : le daemon les exécute au début de toute demande d'autorisation, et Center les exécute lors de la création, de la liste, de la décision et de la récupération de demandes. Rien ne les exécute au démarrage.
- Le rapprochement au démarrage de Center ignore les entrées d'audit des demandes purgées et accepte les entrées
request.expire. Une demandeexpireddoit avoir une expiration auditée. Les identifiants de demande sont des valeurs aléatoires de 128 bits jamais réutilisées ; une demande purgée (y compris récupérée) n'existe donc plus et ne peut pas être récupérée. Pour cette raison, Center ne conserve pas d'ensemble distinct de marqueurs de suppression.
Mode mobile hébergé (client téléphone)
Les modes Center et SaaS de l'application mobile se connectent directement à un Center exécuté avec
--data DIR ; le téléphone n'a pas besoin d'une entrée mode = "saas" du daemon. Consultez
l'utilisation mobile pour la première connexion et le changement de mode,
et la configuration pour la migration des profils.
L'API FFI memories_call accepte ces opérations 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":{…}}utilise la liste de contenu autorisé ci-dessous{"op":"hosted_status","session":N}s'authentifie viaGET /v1/session{"op":"hosted_requests","session":N},hosted_approve/hosted_reject(id),hosted_access(vault){"op":"hosted_disconnect","session":N};memories_lock_allsupprime également toutes les sessions hébergées
HostedClient::rpc dans crates/core/src/center.rs autorise list, read, search, delete,
type_list, type_show, type_create, type_update, type_delete,
card_create, card_fields, card_field, credential_set, credential_show,
et put lorsque content.kind vaut memory, card ou credential. Le serveur applique toujours le
coffre, le rôle et la portée du membre. Cette liste d'autorisation FFI ne promet pas que chaque
opération possède un écran Flutter : les connaissances hébergées prennent en charge la liste, la recherche serveur,
le détail, la modification des mémoires/cartes ordinaires, la création et la suppression confirmée. Les profils
structurés, les cartes protégées, les modèles de types et les cartes scellées nécessitent des éditeurs
spécialisés. Les demandes d'autorisation et les commandes des appareils associés restent disponibles.
GET /v1/session renvoie vault, vault_id, scope, role, expires_at
(secondes Unix ou null), can_read, can_write, can_delete et
server_readable. Il accepte tout jeton de contenu actif à portée définie, y compris
les jetons limités à l'écriture, mais rejette les jetons administrateur de l'opérateur. Il ne renvoie aucune donnée bearer ni
secrète. Chaque requête de contenu revérifie l'autorisation ; cette réponse
est un instantané pour l'interface, pas une autorisation. Déployez ce point de terminaison avant de mettre à niveau l'application :
les anciens serveurs qui en sont dépourvus ne peuvent pas valider une connexion de la nouvelle application.
La recherche utilise le service de récupération partagé et exclut le contenu privé, les identifiants et les fichiers. La liste de connaissances non filtrée peut inclure des cartes privées pour un lecteur autorisé. Les échecs de sauvegarde conservent le brouillon ; la suppression nécessite une confirmation et ne retire l'élément qu'après réussite du serveur. Les mises à jour utilisent le service de révision existant sans précondition atomique de révision attendue ; des modifications simultanées peuvent remplacer les derniers champs les unes des autres.
Le mode hébergé est lisible par le serveur : le Store est chiffré au repos, mais
l'opérateur détient le mot de passe et peut lire le contenu hébergé. L'application l'indique par
« le serveur peut lire ». Rust conserve le bearer de chaque client actif en mémoire, masqué dans
Debug et les erreurs ; Flutter conserve la paire URL/jeton de chaque mode dans le stockage
sécurisé de la plateforme. Changer de mode termine la session active tout en conservant le profil.
Se déconnecter supprime le bearer enregistré de ce profil ; cela ne révoque pas le jeton
du serveur. Le passage en arrière-plan supprime les sessions hébergées et le contenu visible ; la reconnexion au
retour est explicite.
Secrets E2E entre appareils (Center hébergé)
Les coffres hébergés sont lisibles par le serveur. Pour les secrets que Center ne doit jamais voir, associez
des appareils et transmettez des enveloppes scellées (crypto::Envelope : X25519 + signature
par la clé Ed25519 de l'expéditeur). Center stocke uniquement les clés publiques et le contenu chiffré.
Parcours d'association
- CLI :
memories device hosted init --backend <keychain|secret-service|dpapi|owner-file>crée l'identité de l'appareil (graine de 32 octets gardée par le système d'exploitation ; X25519 + Ed25519 dérivés). - CLI (jeton du membre dans
MEMORIES_CENTER_TOKEN) :memories device hosted pair --vault V→POST /v1/devices/pairingsavec les deux clés publiques et une autosignature. Center crée une PermissionRequest en attente portant l'empreinte de l'appareil et renvoie{code, request_id, qr: {pairing_id, fingerprint, center}}(centerest le--center-idde Center). Les associations expirent après 5 minutes. Le CLI affiche un QR de terminal (stderr) de{"type":"memories-pair","v":1,"pairing_id","fingerprint","center","code"}— le code à usage unique est inclus pour qu'une lecture suffise ; le JSON stdout reste inchangé. Réassocier une clé déjà active renvoie 409 ; après révocation, la même clé peut être associée à nouveau. - Le téléphone (administrateur du coffre) lit le QR (ou saisit le code) →
GET /v1/devices/pairings/{code}. Le téléphone refuse un QR qui n'est pas un QR d'association Memories, dont lecenterdiffère du Center connecté ou dont l'empreinte diffère de l'appareil récupéré. Les codes inconnus, expirés ou utilisés renvoient tous 404. — usage unique, expiration, 10 recherches échouées/minute → 429. Affiche l'empreinte de l'appareil. - Le téléphone approuve avec
POST /v1/requests/{id}/approveet le corps{"attestation": DeviceAttestation}signé par la clé Ed25519 du téléphone sur l'empreinte de l'appareil, les deux clés de l'appareil, l'identifiant d'association, l'approbateur, la portée et l'expiration. - CLI :
memories device hosted claim --request-id ID(une fois). Le jeton d'autorisation à portée d'appareil est gardé par le système d'exploitation ; Center ne conserve que son hachage. - Comparez le SAS : le téléphone l'affiche (groupé
123 456) avant l'approbation ; après approbation,memories device hosted sas --pairing-id Paffiche les mêmes chiffres dans le CLI. Chiffres différents : révoquez l'appareil et associez-le à nouveau.
Permissions mobiles : Android CAMERA (fonction caméra facultative), iOS
NSCameraUsageDescription. Si l'accès à la caméra est refusé, le téléphone explique comment
l'activer dans les réglages système et propose la saisie manuelle du code. Le CLI et le téléphone
traduisent les états de Center en messages permettant d'agir (404 expiré/utilisé/autre
Center, 409 déjà effectué ou déjà associé, 403 non autorisé ou appareil
révoqué, 422 contenu e2e refusé, 429 limite de fréquence atteinte).
SAS = les 6 premiers chiffres décimaux de
u64_be(SHA-256("memories/device-sas/v1\0" ‖ phone_ed25519_pub ‖ "\0" ‖ cli_ed25519_pub ‖ "\0" ‖ pairing_id)[0..8]) mod 10^6,
complétés par des zéros. Le téléphone obtient la clé du CLI lors de la récupération ; le CLI apprend la clé du téléphone
depuis l'attestation. Une différence signifie qu'une clé a été substituée : révoquez immédiatement.
Règles
- Les autorisations d'appareil sont liées à l'appareil (non révoqué, attesté) et à la génération du membre ; profondeur de délégation ≤ 2. Les appareils ne peuvent pas émettre d'autorisations ni décider de demandes.
- Incrémenter la génération du membre ou le supprimer invalide l'appareil et supprime ses enveloppes.
PUT|GET|DELETE /v1/sealed/{vault}/{card}/{device},GET /v1/sealed?device=: enveloppes uniquement pour les appareils attestés, adressées à la clé X25519 de l'appareil, provenant d'un signataire connu ; les versions doivent augmenter. L'audit n'enregistre que les empreintes.- Les cartes avec
e2e: truerefusent le contenu secret en clair sur le Center hébergé. /v1/access/{vault}liste les appareils. MCP :device_list,sealed_reveal(nécessiteintent: "reveal_plaintext"et un motif).
Modèle de menace
L'opérateur de Center ne peut pas lire les secrets scellés, mais peut retenir ou rejouer d'anciennes enveloppes (les versions sont monotones par appareil), et voit les métadonnées (qui, quelle carte, quand). L'association fait confiance à la comparaison SAS et à la clé du téléphone de l'approbateur. La révocation ne peut pas rappeler le contenu en clair déjà transmis à un appareil : après révocation, renouvelez le secret en amont.
Renforcement E2E (P2)
- Indicateur
e2epersistant. Une fois qu'une carte este2e: true, les écritures qui retirent cet indicateur sont refusées (422). Seule une autorisation Owner (pas une autorisation d'appareil) peut l'effacer, et uniquement avec une écriture ne contenant aucune donnée secrète ; les écritures ultérieures suivent alors les règles normales. - Paramètres OTP. Un secret OTP scellé peut être une simple graine Base32 (SHA1, 6 chiffres, 30 s) ou un URI
otpauth://totp/...complet ;memories device hosted sealed-get --otprespectealgorithm(SHA1/SHA256/SHA512),digits(6/8) etperiod(30/60) de l'URI.device hosted pairaffiche également la charge utile d'association sous forme de code QR de terminal (stderr). - Rescellement après révocation. Révoquer (ou invalider) un appareil supprime ses enveloppes et marque chaque carte concernée
needs_rotation(affiché dansGET /v1/sealed?device=,GET /v1/sealed/rotation?vault=, la liste d'accès et la page de rescellement du téléphone). La révocation ne peut pas rappeler le contenu en clair déjà transmis : renouvelez le secret en amont, puis utilisez Resceller pour tous les appareils (hosted_reseal_all) du téléphone, qui scelle pour chaque appareil actuellement approuvé avec une nouvelle version et efface l'indicateur. Les appareils révoqués ne sont jamais inclus. - Récupération du téléphone. Center ne détient jamais l'identité du téléphone. À sa création, le téléphone affiche un code de récupération à affichage unique (Base32 de la graine de 32 octets + somme de contrôle de 2 octets, groupes de quatre) à noter ; Restaurer depuis un code de récupération (
hosted_phone_restore) reconstruit la même identité. - Renouvellement de l'identité. Passer à une nouvelle clé de téléphone (
hosted_identity_rotate) crée une nouvelle identité et envoiePOST /v1/devices/identity-rotationsavec un enregistrement signé par l'ancienne clé et la nouvelle clé ; seul un membre administrateur (pas un appareil) peut l'envoyer, et l'ancienne clé doit déjà être approuvée pour un appareil. Les CLI épinglent la première clé de téléphone approbatrice (hosted-device-trust.json, confiance à la première utilisation) et n'acceptent que les signataires d'enveloppe accessibles depuis cette racine via des renouvellements vérifiés. - Stockage adossé au matériel. La graine du téléphone est stockée avec
flutter_secure_storageen utilisant les options intégrées les plus fortes : accessibilité Keychain iOS/macOSpasscode(cet appareil uniquement, sans synchronisation, nécessite un code d'appareil) ; stockage enveloppé par Android Keystore avec contrôle biométrique ou identifiant d'appareil etresetOnError: false. Limites : la graine est toujours exportée dans la mémoire de l'application pour signer/sceller (Secure Enclave / StrongBox ne peuvent pas effectuer X25519/Ed25519 nativement), donc cela protège au repos, pas contre une application en cours d'exécution compromise. - Journal de transparence. Center conserve un journal d'appareils en ajout seul, chaîné par hachage (
device.pair,device.approve,identity.rotate,device.revoke) àGET /v1/devices/log. Chaque entrée intègre l'autosignature d'association du CLI, l'attestation du téléphone ou l'enregistrement de renouvellement.memories device hosted log --verifyvérifie les liens de chaîne, chaque signature et que le journal prolonge la tête vérifiée la dernière fois (hosted-device-log.json) ; un historique réécrit est signalé comme une erreur.