@dotrino/vaultd 0.86.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 +3 -1
- package/package.json +1 -1
- package/src/ctl.js +6 -6
- package/src/secretsStore.js +40 -6
- package/src/vault.js +83 -22
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
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
|
|
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
|
|
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
|
package/src/secretsStore.js
CHANGED
|
@@ -296,8 +296,13 @@ export function openSecretsStore (dir, { sealer = null, recipients = null, signe
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/** Los destinatarios de un cajón: los que dice el acta, más la copia de recuperación. */
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
/**
|
|
300
|
+
* @param {boolean} isPublic Una PÚBLICA se envuelve además para quien administra, aunque
|
|
301
|
+
* el cajón tenga dueño (dueño, 2026-09-02). Puede variar por variable porque cada
|
|
302
|
+
* escritura estrena su propia CEK: el llavero guarda una generación por valor.
|
|
303
|
+
*/
|
|
304
|
+
const wrapAll = async (cek, owner, isPublic = false) => {
|
|
305
|
+
const members = (await recipients?.(owner, { public: isPublic })) || []
|
|
301
306
|
const { wraps, sinLlave } = await sealer.wrapFor(cek, members)
|
|
302
307
|
wraps[RECOVERY] = await sealer.wrapForKey(cek, data.recovery.pub)
|
|
303
308
|
return { wraps, sinLlave }
|
|
@@ -537,7 +542,7 @@ export function openSecretsStore (dir, { sealer = null, recipients = null, signe
|
|
|
537
542
|
// CEK NUEVA, siempre: no se puede reutilizar la de antes sin poder abrirla.
|
|
538
543
|
const cek = await sealer.newKey()
|
|
539
544
|
const gen = (topGen(bag, k)?.gen || 0) + 1
|
|
540
|
-
const { wraps, sinLlave } = await wrapAll(cek, owner)
|
|
545
|
+
const { wraps, sinLlave } = await wrapAll(cek, owner, pub)
|
|
541
546
|
const e = await sealer.encrypt(cek, value, gen)
|
|
542
547
|
// La FIRMA dice que este sobre salió de esta bóveda, y con qué acta (§8.8). Si no
|
|
543
548
|
// hay con qué firmar, el sobre sale sin firma: guardar es más importante.
|
|
@@ -613,6 +618,24 @@ export function openSecretsStore (dir, { sealer = null, recipients = null, signe
|
|
|
613
618
|
return { gen }
|
|
614
619
|
},
|
|
615
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
|
+
|
|
616
639
|
/**
|
|
617
640
|
* ¿EXISTE YA ESTA VARIABLE? Sin abrirla ni decir qué vale.
|
|
618
641
|
*
|
|
@@ -631,9 +654,9 @@ export function openSecretsStore (dir, { sealer = null, recipients = null, signe
|
|
|
631
654
|
* haya dos sitios respondiendo a «quién puede abrir este cajón» (la lista la sabe el
|
|
632
655
|
* acta, y de ahí sale `recipients`).
|
|
633
656
|
*/
|
|
634
|
-
async recipientsFor (owner) {
|
|
657
|
+
async recipientsFor (owner, { public: isPublic = false } = {}) {
|
|
635
658
|
await ensureRecovery(null)
|
|
636
|
-
const members = (await recipients?.(owner)) || []
|
|
659
|
+
const members = (await recipients?.(owner, { public: isPublic })) || []
|
|
637
660
|
return {
|
|
638
661
|
recoveryPub: data.recovery.pub,
|
|
639
662
|
members: members.filter((m) => m.encPub).map((m) => ({ pub: m.pub, encPub: m.encPub }))
|
|
@@ -796,6 +819,13 @@ export function openSecretsStore (dir, { sealer = null, recipients = null, signe
|
|
|
796
819
|
return [...Object.keys(data.ns).map((k) => `ns:${k}`), ...Object.keys(data.dev).map((k) => `dev:${k}`)]
|
|
797
820
|
},
|
|
798
821
|
/** @private */
|
|
822
|
+
/**
|
|
823
|
+
* @param {Array|((isPublic:boolean)=>Promise<Array>)} members A quién envolver. Puede ser
|
|
824
|
+
* una FUNCIÓN de la visibilidad: desde 2026-09-02 una pública se envuelve además para
|
|
825
|
+
* quien administra, y eso se decide por variable — cada escritura estrena su CEK, así
|
|
826
|
+
* que el llavero tiene una generación por valor. Con una lista fija se aplicaría la
|
|
827
|
+
* misma a todas y se le daría a la consola la envoltura de una privada con dueño.
|
|
828
|
+
*/
|
|
799
829
|
async _rewrap (owner, members, adminKey = null, { exact = false } = {}) {
|
|
800
830
|
needSealer('re-wrap the key of a drawer')
|
|
801
831
|
if (needsMigration()) throw new NeedsMigration()
|
|
@@ -805,11 +835,15 @@ export function openSecretsStore (dir, { sealer = null, recipients = null, signe
|
|
|
805
835
|
const priv = await openRecovery(adminKey)
|
|
806
836
|
let wrapped = 0
|
|
807
837
|
const sinLlave = new Set()
|
|
838
|
+
// De qué variable es cada generación: es lo que dice si esa CEK es de una pública.
|
|
839
|
+
const visibilidad = new Map()
|
|
840
|
+
for (const e of Object.values(varsOf(bag, k))) if (e.gen != null) visibilidad.set(e.gen, !!e.pub)
|
|
841
|
+
const paraGen = async (gen) => (typeof members === 'function' ? members(visibilidad.get(gen) === true) : members)
|
|
808
842
|
for (const g of bag[k].keyring || []) {
|
|
809
843
|
const w = g.wraps?.[RECOVERY]
|
|
810
844
|
if (!w) continue
|
|
811
845
|
const cek = await sealer.openWrapWith(priv, w)
|
|
812
|
-
const r = await sealer.wrapFor(cek,
|
|
846
|
+
const r = await sealer.wrapFor(cek, await paraGen(g.gen))
|
|
813
847
|
// `exact`: el llavero queda con lo que dice el acta y NADA más. Es lo que hace
|
|
814
848
|
// falta para rehacerlo al abrir la bóveda:
|
|
815
849
|
// · una envoltura basura que metió alguien se reemplaza por la buena;
|
package/src/vault.js
CHANGED
|
@@ -197,7 +197,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
197
197
|
// A QUIÉN se le envuelve la llave de cada cajón: los servicios de ese namespace (o el
|
|
198
198
|
// propio aparato, si el cajón es suyo) MÁS los aparatos que administran. Sale del
|
|
199
199
|
// acta, y por eso lo pone el vault: el store no conoce el acta.
|
|
200
|
-
recipients: (owner) => recipientsOf(owner),
|
|
200
|
+
recipients: (owner, opts) => recipientsOf(owner, opts),
|
|
201
201
|
// La FIRMA del sobre: dice que salió de esta bóveda y con qué acta (§8.8).
|
|
202
202
|
signer: (body) => signSeal(body),
|
|
203
203
|
// CON QUÉ se cierra la copia de recuperación cuando quien llama no trajo la frase:
|
|
@@ -808,7 +808,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
808
808
|
// Y su cajón PROPIO también: un aparato con variables suyas se quedaba sin poder
|
|
809
809
|
// abrirlas, que es el mismo agujero por otra puerta.
|
|
810
810
|
for (const owner of [`ns:${ns}`, `dev:${chk.device}`]) {
|
|
811
|
-
await spreadKey(owner,
|
|
811
|
+
await spreadKey(owner, (esPublica) => recipientsOf(owner, { public: esPublica }))
|
|
812
812
|
.catch((e) => log(`[vault] ${owner}: could not hand it the key: ${e.message}`))
|
|
813
813
|
}
|
|
814
814
|
const body = { op: 'secrets.result', ns, enc: null, ok: true, ts: Date.now() }
|
|
@@ -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
|
-
//
|
|
1363
|
-
//
|
|
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(),
|
|
@@ -1386,8 +1393,12 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
1386
1393
|
* (`recipientsOf`), y tener dos sitios respondiendo a «quién puede abrir este cajón» es
|
|
1387
1394
|
* exactamente cómo se acaba dejando fuera a alguien sin que nadie se entere.
|
|
1388
1395
|
*/
|
|
1389
|
-
async recipients ({ ns, pub }) {
|
|
1390
|
-
|
|
1396
|
+
async recipients ({ ns, pub, public: isPublic }) {
|
|
1397
|
+
// LA VISIBILIDAD CAMBIA LA LISTA: una pública se envuelve además para quien
|
|
1398
|
+
// administra, así que hay que decirlo al preguntar. Pedirla sin decirlo y luego
|
|
1399
|
+
// guardarla como pública produciría un sobre que la consola no puede abrir — y la
|
|
1400
|
+
// bóveda lo rechazaría, que es lo correcto pero desorienta.
|
|
1401
|
+
return secrets.recipientsFor(ns ? `ns:${ns}` : `dev:${pub}`, { public: !!isPublic })
|
|
1391
1402
|
},
|
|
1392
1403
|
/**
|
|
1393
1404
|
* GUARDAR UNA VARIABLE **SIN VERLA** (dueño, 2026-09-01).
|
|
@@ -1416,7 +1427,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
1416
1427
|
// despachan o no, son políticas; dales el mismo tratamiento de seguridad»). La
|
|
1417
1428
|
// marca solo decide si se entrega sin aprobación.
|
|
1418
1429
|
await verificarAutor(owner, key, sealed, caller)
|
|
1419
|
-
await verificarDestinatarios(owner, sealed.wraps)
|
|
1430
|
+
await verificarDestinatarios(owner, sealed.wraps, !!isPublic)
|
|
1420
1431
|
await secrets.putSealed(owner, key, sealed, { by: who, public: isPublic })
|
|
1421
1432
|
audit('secret.set', { ns: ns || null, key, sealed: true }); scheduleNotice(ns)
|
|
1422
1433
|
await settleDebts(owner)
|
|
@@ -1456,7 +1467,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
1456
1467
|
for (const it of items) {
|
|
1457
1468
|
if (!it?.key || !it?.sealed) throw new Error('var.setMany: each variable needs its key and its sealed envelope')
|
|
1458
1469
|
await verificarAutor(owner, it.key, it.sealed, caller)
|
|
1459
|
-
await verificarDestinatarios(owner, it.sealed.wraps)
|
|
1470
|
+
await verificarDestinatarios(owner, it.sealed.wraps, !!it.public)
|
|
1460
1471
|
}
|
|
1461
1472
|
// Se comprueban TODAS antes de escribir NINGUNA: media carga aplicada es una
|
|
1462
1473
|
// configuración que nadie quiso, y el servicio se reinicia con ella.
|
|
@@ -1660,24 +1671,43 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
1660
1671
|
}
|
|
1661
1672
|
|
|
1662
1673
|
/** Sin duplicar: un cosellador puede ser además el dueño del cajón. */
|
|
1674
|
+
/** Sin repetidos por llave: un aparato puede entrar por dos vías (dueño y admin). */
|
|
1675
|
+
const dedup = (miembros) => {
|
|
1676
|
+
const vistos = new Set()
|
|
1677
|
+
return miembros.filter((m) => m && !vistos.has(m.pub) && vistos.add(m.pub))
|
|
1678
|
+
}
|
|
1663
1679
|
const conCoselladores = (base, co) => {
|
|
1664
1680
|
const vistos = new Set(base.map((m) => m.pub))
|
|
1665
1681
|
return [...base, ...co.filter((m) => !vistos.has(m.pub))]
|
|
1666
1682
|
}
|
|
1667
1683
|
|
|
1668
|
-
async function recipientsOf (owner) {
|
|
1684
|
+
async function recipientsOf (owner, { public: isPublic = false } = {}) {
|
|
1669
1685
|
const co = await cosealerMembers()
|
|
1686
|
+
// UNA PÚBLICA SE ENVUELVE TAMBIÉN PARA QUIEN ADMINISTRA (dueño, 2026-09-02: «la consola
|
|
1687
|
+
// debe poder leer todas las variables… debe tener todos los sobres; la diferencia es si
|
|
1688
|
+
// el vault se los envía para la lectura o no» → «cambia la regla y di que se envuelven
|
|
1689
|
+
// las públicas»).
|
|
1690
|
+
//
|
|
1691
|
+
// Puede decidirse variable a variable porque cada escritura estrena su propia CEK: el
|
|
1692
|
+
// llavero guarda una generación por valor, no una por cajón.
|
|
1693
|
+
//
|
|
1694
|
+
// Y la PRIVADA de un cajón con dueño sigue sin envoltura para la consola, que es la
|
|
1695
|
+
// regla de agosto y su motivo no ha cambiado: el token de R2 no tiene por qué poder
|
|
1696
|
+
// abrirse desde un navegador. Lo que cambia es que «pública» ya sí quiere decir algo
|
|
1697
|
+
// para quien administra — puede leerla— además de que se despacha sin aprobación.
|
|
1698
|
+
const admins = isPublic ? await adminDevices() : []
|
|
1670
1699
|
if (owner.startsWith('ns:')) {
|
|
1671
1700
|
const owned = await nsMembers(owner.slice(3))
|
|
1672
|
-
// Sin dueño (un cajón personal, o uno cuyo servicio ya no está)
|
|
1673
|
-
//
|
|
1674
|
-
|
|
1701
|
+
// Sin dueño (un cajón personal, o uno cuyo servicio ya no está) entra quien administra
|
|
1702
|
+
// SIEMPRE: si no, no quedaría nadie que pudiera abrirlo sin la frase.
|
|
1703
|
+
const base = owned.length ? [...owned, ...admins] : await adminDevices()
|
|
1704
|
+
return conCoselladores(dedup(base), co)
|
|
1675
1705
|
}
|
|
1676
1706
|
const pub = owner.slice(owner.indexOf(':') + 1)
|
|
1677
1707
|
const m = await memberOf(pub)
|
|
1678
1708
|
// El cajón propio de un aparato de SERVICIO es tan suyo como el de su ns.
|
|
1679
|
-
if (m?.cn) return conCoselladores([m], co)
|
|
1680
|
-
return conCoselladores([...(m ? [m] : []), ...await adminDevices()], co)
|
|
1709
|
+
if (m?.cn) return conCoselladores(dedup([m, ...admins]), co)
|
|
1710
|
+
return conCoselladores(dedup([...(m ? [m] : []), ...await adminDevices()]), co)
|
|
1681
1711
|
}
|
|
1682
1712
|
|
|
1683
1713
|
/**
|
|
@@ -2052,9 +2082,9 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
2052
2082
|
*
|
|
2053
2083
|
* Lo de dentro lo corrige el repaso al abrir la bóveda, que es para lo que está.
|
|
2054
2084
|
*/
|
|
2055
|
-
async function verificarDestinatarios (owner, wraps) {
|
|
2085
|
+
async function verificarDestinatarios (owner, wraps, isPublic = false) {
|
|
2056
2086
|
if (!wraps || typeof wraps !== 'object') throw new Error('var.set: the envelope carries no wraps')
|
|
2057
|
-
const { recoveryPub, members } = await secrets.recipientsFor(owner)
|
|
2087
|
+
const { recoveryPub, members } = await secrets.recipientsFor(owner, { public: isPublic })
|
|
2058
2088
|
if (!recoveryPub) throw new Error('var.set: this vault has no recovery key yet')
|
|
2059
2089
|
const deben = new Set(members.map((m) => m.pub))
|
|
2060
2090
|
const traidos = new Set(Object.keys(wraps).filter((k) => k !== RECOVERY_WRAP))
|
|
@@ -2146,7 +2176,10 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
2146
2176
|
const out = { drawers: 0, wrapped: 0, dropped: 0, sinLlave: [], failed: [] }
|
|
2147
2177
|
for (const owner of secrets.owners?.() || []) {
|
|
2148
2178
|
const before = new Set(secrets.recipientsIn(owner))
|
|
2149
|
-
|
|
2179
|
+
// POR VISIBILIDAD, no una lista fija: una pública lleva además la envoltura de quien
|
|
2180
|
+
// administra. Con una sola lista se le daría también la de una privada con dueño, que
|
|
2181
|
+
// es justo lo que no puede pasar.
|
|
2182
|
+
const members = (esPublica) => recipientsOf(owner, { public: esPublica })
|
|
2150
2183
|
try {
|
|
2151
2184
|
const r = await secrets.rewrap(owner, members, adminKey, { exact: true })
|
|
2152
2185
|
const after = new Set(secrets.recipientsIn(owner))
|
|
@@ -2179,7 +2212,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
2179
2212
|
// Los destinatarios de un cajón no son solo quien lo consume: también quien lo
|
|
2180
2213
|
// administra (`recipientsOf`). Envolver solo para los primeros dejaba a la consola
|
|
2181
2214
|
// sin poder destapar lo que ella misma acababa de saldar.
|
|
2182
|
-
const members =
|
|
2215
|
+
const members = (esPublica) => recipientsOf(owner, { public: esPublica })
|
|
2183
2216
|
try { out[owner] = await spreadKey(owner, members, adminKey) } catch (e) {
|
|
2184
2217
|
// Sin frase, el último recurso es pedírselo a quien sí puede: el propio servicio.
|
|
2185
2218
|
let delegated = 0
|
|
@@ -2213,7 +2246,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
2213
2246
|
// (dueño, 2026-09-01: «al envolver hay que envolver siempre para todos los selladores,
|
|
2214
2247
|
// para que siempre tengan la info fresca»). Dos listas para la misma pregunta acaban
|
|
2215
2248
|
// siempre así: una se actualiza y la otra no.
|
|
2216
|
-
try { return await spreadKey(owner,
|
|
2249
|
+
try { return await spreadKey(owner, (esPublica) => recipientsOf(owner, { public: esPublica }), null) } catch (_) { return null }
|
|
2217
2250
|
}
|
|
2218
2251
|
async function deleteSecret (ns, key) { const ok = await secrets.delete(ns, key); if (ok) { audit('secret.rm', { ns, key }); scheduleNotice(ns) } return ok }
|
|
2219
2252
|
|
|
@@ -2301,6 +2334,34 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
2301
2334
|
* de esta máquina»: taparlo justo aquí —en la máquina donde vive, delante de su dueño—
|
|
2302
2335
|
* era lo único que la marca no significaba. La consola remota ya las enseña.
|
|
2303
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
|
+
|
|
2304
2365
|
function listSecrets () {
|
|
2305
2366
|
// YA NO SE ENSEÑA EL VALOR DE UNA PÚBLICA (dueño, 2026-09-02). Desde que todas van en
|
|
2306
2367
|
// sobre, «pública» dejó de significar «en claro»: dice a quién se le despacha sin
|
|
@@ -2448,7 +2509,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
2448
2509
|
// envolver hay que envolver siempre para todos los selladores, para que siempre
|
|
2449
2510
|
// tengan la info fresca»). Repartiendo solo a los del ns, un cajón tocado por esta
|
|
2450
2511
|
// puerta dejaba a los selladores con la envoltura vieja.
|
|
2451
|
-
await spreadKey(`ns:${m.cn}`,
|
|
2512
|
+
await spreadKey(`ns:${m.cn}`, (esPublica) => recipientsOf(`ns:${m.cn}`, { public: esPublica }), adminKey).catch(async (e) => {
|
|
2452
2513
|
log('[vault] could not hand the key to the new service:', e.message)
|
|
2453
2514
|
// Sin la frase la bóveda no puede envolvérsela… pero un HERMANO suyo sí: otro
|
|
2454
2515
|
// servicio del mismo cajón ya tiene la llave abierta (§8.11). Si contesta, el
|