@dotrino/vault 0.82.2 → 0.84.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vault",
3
- "version": "0.82.2",
3
+ "version": "0.84.0",
4
4
  "description": "Usa ESTE dispositivo (navegador) como bóveda/CA del ecosistema Dotrino: atiende enrolamientos por el proxy y firma certificados de delegación a tus máquinas. Incluye el cliente de SERVICIO (Node): un proyecto se enrola una vez y jala sus credenciales del vault en vez del .env (`import '@dotrino/vault/config'`).",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -59,7 +59,7 @@
59
59
  "pairing"
60
60
  ],
61
61
  "peerDependencies": {
62
- "@dotrino/identity": ">=0.99.0",
62
+ "@dotrino/identity": ">=0.111.0",
63
63
  "@dotrino/proxy-client": ">=0.23.1"
64
64
  },
65
65
  "license": "MIT",
package/src/enroll.js CHANGED
@@ -30,7 +30,7 @@
30
30
  * `docs/pairing-protocol.md`).
31
31
  */
32
32
  import { verifyDeviceSig, pubkeyId, commitCode } from '@dotrino/identity/capabilities'
33
- import { verifyContinuity, canSeal, DEVICE_CAPS } from '@dotrino/identity/acta'
33
+ import { verifyContinuity, canSeal, DEVICE_CAPS, VAULT_CN } from '@dotrino/identity/acta'
34
34
 
35
35
  /** Un token de emparejamiento vale 5 min. */
36
36
  export const PAIRING_TTL_MS = 5 * 60 * 1000
@@ -169,12 +169,16 @@ export function createEnrollDesk ({
169
169
  * el código, igual que hacía `caps` después.
170
170
  */
171
171
  async function startPairing ({ scope = defaultScope, ttlMs = defaultTtlMs, label = '', mode = 'join', account = '', caps = null } = {}) {
172
+ // El nombre de la propia bóveda no es un servicio: su llave firma con el perfil cerrado
173
+ // y no puede abrir ningún cajón. Un servicio que entrara con ese nombre tampoco podría.
174
+ if (scopeToCn(scope) === VAULT_CN) throw Object.assign(new Error(`"${VAULT_CN}" is the vault's own name: a service cannot use it`), { code: 'reserved-cn' })
172
175
  if (caps != null) {
173
176
  if (!Array.isArray(caps)) throw Object.assign(new Error('caps must be a list'), { code: 'bad-caps' })
174
177
  const cn = scopeToCn(scope)
175
- const bad = caps.filter((c) => !(DEVICE_CAPS.includes(c) || (c === 'secrets' && cn)))
178
+ const bad = caps.filter((c) => !DEVICE_CAPS.includes(c))
176
179
  if (bad.length) throw Object.assign(new Error('unknown permission(s): ' + bad.join(', ')), { code: 'bad-caps' })
177
- if (!caps.length) throw Object.assign(new Error('the pairing grants no permission'), { code: 'empty-scope' })
180
+ // Un servicio puede entrar sin ningún permiso: su cajón lo abre el CN.
181
+ if (!caps.length && !cn) throw Object.assign(new Error('the pairing grants no permission'), { code: 'empty-scope' })
178
182
  caps = [...new Set(caps)]
179
183
  }
180
184
  pending.clear() // uno a la vez: una sesión nueva supersede a la anterior
@@ -378,13 +382,14 @@ export function createEnrollDesk ({
378
382
  throw Object.assign(new Error('this vault cannot add devices to the account record: no certificate was issued'), { code: 'admit-unavailable' })
379
383
  }
380
384
  // PERMISOS, no tipos (2026-08-22): las capacidades son las del scope ENTERO. Un
381
- // cajón (`secrets:<ns>`) suma `secrets` y fija el CN; no borra lo demás — un bot
382
- // con `sign,secrets:eco` firma como aparato del acta Y lee solo su cajón.
385
+ // cajón (`secrets:<ns>`) fija el CN, que es lo que lo abre —no hay permiso aparte
386
+ // (dueño, 2026-10-13)—, y no borra lo demás: un bot con `sign,secrets:eco` firma como
387
+ // aparato del acta Y lee solo su cajón.
383
388
  // Si quien emparejó ELIGIÓ los permisos (`startPairing({ caps })`), entran esos y en
384
389
  // esta misma acta; si no, los que da el scope.
385
390
  const cn = scopeToCn(pend.scope)
386
- const caps = [...new Set([...(pend.caps || scopeToCaps(pend.scope)), ...(cn ? ['secrets'] : [])])]
387
- if (!caps.length) {
391
+ const caps = [...new Set(pend.caps || scopeToCaps(pend.scope))]
392
+ if (!caps.length && !cn) {
388
393
  throw Object.assign(new Error('the pairing scope grants no permission: no certificate was issued'), { code: 'empty-scope' })
389
394
  }
390
395
 
package/src/index.js CHANGED
@@ -217,7 +217,10 @@ export async function startDeviceVault (identity, { proxyUrl, client: injectedCl
217
217
  deviceId: x.sub ? await deviceIdOf(x.sub) : null, sub: x.sub || null,
218
218
  label: x.label || '', scope: x.scope, seq: x.seq, nonce: x.nonce
219
219
  })))
220
- send(from, { type: MSG.DEVICES_RESULT, devices, revoked: (revoked || []).map((r) => r.nonce || r) })
220
+ // La cadena de selladores va con la lista (como en el daemon): es lo que un aparato de
221
+ // Node necesita para firmar a nombre de la cuenta de forma que un extraño lo compruebe.
222
+ const sealerChain = await identity.sealerChain?.().catch(() => null) || null
223
+ send(from, { type: MSG.DEVICES_RESULT, devices, revoked: (revoked || []).map((r) => r.nonce || r), sealerChain })
221
224
  // ¿el que consulta es una máquina revocada que reapareció? → re-emite el REVOKED firmado.
222
225
  // Se busca en `revokedCerts`: desde que `issued` solo trae lo vigente, los retirados
223
226
  // ya no están ahí y este aviso dejaba de dispararse (se caía en silencio).