@dotrino/vaultd 0.74.0 → 0.75.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/src/admin.js CHANGED
@@ -46,7 +46,7 @@
46
46
  export const ADMIN_OPS = Object.freeze([
47
47
  // `pending`, `pair`, `approve` y `reject` NO están, y no es un olvido: agregar aparatos
48
48
  // a distancia se quitó (ver la cabecera). Lo que queda es mirar, quitar y configurar.
49
- 'status', 'revoke', 'audit',
49
+ 'revoke', 'audit',
50
50
  // Variables de entorno: verlas (nombres siempre; valor solo de las públicas) y
51
51
  // ponerles valor (de las dos). Borrar NO está, y es a propósito: un aparato robado
52
52
  // no debe poder dejar sin configuración a los servicios.
@@ -72,7 +72,7 @@ export const ADMIN_OPS = Object.freeze([
72
72
  * Se dejan las de LEER a propósito: si se cortaran todas, la consola no podría ni
73
73
  * enterarse de por qué no funciona y parecería rota. Así puede decir «está cerrada».
74
74
  */
75
- export const ADMIN_OPS_WHILE_LOCKED = Object.freeze(['status', 'audit', 'vars'])
75
+ export const ADMIN_OPS_WHILE_LOCKED = Object.freeze(['audit', 'vars'])
76
76
 
77
77
  /** Cuánto se recuerda un nonce ya usado (el doble de la ventana de frescura). */
78
78
  export const ADMIN_NONCE_TTL_MS = 10 * 60 * 1000
@@ -159,10 +159,6 @@ export function createAdminDesk ({
159
159
  }
160
160
 
161
161
  try {
162
- // Lo mínimo que la consola necesita saber de la bóveda para explicarse: si está
163
- // cerrada, lo que queda (revocar, configurar) no se puede, y hay que decir por qué en
164
- // vez de dejar pulsar un botón para recibir un error.
165
- if (data.op === 'status') return { ok: true, result: { locked: !!isLocked() } }
166
162
 
167
163
  if (data.op === 'audit') {
168
164
  const limit = Math.min(Math.max(Number(data.limit) || 100, 1), AUDIT_MAX)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.74.0",
3
+ "version": "0.75.1",
4
4
  "type": "module",
5
5
  "description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
6
6
  "bin": {
package/src/vault.js CHANGED
@@ -228,7 +228,11 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
228
228
  try {
229
229
  const info = await identity.profileActa?.()
230
230
  const acta = info?.acta
231
- if (info?.isMaster && acta && (!acta.sealPub || !sealKeys.has(acta.sealPub))) {
231
+ // CON EL PERFIL ABIERTO. Estrenar la llave de sellado SELLA EL ACTA, y una bóveda
232
+ // cerrada no sella nada — la misma regla que el bloque de abajo. Cerrada se queda sin
233
+ // llave y los sobres salen sin firma (lo que ya pasaba antes de §8.8), y se arregla sola
234
+ // la primera vez que alguien abra el perfil.
235
+ if (info?.isMaster && acta && !isLocked() && (!acta.sealPub || !sealKeys.has(acta.sealPub))) {
232
236
  const r = await identity.rotateSealKey()
233
237
  // Sin `%s`: este `log` va con un prefijo por delante, así que el formato no es lo
234
238
  // primero y `console.log` no lo sustituye (salía «record #%s 2»).
@@ -252,7 +256,14 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
252
256
  try {
253
257
  const info = await identity.profileActa?.()
254
258
  const acta = info?.acta
255
- if (acta && !identity.masterLocked) {
259
+ // CON EL PERFIL ABIERTO, y `isLocked()` es la pregunta correcta — no `masterLocked`.
260
+ //
261
+ // Se coló así: en la PRIMERA arrancada tras actualizar, la maestra todavía está guardada
262
+ // en claro (se sella al abrir el perfil), o sea que `masterLocked` es `false` aunque el
263
+ // perfil esté cerrado. Con ese guardián, la bóveda de producción admitió su llave de
264
+ // comunicación y SELLÓ UN ACTA NUEVA (#76 → #77) estando cerrada — justo lo que la regla
265
+ // prohíbe, y encima rotando de paso la llave de sellado sin que nadie lo pidiera.
266
+ if (acta && !isLocked() && !identity.masterLocked) {
256
267
  const pub = await commKey.ensure()
257
268
  const yaEsta = (acta.members || []).some((m) => m?.pub === pub)
258
269
  if (!yaEsta && info?.isMaster) {
@@ -604,14 +615,29 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
604
615
  if (!isFresh(p.data)) return staleReply(from)
605
616
  const chk = await verifyChain({ data: p.data, signature: p.signature, cert: p.cert, ...(await contextoActa()), revoked: await revocationSet() })
606
617
  if (!chk.ok) return denyChain(from, chk, p, null)
607
- // La lista de aparatos es el perfil: quién eres, cómo se llama cada máquina y qué puede.
608
- // Este mostrador no preguntaba al acta, así que un aparato al que le quitaste `lee`
609
- // seguía viendo tu inventario entero hasta que caducara su papel.
610
- if (!await actaAllows(from, chk, SCOPE.READ, 'devices')) return
618
+ // DOS RESPUESTAS, PORQUE SON DOS PREGUNTAS DISTINTAS.
619
+ //
620
+ // Un SERVICIO pregunta aquí para enterarse de dos cosas suyas: si le revocaron el papel
621
+ // y cuál es el acta vigente. Eso no es tu inventario y no puede exigir `lee` — se lo
622
+ // exigí, y en producción dejó ciegos a los servicios (`rejected devices/acta` en la
623
+ // bitácora del VPS, dos veces, antes de que lo viera).
624
+ //
625
+ // La LISTA DE APARATOS sí es el perfil —cómo se llama cada máquina y qué puede— y esa
626
+ // sigue pidiendo `lee`. Antes no preguntaba nada, así que un aparato al que le quitaste
627
+ // el permiso seguía viéndolo todo hasta que su papel caducara.
628
+ const record = (await identity.profileActa?.().catch(() => null))?.acta || null
629
+ const puedeVerTodo = !!record && Acta.memberCanScope(record, chk.device, SCOPE.READ)
611
630
  const { issued, revoked } = await identity.listDelegations()
631
+ if (!puedeVerTodo) {
632
+ if (!record) {
633
+ audit('rejected', { what: 'devices', reason: 'sin-acta' })
634
+ return reply(from, { type: MSG.ERROR, error: 'unauthorized: this vault has no record to decide with' })
635
+ }
636
+ audit('devices', { device: await deviceIdOf(chk.device).catch(() => null), solo: 'revocaciones' })
637
+ return reply(from, { type: MSG.DEVICES_RESULT, devices: [], revoked, acta: record })
638
+ }
612
639
  // El acta viaja con la lista: así cada dispositivo se entera de los cambios de
613
640
  // política (quién manda, quién puede qué) sin un canal aparte.
614
- const record = (await identity.profileActa?.().catch(() => null))?.acta || null
615
641
  // Si el dispositivo estuvo apagado y viene con un `seq` viejo, se le manda la CADENA
616
642
  // que falta (ventana de retención, §1.3) para que compruebe el encadenamiento en vez
617
643
  // de tragarse un salto a ciegas. Si se salió de la ventana, llega vacía y toca