Centro de autorización
Cuentas de autoservicio y SMTP
Pase --auth-config /etc/memories-center/auth.toml (o establezca
MEMORIES_CENTER_AUTH_CONFIG) en modo alojado. Consulte
deploy/center/auth.example.toml.
Sin una configuración de autenticación, Center conserva su comportamiento basado exclusivamente en tokens. Con una configuración,
se habilita el inicio de sesión de cuentas; el registro está deshabilitado de forma predeterminada y usa el modo de correo electrónico.
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"
Las contraseñas SMTP se leen de la variable de entorno indicada. Mantenga esa variable
en el entorno protegido del servicio, nunca en argumentos, URL, el control de versiones
ni la configuración del navegador. Un relay sin autenticación puede omitir username;
TLS con verificación de certificados sigue siendo obligatorio. Una configuración inválida impide
el arranque. La ausencia de SMTP deshabilita el registro por correo; un fallo de transporte devuelve 503
y no crea ninguna cuenta. La respuesta de configuración pública nunca incluye ajustes SMTP.
Establezca registration_enabled = false para cerrar nuevos registros sin deshabilitar
las cuentas existentes. Establezca registration_mode = "username" para el registro con usuario y contraseña
sin correo electrónico; omita [smtp] si no se utiliza. Reinicie después de cambiar la configuración.
Las cuentas existentes de ambos tipos pueden seguir iniciando sesión tras un cambio de modo.
Endpoints públicos (JSON, cuerpo de solicitud máximo de 4 KiB):
| Endpoint | Cuerpo / comportamiento |
|---|---|
GET /v1/auth/config |
Disponibilidad de inicio de sesión, disponibilidad/modo de registro, mínimo de contraseña, duración de sesión |
POST /v1/auth/register |
Modo de correo: {identity,locale?} envía un código. Modo de usuario: {identity,password} crea una cuenta |
POST /v1/auth/verify |
{identity,code,password} demuestra la propiedad del buzón y crea una cuenta |
POST /v1/auth/login |
{identity,password} devuelve una concesión de portador con ámbito y duración de una hora |
POST /v1/auth/logout |
Autorización de portador; revoca esa concesión con caducidad y sus descendientes |
Se eliminan los espacios de los extremos de las direcciones de correo y los nombres de usuario y se normalizan las mayúsculas y minúsculas ASCII. El registro por correo solo acepta direcciones de buzón ASCII. Los nombres de usuario tienen entre 3-64 letras ASCII, dígitos, guiones o guiones bajos. Las contraseñas tienen 12-256 bytes UTF-8 y se almacenan como registros PHC Argon2id con sal (64 MiB, 3 pasadas, paralelismo 1). El cálculo de hashes se ejecuta fuera del bloqueo de la bóveda, con un máximo de dos trabajadores simultáneos. Los usuarios no pueden seleccionar una bóveda, un ámbito ni un rol durante el registro: cada uno recibe una bóveda alojada aleatoria e independiente.
Los códigos de verificación contienen 256 bits aleatorios, caducan a los 15 minutos y son
de un solo uso. Solo se guardan hashes en la memoria del servidor; un reinicio invalida los códigos
pendientes. Los envíos duplicados dentro de ese intervalo devuelven la misma aceptación genérica
sin otro correo; vuelva a intentarlo tras la caducidad. Las contraseñas se eligen durante la verificación,
no al solicitar el correo. Los registros pendientes tienen un límite de 1024.
El campo opcional locale selecciona el asunto y el cuerpo del correo: en, zh-CN, zh-TW,
ja, fr, de o es. Las variantes regionales usan su idioma base; zh-Hant,
zh-HK y zh-MO seleccionan chino tradicional, mientras que zh-Hans explícito selecciona
chino simplificado. Las configuraciones regionales ausentes o no admitidas usan inglés como alternativa para
los clientes API existentes. La configuración regional es por solicitud y no se almacena en la cuenta.
Cambiar de idioma durante un desafío pendiente no envía otro correo
ni elude su límite de frecuencia. No cambia la normalización ni la verificación de la identidad.
Los intentos de autenticación tienen un límite global de 60/minuto y límites por identidad de 3
solicitudes de registro o 10 intentos de inicio de sesión/verificación por cada 15 minutos. Estos
límites locales del proceso se restablecen al reiniciar y pueden causar denegación de servicio durante
un ataque; use un servicio de control de abusos situado delante para despliegues de mayor volumen.
No confían en las cabeceras IP proporcionadas por el cliente.
Los tokens de miembros persistentes existentes siguen siendo compatibles. El formato members.json ahora
tiene credenciales de cuenta opcionales: proteja sus copias de seguridad como bases de datos de contraseñas.
Las compilaciones antiguas de Center no pueden leer registros de cuentas; revertir tras el registro
requiere un binario compatible o una copia de seguridad de datos anterior a la actualización que corresponda (lo que
descartaría los datos posteriores). El restablecimiento de contraseñas, los cambios de buzón y la recuperación por correo no
están implementados. La eliminación administrativa de un miembro deshabilita el inicio de sesión e invalida las sesiones.
Un Center es un plano de control de autorización externo que distribuye tickets de inscripción y capacidades a los endpoints de Memories (CLI, daemon de escritorio, móvil y otros clientes que comparten configuración).
Los Centers alojados también admiten Memories Web, un cliente de navegador para
memorias, tarjetas y archivos. Las nuevas compilaciones lo sirven en /app/. Para conectarse desde el
sitio web público, permita explícitamente --web-origin https://memories.ivjn.us (o
MEMORIES_CENTER_WEB_ORIGINS). El uso del mismo origen no requiere ajustes CORS; la autorización
existente de miembros/concesiones sigue aplicándose. Consulte la guía para la configuración y los límites.
Reglas
- Los endpoints pueden configurar uno o más Centers en
config.toml([[centers]]). - Si se configura cualquier Center, el endpoint está administrado por Center:
- Se prohíbe la inicialización autónoma local (
memories init/restore). - La creación de dispositivos usa un ticket de inicialización firmado por Center (memories center enroll). - Se prohíbe la emisión local de concesiones MCP; importe en su lugar tickets de concesión firmados por Center. - El desbloqueo criptográfico de la bóveda del dispositivo sigue siendo local (contraseña del dispositivo). Un Center solo de tickets nunca recibe claves de bóveda del dispositivo ni secretos en texto claro. El modo alojado opcional posee, en cambio, una bóveda de servidor independiente cuya clave controla el operador (véase más abajo). Un ticket de desbloqueo firmado opcional autoriza un desbloqueo local con contraseña; no contiene ni la contraseña ni las claves del dispositivo.
- Se pueden listar varios Centers; un ticket debe verificarse con el
idy lapublic_keydel Center que lo emitió. - Los tokens de escucha, secretos de firma de Center y tokens de administrador nunca se almacenan en TOML.
Ámbitos de concesión
Los tickets de concesión MCP llevan un ámbito explícito (nivel de permiso). Los identificadores del protocolo y la configuración usan snake_case; la CLI y HTTP también aceptan alias legibles.
| Ámbito | Alias (ejemplos) | Permite |
|---|---|---|
allow_all |
allow all, all, full |
Conjunto completo de operaciones que esta interfaz de concesión puede autorizar (actualmente: lectura + creación/actualización + eliminación de activos ordinarios; reservado para futuras capacidades elevadas controladas por Center en la misma interfaz) |
read |
r |
Listar / obtener / buscar / recuperar / lectura RAG / grafo ordinario — sin modificaciones |
write |
w |
Solo crear/actualizar memorias y tarjetas ordinarias (no leer, no eliminar) |
read_write |
read+write, rw |
Leer + crear/actualizar |
read_write_delete |
read+write+delete, rwd |
Leer + crear/actualizar + eliminar memorias/tarjetas ordinarias |
Notas:
writees deliberadamente de solo escritura para que los Centers puedan emitir agentes con ámbitos limitados. Prefieraread_writecuando el agente también necesite buscar o leer.- La eliminación abarca las eliminaciones ordinarias (no privadas, de memoria/tarjeta) aplicadas a los llamantes MCP; las eliminaciones privadas/de credenciales siguen requiriendo elevación local y quedan fuera de las concesiones MCP.
- Tickets heredados que solo tienen el antiguo campo
write: bool(sinscope):write: false→read,write: true→read_write. - Los tickets nuevos sin un ámbito explícito usan
readde forma predeterminada al emitirse (CLI / HTTP dememories-center). - Estas restricciones de activos ordinarios/elevación describen MCP del daemon, no RPC de
la bóveda alojada. Las concesiones alojadas también cubren tarjetas privadas, adjuntos y perfiles protegidos;
su autoridad exacta se describe más abajo. Esta función no eleva MCP del daemon,
incluso cuando su ámbito es
allow_all.
La importación local de tickets ahora comprueba la pertenencia Admin/Owner y el límite de rol/ámbito, no solo el acceso de escritura, y no requiere elevación de terminal. Consulte autorización de bóvedas para conocer el comportamiento local de delegación, solicitudes, acceso y auditoría y el despliegue por etapas de Center/CLI/MCP.
Adecuación al modelo de amenazas
| Aspecto | Comportamiento |
|---|---|
| Inscripción de dispositivos | Center decide qué dispositivos pueden inicializar un almacén |
| Concesiones MCP / agentes | Center emite tickets de concesión de duración limitada con ámbitos; los endpoints los importan |
| Confidencialidad de la bóveda del dispositivo | Sigue usando Argon2 + cifrado del almacén local; Center solo de tickets es política, no custodia de claves |
| Confidencialidad de la bóveda alojada | Store cifrado en el servidor; los operadores del host poseen la contraseña y pueden leer todo el texto claro alojado; no es cifrado de extremo a extremo |
| Clave de firma de Center comprometida | Un atacante puede inscribir dispositivos / emitir concesiones para ese id de Center — rote la clave y elimine la public_key obsoleta de los endpoints |
| Administrador de Center comprometido | Puede emitir tickets de desbloqueo de dispositivos de duración limitada, bloquear el Store alojado y emitir/actualizar/eliminar miembros alojados; no puede descifrar un Store de dispositivo solo de tickets sin su contraseña local |
| Endpoint comprometido | Posee material de la bóveda tras el desbloqueo; Center no puede revocar el texto cifrado ya presente en disco (igual que actualmente) |
Configuración del endpoint
[[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 = "…"
Las URL de Center que no sean de loopback deben usar https://.
Las URL de conexión SaaS/CLI directa deben contener solo el origen, sin información de usuario, rutas,
consultas ni fragmentos; la configuración en modo de tickets conserva la compatibilidad existente con URL.
Conexión del cliente SaaS
Establezca exactamente un Center configurado en mode = "saas" para usar su backend de contenido
alojado en lugar de una bóveda local paralela:
[[centers]]
id = "corp"
url = "https://center.example.com"
public_key = "…"
mode = "saas"
Inyecte MEMORIES_CENTER_TOKEN="…" en el entorno del daemon: se trata de un
miembro alojado, una concesión estática o un portador de acceso de duración limitada, nunca del token de administrador.
Inicie memories daemon con esa configuración. Las solicitudes de contenido del daemon se envían a
/v1/rpc de ese Center mediante el cliente TLS/HTTP del núcleo, fuera del bloqueo de escritura
local. Las operaciones alojadas no admitidas fallan de forma cerrada; no recurren al almacenamiento
local. Use el nombre de la bóveda vinculada (por ejemplo, default) y los ID canónicos de activos
que devuelve RPC alojado. El estado local/listado de Centers sigue siendo local. Init, restore,
la emisión MCP local y la inscripción local siguen prohibidos; una base de datos local antigua no es
el backend de contenido de esta conexión. El contenido y los permisos pertenecen a
las concesiones/miembros del Center alojado. Pueden coexistir otros Centers solo de tickets.
La URL configurada del daemon es la fuente autorizada (no hay anulación de URL mediante el entorno). El portador del daemon se comparte entre los clientes autenticados del daemon; proteja el acceso IPC y cualquier token de escucha/relay del daemon como delegación de la autoridad de ese miembro. Las capacidades MCP locales no se reenvían con el portador del daemon; use un miembro alojado con un ámbito independiente y un adaptador HTTP/MCP directo. No se añaden herramientas MCP de administración de Center.
Para las llamadas HTTP directas de la CLI, la selección de URL sigue este orden: --center-url, después
MEMORIES_CENTER_URL y después el Center SaaS configurado. Las llamadas administrativas requieren
MEMORIES_CENTER_ADMIN_TOKEN; RPC de contenido requiere MEMORIES_CENTER_TOKEN.
Ninguno de estos tokens de portador debe estar en TOML. Los ajustes TLS reutilizan --ca-file /
--insecure; no deshabilite la verificación TLS en producción. No se siguen
redirecciones. La herramienta directa no requiere un daemon en ejecución:
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"
Las concesiones MCP locales (sin Center) aceptan los mismos ámbitos:
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
Servicio 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 sirve HTTP sin cifrar sobre TCP. --listen https://HOST:PORT solo selecciona
la dirección de enlace; no habilita TLS nativo. Para la exposición pública, vincule
Center a loopback o a una dirección interna protegida detrás de un proxy inverso TLS,
y configure los clientes con la URL pública https:// del proxy. Proteja el token de administrador
y el secreto de firma; no exponga directamente el listener HTTP sin cifrar.
GET /healthGET /v1/public-keyPOST /v1/tickets/bootstrap(token de portador de administrador)POST /v1/tickets/mcp-grant(token de portador de administrador)POST /v1/tickets/unlock(token de portador de administrador;{"ttl":300}, 1–3600 segundos)
Cuerpo de concesión (JSON):
{
"vault": "…",
"scope": "read_write",
"minutes": 30,
"ttl": 3600
}
scope acepta los mismos nombres de protocolo y alias que la CLI. Cuando se omite scope,
write: true se asigna a read_write; en caso contrario, el valor predeterminado es
read. Las concesiones alojadas estáticas pueden omitir el ámbito (TOKEN o TOKEN::VAULT), y
la creación de miembros usa read de forma predeterminada si se omite el ámbito. Solicite allow_all
explícitamente en lugar de confiar en el antiguo valor predeterminado permisivo.
Bóveda alojada completa (SaaS)
Habilite la bóveda alojada con --data DIR (o MEMORIES_CENTER_DATA). Proporcione una
MEMORIES_CENTER_VAULT_PASSWORD no vacía mediante el entorno protegido o la inyección de secretos
del gestor de servicios. Deliberadamente no hay un indicador de contraseña en la línea de comandos.
La contraseña solo es obligatoria con --data; perderla implica perder el acceso al Store.
Use una contraseña fuerte generada de forma independiente, no un token de portador ni una clave de firma.
# 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
Desde un checkout, anteponga cargo run -p memories-center -- a las opciones.
Sin --data, el proceso sigue siendo un Center solo de tickets compatible con versiones anteriores.
Los tickets y la inscripción siguen funcionando en ambos modos.
El modo alojado reutiliza memories_core::service::Service, Request, Content y el
Store SQLite cifrado del daemon. Puede sustituir a Center + daemon para el contenido de
bóvedas alojadas, incluido:
- Campos completos de memorias/tarjetas: cuerpos, etiquetas, fuentes, alias, notas, categorías, indicador de privacidad, enlaces, hosts permitidos y referencias a secretos.
- Tarjetas de cerebro, persona, flujo de trabajo y plantilla de flujo de trabajo; perfiles protegidos de cuenta, entorno,
token, inicio de sesión, TOTP/HOTP y
otpauth. - DAG de revisiones firmadas, historial, diferencias, lecturas históricas y reaplicación; enlaces y grafos de tarjetas dentro de la misma bóveda.
- Instantáneas del mundo de persona, rotación, tarjetas de archivo, cosas/atributos, conversaciones de chat/cursores de lectura, hooks de automatización, listas de permitidos y disparadores.
- Adjuntos de archivos y credenciales cifrados, sustitución de secretos protegidos, entrega explícita de credenciales y generación de OTP.
Almacenamiento y migración
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 también puede crear sus archivos normales de diario transaccional. Los guardados del Store son
transaccionales; la identidad de la bóveda, las revisiones firmadas y el estado de auditoría persisten juntos.
Un único proceso de Center posee el Store en cada momento. La bóveda predeterminada y las bóvedas con nombre
usadas por las concesiones configuradas o emitidas se crean cuando es necesario. Los vínculos de concesión usan
ID canónicos de bóveda internamente y no pueden enumerar/acceder a otras bóvedas.
Los directorios Unix son exclusivos del propietario (0700); los archivos Store y la copia heredada son
exclusivos del propietario (0600). Proteja el directorio y la cuenta de servicio en todas las plataformas.
Si existe DIR/memories.json, el arranque importa sus notas ordinarias, conservando los ID
y nombres de bóveda, en una sola transacción atómica de contenido; después cambia el nombre de la entrada a
memories.json.bak. Los registros inválidos hacen fallar el arranque sin descartar la fuente
ni publicar un lote parcial de notas. Un reintento tras un cambio de nombre interrumpido omite
los registros importados idénticos; los registros en conflicto o una copia de seguridad ya existente
provocan un fallo cerrado para revisión del operador en lugar de sobrescribir datos. La copia heredada
sigue siendo texto claro: elimínela de forma segura o cífrela de forma independiente después de verificar
la importación y organizar una copia de seguridad. El contenido nuevo nunca se guarda como JSON.
Cifrado y modelo de amenazas
El Store se desbloquea al iniciar el proceso con la contraseña en poder del operador (envoltura de contraseña Argon2 y el mismo cifrado autenticado que el Store local). En cada operación de contenido autorizada, el despacho alojado actualiza la sesión del servidor antes de comprobar la caducidad por inactividad; no se bloquea por inactividad tras quince minutos sin actividad. El apagado y el bloqueo administrativo liberan las claves del Store en memoria y el contenido en caché. El bloqueo/desbloqueo administrativo está en rutas HTTP administrativas separadas, nunca en RPC de contenido de concesiones. No hay RPC alojadas de sudo ni de cambio de contraseña.
Esto es cifrado del lado del servidor, no cifrado de cliente de extremo a extremo. Los operadores
que controlan el host/proceso/contraseña pueden leer todas las bóvedas alojadas, incluido
el contenido privado y las credenciales. Cualquiera que posea tanto la contraseña como una copia en disco
puede descifrar el Store. La memoria del host, los volcados de fallos, la inspección del entorno,
los clientes autorizados maliciosos y una terminación TLS comprometida quedan fuera de este
límite de confidencialidad. private sigue controlando los resúmenes seguros/la recuperación, pero
no es una barrera frente a una concesión alojada autorizada para leer esa bóveda.
El daemon, en cambio, usa una contraseña de dispositivo y puede usar claves locales selladas por el SO para funcionamiento desatendido. El modo alojado no tiene claves de dispositivo selladas ni una experiencia de contraseña por dispositivo. Las claves de bóveda de los dispositivos no se cargan ni comparten automáticamente con este Store alojado. Use el proxy inverso TLS descrito arriba; las solicitudes HTTP y la entrega autorizada de secretos son texto claro para Center y el terminador TLS. No registre tokens de portador, cuerpos RPC, respuestas de credenciales ni contraseñas.
Bloqueo/desbloqueo remoto
Las rutas alojadas requieren el portador de administrador y solo existen con --data:
| Ruta | Comportamiento |
|---|---|
GET /v1/admin/status |
Devuelve unlocked, modo alojado, número de miembros y restart_required, sin secretos |
POST /v1/admin/lock |
Descarta las claves/estado del Store, vacía cachés y suspende las concesiones hasta el desbloqueo |
POST /v1/admin/unlock |
Acepta {"password":"…"} y vuelve a abrir el mismo Store existente con la contraseña del operador |
Mientras está bloqueado, las rutas de contenido y la emisión de tokens de acceso alojados devuelven 503;
ninguna concesión puede servir texto claro. El estado administrativo, el listado/eliminación de miembros y la emisión de tickets
de dispositivo siguen disponibles. La creación/actualización de ámbito/bóveda de miembros requiere un
Store desbloqueado. Las contraseñas alojadas incorrectas devuelven 403; la autenticación fallida usa
la espera exponencial del núcleo (los intentos posteriores durante la espera devuelven 429). Un
desbloqueo correcto restaura el acceso estático/de miembros, no los tokens emitidos revocados.
El texto claro devuelto por solicitudes ya completadas antes del bloqueo no puede
retirarse. El arranque sigue requiriendo MEMORIES_CENTER_VAULT_PASSWORD; el desbloqueo remoto
no conserva la contraseña suministrada ni cambia la configuración de arranque.
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
El desbloqueo de dispositivos es diferente: POST /v1/tickets/unlock está disponible tanto en
Centers solo de tickets como alojados. El administrador firma un ticket unlock de corta duración;
el daemon local verifica su tipo, firma, Center configurado y caducidad,
y después desbloquea con la contraseña de dispositivo que se solicita localmente al operador. No concede
elevación sudo/MCP. memories center unlock --ticket siempre usa IPC local
autenticado por el SO, no el daemon remoto seleccionado ni la API HTTP de Center.
No hay entrega automática, custodia ni canal push de contraseñas de dispositivo.
# 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
Los tickets de desbloqueo se pueden reutilizar hasta que caducan y autorizan el desbloqueo en endpoints que confían en ese Center, sujetos a la contraseña de cada endpoint; no son de un solo uso ni están vinculados a un dispositivo. El desbloqueo local con contraseña existente sigue disponible en dispositivos administrados solo de tickets. Las conexiones SaaS no pueden usar un ticket para inscribir ni desbloquear un Store de contenido local paralelo.
Custodia de contraseñas: el operador alojado posee la contraseña del Store alojado y la envía al Center/terminador TLS de confianza solo para el desbloqueo alojado. Las contraseñas de dispositivo y las claves de bóveda permanecen en el dispositivo/daemon local en modo de tickets. Un Center comprometido puede emitir tickets de desbloqueo, pero sigue necesitando una contraseña de dispositivo para desbloquear su texto cifrado. Un administrador alojado comprometido puede bloquear el Store y emitir miembros con autoridad sobre texto claro siempre que esté desbloqueado. Los operadores alojados ya controlan el proceso y pueden leer todo el texto claro desbloqueado; la separación entre administrador y miembro no es cifrado frente al operador.
Miembros persistentes
Los miembros alojados son principales de portador persistentes con nombre y los mismos cinco
niveles de GrantScope, no cuentas de proveedor de identidad. Un role opcional es
reader, editor, admin u owner; los roles omitidos conservan la asignación heredada
de read, write/read_write, read_write_delete y allow_all, respectivamente.
Un rol explícito limita el ámbito del miembro (Reader: read, Editor: read_write,
Admin: read_write_delete, Owner: allow_all). Las rutas CRUD de miembros siguen requiriendo
el portador de administrador del operador, no el rol Admin/Owner de un miembro:
| Ruta | Cuerpo / respuesta |
|---|---|
GET /v1/admin/members |
Matriz members de id, bóveda, ámbito, rol, creación y actualización; sin tokens de portador |
POST /v1/admin/members |
{"id":"agent","vault":"default","scope":"read"}; metadatos más el token generado, mostrado solo en esta respuesta |
PUT /v1/admin/members/{id} |
Ámbito, bóveda y/o rol opcionales; mismo portador, respuesta solo con metadatos |
DELETE /v1/admin/members/{id} |
Elimina el miembro e invalida inmediatamente el portador |
Los ID son nombres estables de 1–64 caracteres (letras/dígitos ASCII, -, _); center-admin
está reservado al Owner del operador. Los ID duplicados
devuelven 409. Los tokens son aleatorios, únicos y distintos de los tokens de administrador/estáticos.
Use una terminal o destino de salida protegido para crear miembros; la CLI imprime
el portador devuelto una sola vez de forma deliberada. Nunca capture respuestas de creación en registros.
Los miembros se autentican mediante la misma tabla de concesiones en /v1/memories y /v1/rpc;
los cambios de ámbito/bóveda se aplican a las solicitudes posteriores sin reiniciar. Las solicitudes
autenticadas antes de una modificación de pertenencia intermedia se rechazan antes
del despacho de contenido. Eliminar/recrear un miembro es el procedimiento de rotación de tokens.
DIR/members.json es un archivo persistente reemplazado atómicamente y exclusivo del propietario (0600 en Unix)
dentro del directorio alojado exclusivo del propietario. Contiene los tokens de portador en
texto claro, pero no contraseñas, claves ni contenido de bóveda; protéjalo como credenciales,
inclúyalo en copias protegidas y no lo incorpore al repositorio. El arranque recarga/valida
los miembros y falla de forma cerrada ante portadores corruptos o en colisión. Un fallo al guardar miembros persistentemente
bloquea el contenido y establece restart_required: el desbloqueo remoto devuelve 503 hasta
que el operador repare el sistema de archivos y reinicie para recargar la autorización persistente.
Esto impide que resultados de persistencia inciertos resuciten portadores revocados.
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
Esto sigue siendo un único Store cuya clave controla el operador, con vinculación lógica de concesiones por bóveda, no aislamiento estricto entre inquilinos, OIDC/SSO, contraseñas de usuario, tokens de actualización, cuotas ni recuperación de autoservicio.
Concesiones y API
Las concesiones estáticas usan --grant TOKEN[:SCOPE[:VAULT]] repetible y/o
MEMORIES_CENTER_GRANTS separado por comas; omitir el ámbito significa read y omitir la bóveda
significa default (TOKEN::VAULT elige una bóveda con el ámbito predeterminado). Su duración es
la del proceso. POST /v1/tickets/mcp-grant autenticado por administrador devuelve el
ticket firmado sin cambios más un access_token alojado vinculado a su bóveda/ámbito.
Ese token de acceso caduca tras minutes (1–60) y sobrevive al reinicio alojado.
El ttl del ticket gobierna por separado la validez de importación del ticket. Las concesiones alojadas emitidas
persisten atómicamente en DIR/grants.json, exclusivo del propietario (0600): solo hashes SHA-256
de portadores y metadatos de autorización, nunca tokens emitidos en texto claro. Los hashes de portador
se comparan en tiempo constante. Las revocaciones persisten, y la eliminación de miembros
sigue propagándose por la ascendencia de concesiones restaurada tras reiniciar. Las concesiones caducadas
y sus descendientes se purgan al cargar, cada minuto y durante
la autenticación/emisión. Trate este archivo de autorización controlado por el propietario y sus
copias de seguridad como sensibles para la seguridad; revertirlo o revertir el Store puede restaurar estados antiguos.
Un fallo de persistencia bloquea el servicio y requiere reparación/reinicio.
Bloquear el Store alojado descarta sus claves e inutiliza las concesiones sin
revocarlas; las concesiones emitidas no caducadas, las estáticas y los miembros persistentes se reanudan
tras desbloquear. Cada miembro persistente tiene un contador de generación persistido. Las generaciones del emisor y
destinatario se fijan al emitir y nunca se actualizan mediante
la autenticación; el campo público generation es la generación del destinatario al
emitirse. Una actualización de ámbito/rol/bóveda o una eliminación incrementa solo la generación de ese miembro;
los contadores de miembros eliminados se conservan para que recrear un ID no los restablezca.
La eliminación/actualización revoca explícitamente las concesiones emitidas por ese miembro o para él y todos
sus descendientes de forma transitiva; las concesiones no relacionadas siguen siendo utilizables, incluso tras
reiniciar. Las credenciales estáticas/persistentes se restablecen en la transición explícita de política,
no durante la autenticación. Los ID de principales estáticos y padres de miembros persistentes
son estables entre reinicios, por lo que las cadenas de delegación restauradas permanecen conectadas.
El portador de administrador por sí solo recibe 401 en /v1/memories y /v1/rpc, incluso para
lecturas. No puede modificar directamente el contenido de bóveda. Los administradores aún pueden emitir
concesiones separadas mediante la API de tickets o autorización; esa capacidad administrativa no es
una barrera de confidencialidad frente al administrador.
API HTTP de autorización
Estas rutas solo existen en modo alojado. Autentican el portador de administrador del operador (un Owner en todas las bóvedas) o el portador de un principal alojado. El operador controla el bloqueo/desbloqueo del servidor y el CRUD de miembros; un Admin/Owner alojado puede gestionar la autorización solo dentro de la bóveda y el ámbito del portador. No se necesita elevación de terminal. Los Readers y Editors no pueden emitir ni delegar, incluso si reciben un acceso de contenido más amplio. Las infracciones de rol/ámbito/bóveda/caducidad devuelven 403 con un error claro de autoridad; los fallos de validación del manejador devuelven 400, los objetos ausentes 404, las decisiones repetidas 409 y los portadores caducados/revocados/de generación obsoleta 401. JSON malformado o con tipos incorrectos puede ser rechazado por el extractor JSON de Axum con 400 o 422.
| Ruta | Campos y respuesta |
|---|---|
GET /v1/access/{vault} |
Admin/Owner; vault, principals agrupados en owner, admin, editor, reader, grants activos con caducidad, más pending_requests y approved_unclaimed_requests (id, requester, scope, lista approvers o "broadcast", created y expires en segundos Unix) |
POST /v1/grants |
Admin/Owner; vault, scope opcional (predeterminado read), duration_minutes (predeterminado 30, alias minutes), grantee; metadatos de concesión más token de una sola entrega |
GET /v1/grants |
vault opcional; Admin/Owner ven todas las concesiones emitidas en la bóveda; los demás principales actuales solo ven sus propias concesiones emitidas; matriz grants, excluyendo credenciales estáticas/persistentes y concesiones caducadas/obsoletas |
DELETE /v1/grants/{id} |
Emisor o Admin/Owner dentro de la bóveda; revoca la concesión emitida y sus descendientes, devuelve revoked y count |
POST /v1/requests |
Cualquier miembro; vault, scope opcional (predeterminado read), reason, duration_minutes (predeterminado 30, alias duration), approvers; metadatos de solicitud pendiente |
GET /v1/requests |
vault opcional; solicitudes pendientes del solicitante o de un aprobador elegible, más las solicitudes aprobadas sin reclamar del solicitante; sin tokens |
POST /v1/requests/{id}/approve |
Admin/Owner elegible; sin cuerpo; solo metadatos de request aprobada sin reclamar y límites validados, sin concesión ni token |
POST /v1/requests/{id}/claim |
Solo portador de miembro persistente del solicitante; sin cuerpo; request más grant recién emitida con token de una sola entrega |
POST /v1/requests/{id}/reject |
Admin/Owner elegible; reason JSON opcional; request con estado rejected |
POST /v1/requests/{id}/cancel |
Solo solicitante; sin cuerpo; request con estado cancelled |
GET /v1/audit |
Admin/Owner; vault opcional, format=json|jsonl (predeterminado json); entries/verified verificados, o exportación delimitada por saltos de línea |
Todas las duraciones de emisión/solicitud son de 1–60 minutos. Puede haber como máximo 10,000 concesiones
activas a la vez (memories_core::service::MAX_ACTIVE_GRANTS); el daemon local
usa el mismo límite. Los emisores no pueden superar el límite de su rol
ni el ámbito de su portador, salir de su bóveda ni prolongar la caducidad de su padre.
Los destinatarios son, de forma predeterminada, el miembro emisor; los nombres grantee explícitos deben ser principales
existentes de la bóveda seleccionada. El destinatario reservado center-admin siempre se
rechaza con HTTP 403; las concesiones persistidas anteriormente con ese sujeto tampoco
son utilizables. La emisión del operador sin destinatario (incluida la emisión alojada
de tickets) crea un principal anónimo cuyo sujeto es su ID de concesión,
no center-admin. Ningún destinatario puede tener un rol superior al rol
efectivo del emisor, incluso para una concesión de solo lectura.
Tanto Center como el daemon definen pub const MAX_DELEGATION_DEPTH: u32 = 2.
La emisión directa de un miembro/operador tiene profundidad 1; usar una concesión emite a
parent.depth + 1, y la profundidad 3 devuelve HTTP 403. Las credenciales de miembros persistentes son
autoridad directa del miembro (profundidad interna 0); las concesiones que emiten tienen profundidad 1 y
ninguna concesión padre. Las concesiones heredadas persistidas sin depth se reconstruyen a partir de
su cadena de padres completa, incluidas las credenciales estáticas y persistentes configuradas.
Una ascendencia ausente/cíclica o que supere el límite es inutilizable y se purga al arrancar.
La autoridad de concesión con subject != issued_by es un llamante de concesión designado.
Estos llamantes no pueden aprobar, rechazar, cancelar ni reclamar ninguna solicitud de permiso,
ni siquiera las propias (HTTP 403); no son aprobadores elegibles. Pueden crear
solicitudes, pero deben usar sus credenciales reales de miembro para los flujos de permisos.
La emisión delegada conserva al emisor real para que un portador designado no pueda
convertirse indebidamente en una concesión autoemitida. Las concesiones autoemitidas conservan sus reglas
existentes de solicitudes, y las reclamaciones siguen requiriendo el portador de miembro persistente del solicitante.
Las aprobaciones heredadas diferidas realizadas con una concesión de destinatario designado no pueden autorizar
una reclamación; obtenga una nueva aprobación de una autoridad de miembro elegible.
Cambios incompatibles: se rechazan los destinatarios de rol superior y center-admin, las decisiones/cancelaciones/reclamaciones
de permisos con destinatario designado y la delegación de más de dos concesiones.
Los sujetos emitidos por el operador ahora son anónimos en lugar de
center-admin; las raíces emitidas por miembros ya no incluyen la credencial de miembro persistente
como padre de delegación. Vuelva a emitir una raíz adecuada o use una credencial de miembro
en lugar de depender de estos comportamientos anteriores.
Las concesiones registran id (un ID opaco independiente),
subject, scope, vault, expiry en segundos Unix, parent, issued_by,
generation, depth y role. Los ID de principales estáticos son static:OPAQUE_ID.
GET /v1/grants devuelve solo concesiones emitidas de duración limitada con id, parent,
issued_by, grantee, scope, vault, expiry en segundos Unix, generation,
depth y created en segundos Unix cuando está disponible. Nunca devuelve un token ni
un hash de portador.
grantee es el destinatario (subject en las respuestas de emisión/acceso).
La generación sigue siendo la generación al emitir. Esta vista de concesiones emitidas difiere
de la vista de principales/acceso sin cambios de GET /v1/access/{vault}: las credenciales estáticas y
persistentes solo aparecen en esta última y se eliminan mediante
configuración/CRUD de miembros, no con DELETE /v1/grants/{id}. Los emisores pueden listar sus
propias concesiones; recibir una concesión por sí solo no la hace visible a un destinatario
que no sea administrador. Los portadores alojados están restringidos a su bóveda; omitir vault
selecciona esa bóveda, mientras que el operador puede listar entre bóvedas.
memories center grant list [VAULT] devuelve esta misma respuesta de concesiones emitidas.
Las solicitudes con approvers vacío/omitido se difunden mediante listas de pendientes elegibles;
las listas no vacías contienen ID de aprobadores Admin/Owner con nombre (o center-admin). Tanto
la aprobación como el rechazo están limitados por el ámbito, bóveda, rol y
vigencia restante de la concesión del aprobador. El solicitante no puede aprobar ni rechazar su propia
solicitud, ni siquiera como Admin/Owner o aprobador designado; sí puede cancelarla.
Las decisiones se serializan bajo el mismo bloqueo de autorización y solo pueden ocurrir
una vez. Los estados de solicitud son pending, approved, rejected o cancelled.
La aprobación registra los parámetros aprobados de concesión y los límites de ámbito/bóveda/rol/
caducidad del aprobador, pero no emite nada ni devuelve tokens. El miembro solicitante
usa su credencial de miembro persistente para llamar a POST /v1/requests/{id}/claim, o
memories center request claim ID. Se rechazan los aprobadores, otros miembros y las concesiones emitidas
para el mismo sujeto solicitante. Las reclamaciones se serializan: solo una
tiene éxito y los intentos posteriores devuelven 409, incluso tras reiniciar.
La generación de la concesión y el reloj de creación/caducidad comienzan al reclamar; su caducidad está
limitada por la caducidad de la autoridad aprobadora registrada. Los padres caducados/revocados y
los cambios de generación del emisor/solicitante impiden reclamar. Solo se conserva el hash del token
con los metadatos de concesión. request.claim audita el actor solicitante y
el ID de concesión, nunca el token. Si se pierde la respuesta de una sola entrega, solicite una nueva aprobación.
Una concesión de contenido aprobada no cambia el rol de miembro de su solicitante ni
su autoridad de delegación.
Cambio incompatible: las respuestas de aprobación ya no contienen grant ni su token;
los clientes deben reclamar explícitamente con el portador de miembro solicitante. Las listas de solicitudes
muestran metadatos de aprobadas sin reclamar, no tokens. Las solicitudes aprobadas heredadas sin
límites de aprobación diferida no se pueden reclamar; cree una nueva solicitud.
Archivos de auditoría y solicitudes de Center
DIR/requests.json se reemplaza atómicamente con modo 0600 y almacena metadatos
de solicitudes y decisiones completadas entre reinicios. DIR/audit.jsonl se abre en
modo de adición con modo 0600, se sincroniza con fsync después de cada entrada y Center nunca
lo reescribe. Ambos están en el directorio alojado con modo 0700. Los motivos son metadatos
de auditoría: no incluya contraseñas ni otros secretos. Los registros de auditoría no
incluyen tokens de portador, contraseñas, claves ni contenido de bóveda.
Las entradas de auditoría exponen sequence, timestamp, prev_hash, hash, action
y los campos opcionales issued_by / via_grant para acciones autorizadas por concesión. El primero
es el emisor real; el segundo es el ID de concesión del llamante; actor sigue siendo el sujeto de
la concesión salvo que grant.issue registra al emisor real como actor y al
destinatario como sujeto. Los campos están ausentes de forma predeterminada y se omiten cuando no existen,
por lo que los registros antiguos conservan sus hashes existentes. Las auditorías firmadas de contenido alojado del Store
compartido también registran estos campos. Los campos existentes incluyen
actor, subject, scope, vault, expiry, parent, reason, request_id y
object. El primer prev_hash es null; los valores posteriores referencian el hash
anterior. SHA-256 cubre la tupla JSON (sequence, timestamp, prev_hash, event_fields).
verify_audit_chain(path) en crates/center/src/saas/audit.rs verifica continuidad,
hashes y líneas JSONL completas. El arranque verifica el archivo y concilia las decisiones
y reclamaciones con él, de modo que un fallo después de auditar una decisión o reclamación
pero antes de reemplazar los metadatos de solicitud no pueda permitir una repetición. Las adiciones y lecturas
verifican la cadena frente a la cabecera actual en el proceso; las lecturas de auditoría verifican toda la cadena
antes de aplicar un filtro de bóveda.
Las acciones son device.authorize (emisión de ticket de inicialización), grant.issue,
grant.revoke, member.add, member.set, member.remove, session.lock,
session.unlock, request.create, request.approve, request.claim,
request.reject y request.cancel. Las revocaciones por bloqueo y cambio de miembro
también se auditan. Las exportaciones JSONL
usan Content-Type: application/x-ndjson; las exportaciones filtradas conservan los
hashes/secuencias originales y no son cadenas completas independientes. Los llamantes Admin/Owner vinculados a bóveda
solo ven las entradas de su bóveda; el operador también ve eventos globales y
puede exportar el registro completo. Cualquier fallo al añadir auditoría o persistir solicitudes bloquea
el servicio, establece restart_required y deshabilita el desbloqueo remoto hasta reparar y
reiniciar. Las modificaciones de autorización y decisiones de permisos devuelven 503 mientras
restart_required está establecido; la inspección del operador de solo lectura sigue disponible.
Mantenga estos archivos en copias de seguridad protegidas, no en el control de versiones.
Esta es una cadena sin clave, no una firma de dispositivo ni un anclaje externo: un operador comprometido puede reescribirla/recalcularla, omitir eventos o revertir todos los archivos juntos. La truncación de toda la cola antes del arranque no se puede detectar sin una cabecera conocida externa. No trate la verificación de cadena como una auditoría de seguridad independiente.
Ámbito y API de contenido alojado
Los ámbitos de contenido alojado se aplican a la operación, no solo al método HTTP.
Las rutas de gestión de autorización comprueban además el rol del principal como
se describe arriba; el scope de contenido por sí solo nunca concede administración del host:
| Ámbito | Autoridad alojada en la bóveda vinculada |
|---|---|
read |
Listado/búsqueda/grafo seguros; lecturas autorizadas completas, lecturas de archivos, historial/diferencias/auditoría y credential_copy explícito |
write |
Creación/actualización de contenido, inscripción protegida/OTP, archivos/credenciales, escrituras de mundo/chat/automatización; sin lectura ni eliminación de contenido anterior |
read_write |
Ambos; también otp_code (requiere lectura + escritura porque HOTP avanza el estado) |
read_write_delete |
Ambos más eliminación de activos, cosas, atributos y hooks de automatización |
allow_all |
Todo lo anterior dentro de la interfaz alojada exclusiva de contenido, no administración de servidor/dispositivos |
A diferencia de MCP del daemon, el despacho alojado proporciona elevación local a la operación solo después de
superar la lista cerrada de operaciones permitidas, el ámbito y la vinculación de bóveda. check_mcp del daemon
y sus restricciones locales de elevación no cambian. Los agentes/adaptadores MCP alojados
pueden usar su portador con ámbito en toda la interfaz RPC de contenido; no se requiere elevación de terminal
local ni conexión al daemon. Las referencias de cerebro/archivo entre bóvedas
no pueden resolver contenido de otra bóveda mediante una concesión.
GET /v1/session→ bóveda, rol, ámbito, caducidad y capacidades de lectura/escritura/eliminación actuales, sin material de portador. Los tokens de solo escritura pueden inspeccionar su sesión; los tokens de administrador del operador no. Consulte modo alojado móvil.GET /v1/memories→{"memories":[...]}. Los registros de notas ordinarias no privadas conservanid,vault,title,body; los registros de tarjetas/privados/archivos/credenciales usan resúmenes seguros, nunca cuerpos de credenciales ni secretos resueltos.GET /v1/memories/{id}→ contenido completo seguro para el cliente +id,vault. Las credenciales requierencredential_copyexplícito en lugar de lecturas genéricas.POST /v1/memoriesyPUT /v1/memories/{id}aceptantitle/bodymás los metadatos completos deContent. PUT conserva los campos omitidos. Añadir metadatos de tarjeta en la creación infierekind: "card"si no se proporciona tipo. La creación de perfiles estructurados/protegidos usa su RPC dedicada, no PUT genérico. Un PUT de solo escritura devuelve únicamente los campos enviados y la identidad del registro, no campos privados existentes.- El
vaultJSON opcional /?vault=…debe coincidir con el nombre o ID canónico de la concesión; omitirlo selecciona la bóveda de la concesión.DELETE /v1/memories/{id}requiere eliminación. POST /v1/rpcacepta el JSONRequestdel daemon (open snake_case), devuelve su resultado JSON directo y comprueba cada operación de forma independiente. Use los ID canónicos de activo/revisión de las respuestas; vault acepta el nombre o ID canónico de la concesión.listsinvaultselecciona solo la bóveda de la concesión. Los errores usan HTTP 401 (token desconocido/de administrador/caducado), 403 (ámbito/interfaz/bóveda), 404 (ausente), 409 (conflicto), 400 (entrada inválida) o 422 (JSON/esquema de Request malformado).
Ejemplos (envíelos con el portador de la concesión, no con el portador de administrador):
{"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"}}
Los archivos/credenciales usan la convención existente de cuerpo base64 segura para URL, no un segundo
almacén de blobs. Sigue aplicándose el límite de 16 MiB del cuerpo de contenido del núcleo; el límite del cuerpo
JSON HTTP es 24 MiB. protect_secret mantiene atómicamente las referencias a credenciales.
Las lecturas/listados/diferencias genéricas nunca resuelven los bytes de credenciales almacenados; el
credential_copy autorizado es el endpoint explícito e intencional de entrega de texto claro.
archive_history/archive_read alojados direccionan revisiones firmadas de la bóveda activa
vinculada (incluidas revisiones históricas/con marcas de eliminación), no archivos de recuperación
del daemon. archive_audit direcciona la auditoría de esa bóveda. reapply alojado añade una
revisión histórica o en cuarentena firmada como nueva cabecera, incluidos los metadatos estructurados/
protegidos; reaplicar una marca de eliminación también requiere eliminación. A diferencia de inventar
referencias protegidas mediante PUT genérico, esto restaura referencias ya
autenticadas en el historial firmado de la bóveda. No resucita automáticamente una credencial
vinculada eliminada; reaplique por separado esa revisión de credencial si es
necesario.
Diferencias deliberadas respecto al daemon
POST /v1/bootstrapy/v1/restoresiempre devuelven 403: sigue siendo una biblioteca administrada por Center, no inscripción/restauración autónoma de dispositivos.- No hay sudo, inscripción de dispositivos dentro de contenido alojado, administración de miembros
de la política de bóveda, RPC de contenido para emitir/revocar concesiones, RPC de copia/restauración de todo el almacén, sesiones/
proxies remotos, sincronización cloud/relay, custodia desatendida, ejecución ni ejecución
de trabajos criptográficos. Estas solicitudes fallan de forma cerrada incluso con
allow_all. - El bloqueo/desbloqueo/estado alojado y la administración de miembros persistentes son rutas HTTP separadas
autorizadas por administrador, no capacidades conferidas por
allow_all. - No se exponen archivos de recuperación ni resolución de contenido entre bóvedas. Se admiten lecturas históricas/reaplicación de las revisiones propias de la bóveda alojada.
- La configuración/generación de modelos semánticos/RAG y la instalación de modelos no son
endpoints alojados. La búsqueda léxica
searchy la ruta síncronaretrievereutilizan la política de recuperación del núcleo (sin contenido privado ni cuerpos de archivos o credenciales). - La automatización se ejecuta mediante RPC explícita y los eventos existentes disparados por contenido; no hay planificador en segundo plano. Los webhooks salientes usan la lista exacta de URL permitidas del núcleo; trate a un escritor alojado capaz de configurar webhooks como autorizado para acceder a la red saliente y aplique restricciones de salida del operador para inquilinos no confiables.
- Los daemons configurados para SaaS y la herramienta CLI ligera usan
/v1/rpcalojado; el móvil nativo usa su ruta FFIhosted_*separada con una lista menor de contenido permitido. El IPC local solo de tickets permanece intacto. No hay servidor de herramientas administrativas MCP JSON-RPC.
La persistencia del Store sigue usando guardados transaccionales de todo el estado y operaciones serializadas del servicio; esto no es cifrado independiente por clave de inquilino ni una afirmación de aislamiento multiinquilino de producción/certificación de seguridad.
Móvil / FFI
La aplicación Flutter distribuida elige Standalone, Center o SaaS en su propia interfaz.
No carga el config.toml del daemon ni configura un servicio local administrado por
tickets. Su opción Center se conecta a un servidor autoalojado con almacenamiento
alojado, mientras que SaaS se conecta a https://memories.ivjn.us. Ambas usan las
operaciones FFI alojadas de más abajo.
El servicio compartido aplica las restricciones de Init/Restore y concesiones administradas por tickets cuando un llamante configura Centers en ese servicio. Esto es independiente del selector de modo del teléfono. FFI móvil sigue denegando Run/CryptoJob/gestión local de concesiones MCP/ remote/proxy y RPC locales de concesiones/solicitudes de permisos; seleccionar un modo alojado no amplía esa interfaz local.
Trabajo pendiente
Las aprobaciones de instrucciones remotas y otras interfaces de política elevadas podrían aceptar más adelante
tickets de aprobación firmados por Center. Hasta entonces, los endpoints administrados por Center siguen usando
sudo local para operaciones elevadas de bóveda distintas de la emisión de concesiones MCP y
la inicialización. allow_all está reservado para crecer con esas interfaces sin renombrar
ámbitos.
Límites, caducidad y retención de solicitudes de permisos
Se aplica tanto al daemon (solicitudes en el Store cifrado) como a Center (requests.json):
MAX_PENDING_PER_REQUESTER_PER_VAULT = 20yMAX_PENDING_PER_VAULT = 200cuentan solo las solicitudes pendientes. Las solicitudes decididas, canceladas, caducadas y aprobadas sin reclamar no cuentan para estos límites. (Esto reemplaza el antiguo límite global de 1000 solicitudes, que contaba todas las solicitudes realizadas.) Si se supera un límite, el daemon devuelve un error de límite y Center devuelve HTTP 429.PENDING_REQUEST_TTL = 7 days: una solicitud pendiente más antigua pasa al estadoexpired. Se audita comorequest.expirecon actorsystemy ya no se puede aprobar. Una solicitud de ámbito aprobada que no se haya reclamado en los 7 días posteriores a su aprobación también caduca del mismo modo y no se puede reclamar después.REQUEST_RETENTION = 30 days: las solicitudes rechazadas, canceladas, caducadas o reclamadas se eliminan transcurrido ese tiempo desde su última transición (la marca de tiempodecided, ocreatedpara registros antiguos que no la tengan).- La caducidad y la depuración son diferidas: el daemon las ejecuta al comenzar cualquier solicitud de autorización y Center las ejecuta al crear, listar, decidir y reclamar solicitudes. No se ejecutan durante el arranque.
- La conciliación del arranque de Center ignora las entradas de auditoría de solicitudes depuradas y acepta entradas
request.expire. Una solicitudexpireddebe tener una caducidad auditada. Los ID de solicitud son valores aleatorios de 128 bits que nunca se reutilizan, por lo que una solicitud depurada (incluida una reclamada) ya no existe y no se puede reclamar. Por ello, Center no mantiene un conjunto separado de marcas de eliminación.
Modo alojado móvil (cliente telefónico)
Los modos Center y SaaS de la aplicación móvil se conectan directamente a un Center que se ejecuta con
--data DIR; el teléfono no necesita una entrada mode = "saas" del daemon. Consulte
uso móvil para la primera conexión y los cambios,
y configuración para la migración de perfiles.
La API FFI memories_call acepta estas operaciones 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":{…}}usa la lista de contenido permitido de más abajo{"op":"hosted_status","session":N}se autentica medianteGET /v1/session{"op":"hosted_requests","session":N},hosted_approve/hosted_reject(id),hosted_access(vault){"op":"hosted_disconnect","session":N};memories_lock_alltambién descarta todas las sesiones alojadas
HostedClient::rpc en crates/core/src/center.rs permite list, read, search, delete,
type_list, type_show, type_create, type_update, type_delete,
card_create, card_fields, card_field, credential_set, credential_show
y put cuando content.kind es memory, card o credential. El servidor sigue aplicando la
bóveda, el rol y el ámbito del miembro. Esta lista FFI de permitidos no promete que cada
operación tenga una pantalla Flutter: el conocimiento alojado admite listado, búsqueda en servidor,
detalle, edición ordinaria de memorias/tarjetas, creación y eliminación confirmada. Los perfiles
estructurados, las tarjetas protegidas, las plantillas de tipo y las tarjetas selladas requieren editores
especializados. Las solicitudes de autorización y los controles de dispositivos emparejados siguen disponibles.
GET /v1/session devuelve vault, vault_id, scope, role, expires_at
(segundos Unix o null), can_read, can_write, can_delete y
server_readable. Acepta cualquier token activo de contenido con ámbito, incluidos
los de solo escritura, pero rechaza tokens de administrador del operador. No devuelve portadores ni
material secreto. Cada solicitud de contenido vuelve a comprobar la autorización; esta respuesta
es una instantánea para la interfaz, no una concesión. Despliegue este endpoint antes de actualizar la aplicación:
los servidores antiguos sin él no pueden validar una conexión de la nueva aplicación.
La búsqueda usa el servicio de recuperación compartido y excluye contenido privado, credenciales y archivos. El listado de conocimiento sin filtrar puede incluir tarjetas privadas para un lector autorizado. Los guardados fallidos conservan el borrador; la eliminación requiere confirmación y quita el elemento solo después del éxito del servidor. Las actualizaciones usan el servicio de revisiones existente sin una precondición atómica de revisión esperada; las ediciones simultáneas pueden sustituir los campos más recientes de las otras.
El modo alojado es legible por el servidor: el Store está cifrado en reposo, pero el
operador posee la contraseña y puede leer el contenido alojado. La aplicación lo marca como
«el servidor puede leer». Rust mantiene el portador de cada cliente activo en memoria, oculto en
Debug y errores; Flutter conserva el par URL/token de cada modo en el almacenamiento
seguro de la plataforma. Cambiar termina la sesión activa y conserva el perfil.
Desconectar elimina el portador guardado de ese perfil; no revoca el token del
servidor. Pasar a segundo plano descarta las sesiones alojadas y el contenido visible; la reconexión al
volver es explícita.
Secretos E2E entre dispositivos (Center alojado)
Las bóvedas alojadas son legibles por el servidor. Para los secretos que Center nunca debe ver, empareje
dispositivos y entregue sobres sellados (crypto::Envelope: X25519 + firma
con la clave Ed25519 del emisor). Center almacena solo claves públicas y texto cifrado.
Flujo de emparejamiento
- CLI:
memories device hosted init --backend <keychain|secret-service|dpapi|owner-file>crea la identidad del dispositivo (semilla de 32 bytes bajo custodia del SO; X25519 + Ed25519 derivadas). - CLI (token de miembro en
MEMORIES_CENTER_TOKEN):memories device hosted pair --vault V→POST /v1/devices/pairingscon ambas claves públicas y una autofirma. Center crea una PermissionRequest pendiente que contiene la huella del dispositivo y devuelve{code, request_id, qr: {pairing_id, fingerprint, center}}(centeres el--center-idde Center). Los emparejamientos caducan a los 5 minutos. La CLI imprime en la terminal un QR (stderr) de{"type":"memories-pair","v":1,"pairing_id","fingerprint","center","code"}— se incluye el código de un solo uso para que baste con escanearlo; el JSON de stdout no cambia. Volver a emparejar una clave que ya está activa devuelve 409; tras revocarla, la misma clave puede emparejarse de nuevo. - El teléfono (administrador de la bóveda) escanea el QR (o introduce el código) →
GET /v1/devices/pairings/{code}. El teléfono rechaza un QR que no sea un QR de emparejamiento de Memories, cuyocenterdifiera del Center conectado o cuya huella difiera del dispositivo obtenido. Los códigos desconocidos, caducados o usados devuelven todos 404. — de un solo uso, con caducidad, 10 consultas fallidas/minuto → 429. Muestra la huella del dispositivo. - El teléfono aprueba con
POST /v1/requests/{id}/approvey cuerpo{"attestation": DeviceAttestation}firmado por la clave Ed25519 del teléfono sobre la huella del dispositivo, ambas claves del dispositivo, id de emparejamiento, aprobador, ámbito y caducidad. - CLI:
memories device hosted claim --request-id ID(una vez). El token de concesión con ámbito de dispositivo se almacena bajo custodia del SO; Center conserva solo su hash. - Compare el SAS: el teléfono lo muestra (agrupado
123 456) antes de aprobar; tras la aprobación,memories device hosted sas --pairing-id Pimprime los mismos dígitos en la CLI. Si los dígitos difieren: revoque el dispositivo y vuelva a emparejar.
Permisos móviles: Android CAMERA (función de cámara opcional), iOS
NSCameraUsageDescription. Si se deniega el acceso a la cámara, el teléfono explica cómo
habilitarlo en los Ajustes del sistema y ofrece introducir el código manualmente. La CLI y el teléfono
traducen los estados de Center en mensajes con acciones concretas (404 caducado/usado/otro
Center, 409 ya realizado o ya emparejado, 403 no permitido o dispositivo revocado,
422 contenido e2e rechazado, 429 límite de frecuencia).
SAS = los primeros 6 dígitos decimales 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,
rellenados con ceros. El teléfono obtiene la clave de la CLI mediante la consulta; la CLI conoce la clave del teléfono
a partir de la atestación. Una discrepancia significa una clave sustituida: revoque inmediatamente.
Reglas
- Las concesiones de dispositivo están vinculadas al dispositivo (no revocado, atestado) y a la generación del miembro; profundidad de delegación ≤ 2. Los dispositivos no pueden emitir concesiones ni decidir solicitudes.
- Incrementar la generación o eliminar al miembro invalida el dispositivo y elimina sus sobres.
PUT|GET|DELETE /v1/sealed/{vault}/{card}/{device},GET /v1/sealed?device=: sobres solo para dispositivos atestados, dirigidos a la clave X25519 del dispositivo y de un firmante conocido; las versiones deben aumentar. La auditoría registra solo huellas.- Las tarjetas con
e2e: truerechazan contenido secreto en texto claro en el Center alojado. /v1/access/{vault}lista dispositivos. MCP:device_list,sealed_reveal(requiereintent: "reveal_plaintext"y un motivo).
Modelo de amenazas
El operador de Center no puede leer los secretos sellados, pero puede retener o reproducir sobres antiguos (las versiones son monótonas por dispositivo), y ve metadatos (quién, qué tarjeta, cuándo). El emparejamiento confía en la comparación SAS y la clave del teléfono del aprobador. La revocación no puede retirar texto claro ya entregado a un dispositivo: después de revocar, rote el secreto en el sistema de origen.
Refuerzo E2E (P2)
- Indicador
e2epersistente. Cuando una tarjeta tienee2e: true, se rechazan las escrituras que quitan el indicador (422). Solo una concesión Owner (no una concesión de dispositivo) puede quitarlo, y únicamente con una escritura que no contenga material secreto; las escrituras posteriores siguen las reglas normales. - Parámetros OTP. Un secreto OTP sellado puede ser una semilla Base32 simple (SHA1, 6 dígitos, 30 s) o una URI completa
otpauth://totp/...;memories device hosted sealed-get --otprespetaalgorithm(SHA1/SHA256/SHA512),digits(6/8) yperiod(30/60) de la URI.device hosted pairtambién imprime la carga de emparejamiento como código QR en terminal (stderr). - Volver a sellar tras revocar. Revocar (o invalidar) un dispositivo elimina sus sobres y marca cada tarjeta afectada como
needs_rotation(se muestra enGET /v1/sealed?device=,GET /v1/sealed/rotation?vault=, la lista de acceso y la página de nuevo sellado del teléfono). La revocación no puede retirar texto claro ya entregado: rote el secreto en el sistema de origen y use después Volver a sellar para todos los dispositivos (hosted_reseal_all) del teléfono, que sella para cada dispositivo aprobado actualmente con una nueva versión y quita el indicador. Los dispositivos revocados nunca se incluyen. - Recuperación del teléfono. Center nunca posee la identidad del teléfono. Al crearla, el teléfono muestra un código de recuperación una sola vez (Base32 de la semilla de 32 bytes + suma de comprobación de 2 bytes, en grupos de cuatro) para anotarlo; Restaurar desde código de recuperación (
hosted_phone_restore) reconstruye la misma identidad. - Rotación de identidad. Cambiar a una nueva clave de teléfono (
hosted_identity_rotate) crea una nueva identidad y envíaPOST /v1/devices/identity-rotationscon un registro firmado tanto por la clave antigua como por la nueva; solo un miembro administrador (no un dispositivo) puede enviarlo, y la clave antigua ya debe ser de confianza para algún dispositivo. Las CLI fijan la primera clave del teléfono aprobador (hosted-device-trust.json, confianza en el primer uso) y aceptan únicamente firmantes de sobres alcanzables desde esa raíz mediante rotaciones verificadas. - Almacenamiento respaldado por hardware. La semilla del teléfono se almacena con
flutter_secure_storageusando las opciones integradas más fuertes: accesibilidadpasscodede Keychain en iOS/macOS (solo este dispositivo, sin sincronizar, requiere código del dispositivo); almacenamiento envuelto por Android Keystore con control biométrico o de credencial del dispositivo yresetOnError: false. Límites: la semilla sigue exportándose a la memoria de la aplicación para firmar/sellar (Secure Enclave / StrongBox no pueden hacer X25519/Ed25519 de forma nativa), por lo que protege en reposo, no frente a una aplicación en ejecución comprometida. - Registro de transparencia. Center mantiene un registro de dispositivos de solo adición encadenado por hashes (
device.pair,device.approve,identity.rotate,device.revoke) enGET /v1/devices/log. Cada entrada incorpora la autofirma de emparejamiento de la CLI, la atestación del teléfono o el registro de rotación.memories device hosted log --verifycomprueba los enlaces de la cadena, cada firma y que el registro amplíe la cabecera que verificó la última vez (hosted-device-log.json); un historial reescrito se informa como error.