@dotrino/vaultd 0.87.0 → 0.88.0

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
@@ -204,7 +204,9 @@ export function createAdminDesk ({
204
204
  const toScope = typeof data.ns === 'string' && !!data.ns
205
205
  const toDevice = typeof data.pub === 'string' && !!data.pub
206
206
  if (data.op === 'vars') {
207
- const result = await vars.list({ by })
207
+ // QUIÉN PREGUNTA, no solo su nombre: con su llave se le manda la envoltura de
208
+ // cada pública para que pueda abrirla en su casa.
209
+ const result = await vars.list({ by, caller: chk.device })
208
210
  audit('admin.vars', { by })
209
211
  return { ok: true, result }
210
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.87.0",
3
+ "version": "0.88.0",
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/ctl.js CHANGED
@@ -1112,12 +1112,12 @@ async function cmdSecret (rest) {
1112
1112
  /**
1113
1113
  * Una variable en la lista: su nombre, y que TIENE valor. Ninguna lo enseña.
1114
1114
  *
1115
- * La pública lo enseñaba, porque se guardaba en claro. Desde 2026-09-02 las dos van
1116
- * selladas igual y `pública` solo dice que se entrega sin pedir aprobación (dueño: «la
1117
- * única diferencia es si se despachan o no, son políticas»). Enseñar el hueco vacío de
1118
- * una pública era peor que no enseñar nada: parecía que se había perdido.
1115
+ * Las dos van selladas igual desde 2026-09-02. Lo que las separa es que a quien administra
1116
+ * se le hace la envoltura de las PÚBLICAS y la bóveda le manda su sobre, así que puede
1117
+ * abrirlas; de una privada de un cajón con dueño no tiene envoltura ni recibe sobre.
1119
1118
  */
1120
- const printVar = (k) => console.log(' · %s ••••••%s', k.key, k.public ? ' (pública)' : '')
1119
+ const printVar = (k) => console.log(' · %s %s', k.key,
1120
+ k.public ? `${k.value ?? '••••••'} (pública)` : '••••••')
1121
1121
  /**
1122
1122
  * Por qué no se aplicó. El daemon deja el motivo en el volcado siguiente; sin él lo
1123
1123
  * único que se podía decir era «revisa los logs», que no ayuda a quien acaba de
@@ -1187,7 +1187,7 @@ async function cmdSecret (rest) {
1187
1187
  // QUÉ SIGNIFICA HOY (dueño, 2026-09-02): todas se guardan selladas igual, y `pública`
1188
1188
  // solo dice a quién se le entrega SIN APROBACIÓN. Decir «su valor se puede ver» era
1189
1189
  // verdad cuando se guardaba en claro; dejarlo ahí sería prometer algo que ya no pasa.
1190
- console.log('\n(pública) = se entrega sin pedirte aprobación. Todas se guardan cifradas igual.\n')
1190
+ console.log('\n(pública) = se entrega sin pedirte aprobación y quien administra puede leerla. Todas van cifradas.\n')
1191
1191
  printPending(d)
1192
1192
  await printNoPassword()
1193
1193
  return
@@ -618,6 +618,24 @@ export function openSecretsStore (dir, { sealer = null, recipients = null, signe
618
618
  return { gen }
619
619
  },
620
620
 
621
+ /**
622
+ * LA ENTRADA TAL CUAL (sobre, generación, visibilidad) — sin abrir nada.
623
+ *
624
+ * Es lo que hace falta para MANDARLE a alguien una variable que sí puede leer: el sobre
625
+ * viaja cerrado y lo abre en su casa, con su envoltura. Aquí no se descifra.
626
+ */
627
+ entryOf (owner, key) {
628
+ const [kind, k] = splitOwner(owner)
629
+ const bag = kind === 'ns' ? data.ns : data.dev
630
+ return varsOf(bag, k)[key] || null
631
+ },
632
+ /** La envoltura de ESA generación para ESE miembro, o `null` si no la tiene. */
633
+ wrapOf (owner, gen, memberPub) {
634
+ const [kind, k] = splitOwner(owner)
635
+ const bag = kind === 'ns' ? data.ns : data.dev
636
+ return (bag[k]?.keyring || []).find((g) => g.gen === gen)?.wraps?.[memberPub] || null
637
+ },
638
+
621
639
  /**
622
640
  * ¿EXISTE YA ESTA VARIABLE? Sin abrirla ni decir qué vale.
623
641
  *
package/src/vault.js CHANGED
@@ -1358,14 +1358,21 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1358
1358
  }
1359
1359
 
1360
1360
  const varsDesk = {
1361
- async list () {
1362
- // `listSecrets`/`listDeviceSecrets` ya traen el valor de las públicas y solo de esas:
1363
- // la frontera se decide en un sitio, y lo mismo ve el dueño en su terminal que aquí.
1361
+ async list ({ caller = null } = {}) {
1362
+ // EL VAULT DECIDE QUÉ ENVÍA PARA LEER (dueño, 2026-09-02: «la consola debe tener todos
1363
+ // los sobres; la diferencia es si el vault se los envía para la lectura o no»).
1364
+ //
1365
+ // Tener la envoltura y recibir el sobre son dos cosas: quien administra tiene la
1366
+ // envoltura de cada PÚBLICA, y por eso aquí se le manda el sobre de esas y solo de
1367
+ // esas. Las privadas de un cajón con dueño no se mandan — y aunque se mandaran no
1368
+ // podría abrirlas, porque su envoltura no existe. Dos cerrojos para lo mismo, a
1369
+ // propósito: que uno falle no basta para leerlas.
1370
+ //
1364
1371
  // Se sella la lista ENTERA, no solo los valores: el proxy tampoco tiene por qué
1365
1372
  // aprender cómo se llaman tus variables ni qué servicios corres.
1366
1373
  return {
1367
1374
  enc: await identity.sealContent(JSON.stringify({
1368
- ns: listSecrets(), dev: await listDeviceSecrets(),
1375
+ ns: conSobres(listSecrets(), 'ns', caller), dev: conSobres(await listDeviceSecrets(), 'dev', caller),
1369
1376
  // Aparatos que están en el acta pero no pueden abrir lo suyo: hay que
1370
1377
  // enseñarlo donde se administra, no solo en el log del servicio.
1371
1378
  incomplete: await incompleteMembers(),
@@ -2327,6 +2334,34 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
2327
2334
  * de esta máquina»: taparlo justo aquí —en la máquina donde vive, delante de su dueño—
2328
2335
  * era lo único que la marca no significaba. La consola remota ya las enseña.
2329
2336
  */
2337
+ /**
2338
+ * LE PONE A CADA PÚBLICA SU SOBRE Y LA ENVOLTURA DE QUIEN PREGUNTA, para que pueda leerla.
2339
+ *
2340
+ * Solo a las públicas: es la política de despacho, y es lo único que cambia entre una y
2341
+ * otra. A una privada no se le manda nada — y aunque se le mandara, quien administra no
2342
+ * tiene su envoltura en un cajón con dueño, así que no la abriría igual.
2343
+ *
2344
+ * Si falta la envoltura de quien pregunta (todavía no se le ha hecho, o el cajón se
2345
+ * escribió antes de que entrara) se manda la variable SIN sobre: se ve que existe y que es
2346
+ * pública, y no se puede leer. Eso es cierto y se arregla abriendo la bóveda; inventarse
2347
+ * un valor o esconder la fila sería peor.
2348
+ */
2349
+ const conSobres = (lista, tipo, caller) => {
2350
+ if (!caller) return lista
2351
+ const dar = (owner, k) => {
2352
+ if (!k.public) return k
2353
+ const e = secrets.entryOf?.(owner, k.key)
2354
+ const wrap = e?.gen != null ? secrets.wrapOf?.(owner, e.gen, caller) : null
2355
+ return e?.e && wrap ? { ...k, e: e.e, wrap } : k
2356
+ }
2357
+ if (tipo === 'ns') {
2358
+ const out = {}
2359
+ for (const [ns, keys] of Object.entries(lista)) out[ns] = keys.map((k) => dar(`ns:${ns}`, k))
2360
+ return out
2361
+ }
2362
+ return lista.map((row) => ({ ...row, keys: row.keys.map((k) => dar(`dev:${row.pub}`, k)) }))
2363
+ }
2364
+
2330
2365
  function listSecrets () {
2331
2366
  // YA NO SE ENSEÑA EL VALOR DE UNA PÚBLICA (dueño, 2026-09-02). Desde que todas van en
2332
2367
  // sobre, «pública» dejó de significar «en claro»: dice a quién se le despacha sin