@dotrino/vaultd 0.94.0 → 0.95.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.
Files changed (55) hide show
  1. package/README.md +0 -0
  2. package/bin/dotrino-vault-tui.js +0 -0
  3. package/bin/dotrino-vault.js +0 -0
  4. package/bin/dotrino-vaultd.js +0 -0
  5. package/bin/sea-entry.js +0 -0
  6. package/lib/src/admin.js +0 -0
  7. package/lib/src/atrest.js +0 -0
  8. package/lib/src/config.js +0 -0
  9. package/lib/src/enroll.js +0 -0
  10. package/lib/src/env.js +0 -0
  11. package/lib/src/envtext.js +0 -0
  12. package/lib/src/index.js +0 -0
  13. package/lib/src/invite.js +0 -0
  14. package/lib/src/kek.js +0 -0
  15. package/lib/src/lock.js +0 -0
  16. package/lib/src/protocol.js +0 -0
  17. package/lib/src/revocation.js +0 -0
  18. package/lib/src/sealed.js +0 -0
  19. package/lib/src/service.js +0 -0
  20. package/lib/src/sshAgent.js +0 -0
  21. package/lib/src/sshKeys.js +0 -0
  22. package/package.json +1 -1
  23. package/src/approvals.js +0 -0
  24. package/src/atrest.js +0 -0
  25. package/src/client.js +0 -0
  26. package/src/commKey.js +0 -0
  27. package/src/ctl.js +6 -4
  28. package/src/daemon.js +0 -0
  29. package/src/ipc.js +0 -0
  30. package/src/keyowner.js +0 -0
  31. package/src/manager.js +0 -0
  32. package/src/node-globals.js +0 -0
  33. package/src/passwords.js +0 -0
  34. package/src/paths.js +0 -0
  35. package/src/profiles.js +0 -0
  36. package/src/protocol.js +0 -0
  37. package/src/qr.js +0 -0
  38. package/src/sealKey.js +0 -0
  39. package/src/sealer.js +0 -0
  40. package/src/sealers.js +0 -0
  41. package/src/secretsStore.js +0 -0
  42. package/src/selfupdate.js +0 -0
  43. package/src/sshAgent.js +0 -0
  44. package/src/sshKeys.js +0 -0
  45. package/src/store.js +0 -0
  46. package/src/threadStore.js +0 -0
  47. package/src/transport.js +0 -0
  48. package/src/tui/app.js +0 -0
  49. package/src/tui/i18n.js +0 -0
  50. package/src/tui/term.js +0 -0
  51. package/src/vault.js +10 -2
  52. package/src/vaultControl.js +0 -0
  53. package/src/version.js +0 -0
  54. package/vendor/qrcode-generator.cjs +0 -0
  55. package/lib/README.md +0 -289
package/README.md CHANGED
File without changes
File without changes
File without changes
File without changes
package/bin/sea-entry.js CHANGED
File without changes
package/lib/src/admin.js CHANGED
File without changes
package/lib/src/atrest.js CHANGED
File without changes
package/lib/src/config.js CHANGED
File without changes
package/lib/src/enroll.js CHANGED
File without changes
package/lib/src/env.js CHANGED
File without changes
File without changes
package/lib/src/index.js CHANGED
File without changes
package/lib/src/invite.js CHANGED
File without changes
package/lib/src/kek.js CHANGED
File without changes
package/lib/src/lock.js CHANGED
File without changes
File without changes
File without changes
package/lib/src/sealed.js CHANGED
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.94.0",
3
+ "version": "0.95.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/approvals.js CHANGED
File without changes
package/src/atrest.js CHANGED
File without changes
package/src/client.js CHANGED
File without changes
package/src/commKey.js CHANGED
File without changes
package/src/ctl.js CHANGED
@@ -963,12 +963,14 @@ function verifyActivity (lines) {
963
963
  let prev = null; let seq = null; let checked = 0; let unchained = 0
964
964
  for (const text of lines) {
965
965
  let e; try { e = JSON.parse(text) } catch { continue }
966
- if (typeof e.seq !== 'number') { unchained++; continue } // entradas de antes de 0.94
966
+ // Sin `logSeq` es de antes de que la cadena existiera: no se puede verificar, y se
967
+ // dice cuántas hay en vez de callarlo.
968
+ if (typeof e.logSeq !== 'number') { unchained++; continue }
967
969
  if (seq !== null) {
968
- if (e.seq !== seq + 1) return { ok: false, at: e.seq, why: `salta del #${seq} al #${e.seq}` }
969
- if (e.prev !== prev) return { ok: false, at: e.seq, why: `el #${e.seq} no encadena con el #${seq}` }
970
+ if (e.logSeq !== seq + 1) return { ok: false, at: e.logSeq, why: `salta del #${seq} al #${e.logSeq}` }
971
+ if (e.logPrev !== prev) return { ok: false, at: e.logSeq, why: `el #${e.logSeq} no encadena con el #${seq}` }
970
972
  }
971
- seq = e.seq; prev = sha256(text); checked++
973
+ seq = e.logSeq; prev = sha256(text); checked++
972
974
  }
973
975
  return { ok: true, checked, unchained, last: seq }
974
976
  }
package/src/daemon.js CHANGED
File without changes
package/src/ipc.js CHANGED
File without changes
package/src/keyowner.js CHANGED
File without changes
package/src/manager.js CHANGED
File without changes
File without changes
package/src/passwords.js CHANGED
File without changes
package/src/paths.js CHANGED
File without changes
package/src/profiles.js CHANGED
File without changes
package/src/protocol.js CHANGED
File without changes
package/src/qr.js CHANGED
File without changes
package/src/sealKey.js CHANGED
File without changes
package/src/sealer.js CHANGED
File without changes
package/src/sealers.js CHANGED
File without changes
File without changes
package/src/selfupdate.js CHANGED
File without changes
package/src/sshAgent.js CHANGED
File without changes
package/src/sshKeys.js CHANGED
File without changes
package/src/store.js CHANGED
File without changes
File without changes
package/src/transport.js CHANGED
File without changes
package/src/tui/app.js CHANGED
File without changes
package/src/tui/i18n.js CHANGED
File without changes
package/src/tui/term.js CHANGED
File without changes
package/src/vault.js CHANGED
@@ -400,7 +400,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
400
400
  try {
401
401
  const text = activityAtRest.decrypt(lines[i])
402
402
  const e = JSON.parse(text)
403
- if (typeof e.seq === 'number') { auditSeq = e.seq; auditPrev = sha256(text); break }
403
+ if (typeof e.logSeq === 'number') { auditSeq = e.logSeq; auditPrev = sha256(text); break }
404
404
  } catch (_) { /* una línea vieja o ilegible: se sigue hacia atrás */ }
405
405
  }
406
406
  } catch (_) { /* no hay bitácora todavía */ }
@@ -424,7 +424,15 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
424
424
 
425
425
  const audit = (op, info = {}) => {
426
426
  try {
427
- const entry = { seq: ++auditSeq, prev: auditPrev, ts: Date.now(), op, ...info }
427
+ // LOS CAMPOS DE LA CADENA VAN AL FINAL Y CON NOMBRE PROPIO, y las dos cosas por el
428
+ // mismo motivo: que quien llama no pueda pisarlos.
429
+ //
430
+ // Estaban delante y se llamaban `seq`/`prev`, así que `...info` los sobrescribía. Y
431
+ // no era hipotético: `renew` ya mandaba un `seq` —el del ACTA, que no tiene nada que
432
+ // ver— y dejaba dos entradas seguidas numeradas igual. Lo cazó el propio verificador
433
+ // contra la bitácora de producción a los diez minutos de desplegarlo, que es
434
+ // exactamente para lo que sirve.
435
+ const entry = { ...info, ts: Date.now(), op, logSeq: ++auditSeq, logPrev: auditPrev }
428
436
  const text = JSON.stringify(entry)
429
437
  fs.appendFileSync(activityFile, activityAtRest.encrypt(text) + '\n', { mode: 0o600 })
430
438
  auditPrev = sha256(text)
File without changes
package/src/version.js CHANGED
File without changes
File without changes
package/lib/README.md DELETED
@@ -1,289 +0,0 @@
1
- # @dotrino/vault
2
-
3
- Usa **este dispositivo (navegador) como bóveda/CA** del ecosistema Dotrino, sin un PC
4
- con el daemon. Es la contraparte browser del daemon `dotrino-vault`: atiende el mismo
5
- protocolo de enrolamiento endurecido por el proxy y firma certificados de delegación
6
- `D ← P` (donde `P` es la identidad de este dispositivo, `@dotrino/identity`).
7
-
8
- Pensado para que **cualquier app** del ecosistema (no solo la terminal) pueda ofrecer
9
- "usar este dispositivo como bóveda".
10
-
11
- ## Uso
12
-
13
- ```js
14
- import { Identity } from '@dotrino/identity'
15
- import { startDeviceVault } from '@dotrino/vault'
16
-
17
- const identity = await Identity.connect()
18
- const vault = await startDeviceVault(identity) // se conecta al proxy como P
19
-
20
- // 1) Abrir un emparejamiento y mostrar el QR/JSON al dispositivo a enrolar:
21
- const { qr } = vault.startPairing({ label: 'mi-agente' })
22
- // El dispositivo (p. ej. @dotrino/identity#enrollDevice) consume `qr`, GENERA un
23
- // código aleatorio y lo MUESTRA (no lo envía).
24
-
25
- // 2) Cuando el dispositivo pide acceso, aparece en la lista de pendientes:
26
- vault.onPendingChange(() => {
27
- for (const { deviceId } of vault.listPending()) {
28
- // Un humano LEE el código del dispositivo y lo TIPEA aquí:
29
- // await vault.approve(deviceId, codigoTipeado)
30
- }
31
- })
32
-
33
- // 3) Máquinas ya enroladas / revocar:
34
- const machines = await vault.listMachines() // [{ sub, deviceId, label, exp, nonce, scope }]
35
- // await vault.revoke(nonce)
36
-
37
- vault.close()
38
- ```
39
-
40
- ## Credenciales del vault en vez del `.env` (Node)
41
-
42
- La cara "dotenv" del paquete: **cualquier proyecto Node** jala sus credenciales del
43
- vault del dueño y las deja en `process.env`. En el disco del servicio **no queda
44
- ningún secreto**: solo la llave del dispositivo (generada ahí, nunca sale) y un
45
- certificado con scope `vault:secrets:<ns>`. Los valores viven **solo en memoria**;
46
- si la máquina se compromete, revocas el cert y no había nada que robar.
47
-
48
- ### 1) Registro del cliente (una sola vez)
49
-
50
- ```bash
51
- # en el VAULT (tu PC): abres el emparejamiento del servicio y cargas sus secretos
52
- dotrino-vault pair --service miapp # invitación con scope SOLO vault:secrets:miapp
53
- dotrino-vault secret set miapp API_KEY sk-… # la comparten TODAS las máquinas del ns
54
-
55
- # en el PROYECTO/servidor: enrola esta máquina (pega la invitación)
56
- npx dotrino-env enroll --ns miapp
57
- # → muestra un código: dotrino-vault approve 7K3F-92Q1
58
-
59
- # de vuelta en el VAULT: lo tipeas leyéndolo de esa pantalla
60
- dotrino-vault approve 7K3F-92Q1
61
- ```
62
-
63
- Si la misma app corre en **varias máquinas**, lo que cambia de una a otra (el puerto,
64
- la URL pública) va en el cajón **por aparato**, sin partir el `ns`:
65
-
66
- ```bash
67
- dotrino-vault devices # el ID del aparato: AB12-CD34
68
- dotrino-vault secret device set AB12-CD34 PORT 8443 # solo la lee ESA máquina
69
- ```
70
-
71
- Llegan **mezcladas en el mismo bundle** —y por lo tanto en el mismo `process.env`—:
72
- las del scope, con las del aparato **encima** si se llaman igual.
73
-
74
- El código lo **genera el servicio** y **no viaja** por la red: el vault solo puede
75
- echarlo de vuelta si un humano lo tipeó. Así, un vault falso no puede enrolarte y
76
- aprobar a ciegas no enrola a nadie. Queda `~/.dotrino/service/<ns>/service-identity.json`
77
- (0600) con `{ device, cert, iss, proxy, ns }`.
78
-
79
- Es un **comando previo**, no el primer arranque de la app: el enrolamiento necesita a
80
- un humano leyendo el código en esta pantalla (bajo systemd/PM2 no hay TTY y el código
81
- acabaría en un log), bloquea esperando la aprobación y **escribe** en disco consumiendo
82
- una invitación de un solo uso. El arranque, en cambio, solo **lee** la identidad ya
83
- guardada: es idempotente y no interactúa con nadie. Corre el `enroll` donde corres el
84
- `npm ci` al aprovisionar la máquina.
85
-
86
- ### 2) En el código
87
-
88
- ```js
89
- import '@dotrino/vault/config' // como `dotenv/config`, pero contra el vault (ns = DOTRINO_NS)
90
- console.log(process.env.API_KEY)
91
- ```
92
-
93
- o explícito:
94
-
95
- ```js
96
- import { loadEnv } from '@dotrino/vault/env'
97
- const { secrets } = await loadEnv({ ns: 'miapp', required: ['API_KEY'] })
98
- ```
99
-
100
- Es **asíncrono a propósito**: el `import` bloquea el arranque (top-level await) hasta
101
- que los secretos estén. Si el vault no está disponible, **espera** (reintento con
102
- backoff) — un servicio sin vault no arranca, no opera con secretos viejos ni vacíos.
103
- Un fallo NO transitorio (sin enrolar, cert revocado, scope equivocado) sí aborta.
104
-
105
- #### Esperar al vault es la REGLA. La excepción es una sola
106
-
107
- Un agente enrolado **espera**. No es una preferencia: arrancar igual significaría
108
- operar con la configuración vieja del `.env`, que es justo lo que el vault vino a
109
- dejar de ser. Y la espera casi nunca duele, porque estos agentes no son críticos:
110
- que un bot o un firmador tarden en levantar no rompe a nadie.
111
-
112
- La **única** excepción conocida es **el proxio**, y no por importancia sino por una
113
- razón estructural: el vault habla con sus servicios **por el proxio**. Un proxio que
114
- espera al vault espera a alguien que necesita que el proxio ya esté escuchando —
115
- abrazo mortal, y con él se cae el vault de todo el mundo. Por eso el proxio arranca
116
- con lo que tenga y aplica la configuración cuando llega, con `applyEnv`.
117
-
118
- > `applyEnv` existe **para ese caso**, no como alternativa cómoda al bloqueo. Si tu
119
- > agente no está en el camino por el que viaja el propio vault, usa
120
- > `import '@dotrino/vault/config'` y deja que espere. El precio de la excepción es
121
- > real: lo que sólo se lee al arrancar llega tarde y no toma efecto hasta reiniciar,
122
- > así que hay que avisarlo en el log — el proxio lo hace.
123
-
124
- Para procesos que no son Node, el CLI los inyecta en el entorno de un hijo:
125
-
126
- ```bash
127
- dotrino-env run --ns miapp -- ./mi-binario
128
- ```
129
-
130
- ### Un agente tiene UNA identidad, y se la da el vault
131
-
132
- Un **aparato** puede llevar varios perfiles, y hasta meter su propia cuenta al vault
133
- por adopción: llega con una historia que conservar. Un **agente** no es eso. Es un
134
- servicio: su identidad se la cede el vault y no hay caso en que quiera empujar la
135
- suya hacia arriba. De ahí tres reglas, que el paquete aplica solas:
136
-
137
- - **No adopta, nunca.** La invitación declara su modo (`join` / `adopt`); si viene
138
- abierta para adoptar, `enrollService` la **rechaza al pegarla**, sin salir a la red.
139
- La intención `join` va además firmada dentro de la petición, para que nadie en el
140
- medio la convierta en otra cosa.
141
- - **No acumula.** Enrolar de nuevo **reemplaza** la identidad anterior, que deja de
142
- existir en ese agente. No es un error a desbloquear con `--force`: es la forma de
143
- **rotar** la identidad de un agente comprometido. Se avisa por `onReplace` qué se
144
- descarta.
145
- - **Un agente, un `ns`.** Varios agentes pueden convivir en una máquina (un
146
- directorio por namespace); lo que no existe es un agente que sea varios.
147
-
148
- > ⚠ Si esa llave es además la **identidad de red** del servicio —el caso del proxio,
149
- > cuyo id de nodo se deriva de ella— reemplazarla le cambia el nombre en la red: las
150
- > instancias y citas vivas dejan de resolver y los peers que lo tenían pineado lo
151
- > rechazan hasta re-pinearlo. Es a propósito: así se echa a un nodo comprometido.
152
-
153
- ### Rotar: la bóveda avisa y el agente SE REINICIA
154
-
155
- Cambiar un secreto en la bóveda no sirve de nada si quien lo usa no se entera. Al
156
- guardar, la bóveda manda un **aviso firmado** a los agentes de ese `ns` (sin
157
- valores: sólo dice que cambió), **agrupando** las escrituras seguidas para que
158
- cargar cinco valores no provoque cinco reinicios.
159
-
160
- El agente **no recarga en caliente: termina**, y lo levanta su supervisor (pm2,
161
- systemd `Restart=always`). Con `import '@dotrino/vault/config'` ya viene puesto.
162
-
163
- Salir en vez de recargar, por tres razones — y la primera es la de peso:
164
-
165
- 1. **Borra de memoria el valor viejo.** En JavaScript un secreto no se puede
166
- borrar: los strings son inmutables, no hay `zeroize`, y el valor sigue en el
167
- heap hasta que al recolector le apetezca, más lo que capturó cada *closure* y
168
- cada caché derivada. Una llave se rota casi siempre **porque se filtró**, así
169
- que dejarla viva en el proceso anula el motivo de rotarla. Un proceso nuevo
170
- empieza con el heap limpio.
171
- 2. **Lee todo fresco.** Recargar en caliente exige que cada sitio que leyó una
172
- variable sepa releerla; esa lista hay que mantenerla para siempre y, cuando se
173
- queda corta, falla en silencio.
174
- 3. **Es un interruptor de emergencia.** Revocar el cert de un agente ya no espera a
175
- que alguien se acuerde de reiniciarlo: recibe el `REVOKED` firmado, se apaga, y
176
- al arrancar `fetchSecrets` recibe «unauthorized: revoked», que no se arregla
177
- reintentando. Antes, revocar no le quitaba nada a un proceso ya corriendo.
178
-
179
- Defensas, porque una señal que provoca reinicios es un arma si se descuida: firma
180
- de la maestra pineada, `ns` que coincida, frescura y anti-replay, **gracia de
181
- arranque** y **piso entre avisos** (si la configuración nueva rompe el arranque, sin
182
- eso el servicio entra en ciclo) y **jitter** (diez agentes del mismo `ns` no salen
183
- todos en el mismo segundo).
184
-
185
- ```js
186
- import { watchEnv } from '@dotrino/vault/env'
187
- await watchEnv({ ns: 'miapp' }) // termina el proceso al cambiar
188
- await watchEnv({ ns: 'proxy', onUpdate: (i) => … }) // o decide tú (ver abajo)
189
- ```
190
-
191
- `onUpdate` es para cuando terminar **no es una opción**. El caso real es el proxio:
192
- reiniciarlo corta el transporte de todo el ecosistema, así que anota el aviso, lo
193
- publica en su `GET /peers` y deja el momento a un humano.
194
-
195
- ### Precedencia: el vault MANDA
196
-
197
- Los valores del vault **pisan** los del `.env` y los del entorno. El vault no
198
- reemplaza al `.env` —que sigue siendo lo que arranca una máquina sin enrolar— pero
199
- sí tiene la última palabra sobre las claves que administra.
200
-
201
- Esa es la pieza que hace barata la **rotación**: cambias el valor en un solo lugar y
202
- ningún `.env` viejo olvidado en un VPS puede seguir ganando. Con la precedencia al
203
- revés (como estaba hasta la 0.14.0) rotar exigía además ir a limpiar cada copia
204
- rancia —el trabajo que se quería evitar— y, peor, el servicio arrancaba con la llave
205
- vieja **sin decir nada**.
206
-
207
- Lo que sí se dice en voz alta: al arrancar se listan las claves que el vault tuvo que
208
- pisar. Es la señal de que en esa máquina quedó un `.env` por limpiar.
209
-
210
- ```bash
211
- DOTRINO_ENV_OVERRIDE=0 node server.js # escotilla: por esta corrida, gana el entorno
212
- dotrino-env check # dice qué claves pisaría en esta máquina
213
- ```
214
-
215
- ### API `@dotrino/vault/env`
216
-
217
- - `loadEnv({ ns?, dir?, override?, wait?, required?, onRetry? }) → { ns, secrets, injected, overridden, skipped }`
218
- (por defecto **pisa** lo que ya esté en el entorno; `override: false` invierte la regla).
219
- `overridden` son las claves que tenían otro valor y el vault reemplazó.
220
- - `applyEnv(secrets, override?) → { injected, overridden, skipped }` — vuelca un bundle
221
- ya obtenido, sin pedirlo. Para servicios que **no pueden bloquear su arranque**
222
- esperando al vault y lo aplican cuando llega (el caso del proxy: el vault le habla
223
- *por* el proxy, así que esperarlo sería un abrazo mortal).
224
- - `serviceDir(ns)`, `serviceRoot()`, `listEnrolled()`, `resolveNs(ns?)`
225
- - Entorno: `DOTRINO_NS` · `DOTRINO_ENV_DIR` · `DOTRINO_ENV_HOME` · `DOTRINO_ENV_QUIET` ·
226
- `DOTRINO_ENV_OVERRIDE`
227
- - CLI: `dotrino-env enroll|status|check|run` (`check` lista **nombres** de secretos, nunca valores)
228
-
229
- Bajo el capó es `@dotrino/vault/service` (`enrollService` / `waitForSecrets`): petición
230
- firmada por la llave del servicio + cert, respuesta **sellada** (ECDH efímero + AES-GCM,
231
- el proxy no ve los valores) y **firmada por la maestra**, verificada contra la `iss`
232
- pineada en el enrolamiento.
233
-
234
- ## Modelo de aprobación (seguro por diseño)
235
-
236
- - El **dispositivo** que se enrola genera un **código aleatorio** (`makePairingCode`) y
237
- lo **muestra**; el código **no viaja** por la red.
238
- - Esta bóveda **no conoce** el código: un humano lo **lee del dispositivo** y lo **tipea**
239
- aquí. Al aprobar, la bóveda firma el cert y **echa** el código tipeado de vuelta.
240
- - El dispositivo acepta el cert **solo si el código echado coincide** con el que generó.
241
- Así, una bóveda falsa (que nunca vio el código) no puede enrolarlo, y **aprobar a ciegas**
242
- (sin ir a leer el código del dispositivo) no enrola a nadie.
243
-
244
- ## API
245
-
246
- `startDeviceVault(identity, { proxyUrl? }) → Promise<handle>`
247
-
248
- - `startPairing({ scope?, ttlMs?, label?, mode?, account? }) → { qr, expiresInMs }`
249
- - `stopPairing(token)`
250
- - `listPending() → [{ deviceId, label }]`
251
- - `approve(deviceId, code) → Promise<{ ok, deviceId }>` (code = lo que muestra el dispositivo)
252
- - `reject(deviceId)`
253
- - `listMachines() → Promise<[{ sub, deviceId, label, scope, exp, nonce }]>`
254
- - `revoke(nonce) → Promise`
255
- - `getSelfCert() → Promise<cert>` (self-cert `P ← P`, para actuar además de cliente)
256
- - `onPendingChange(fn)`, `onAdopted(fn)`, `close()`
257
-
258
- Cripto y firma: `@dotrino/identity`. Transporte: `@dotrino/proxy-client`. No reimplementa
259
- nada del ecosistema.
260
-
261
- ### Qué atiende, y qué NO
262
-
263
- Esta bóveda **no es** el daemon del PC: comparte el núcleo de enrolamiento
264
- (`lib/src/enroll.js`, el mismo archivo), pero atiende menos mensajes del protocolo.
265
-
266
- Atiende: `vault.hello` (la llave que pide el QR corto), `vault.enroll` +
267
- `vault.acta.sealed` (enrolar y adoptar), `vault.renew` (**renovación automática** del
268
- cert de una máquina vigente: sin esto toda máquina enrolada caducaba a los 30 días) y
269
- `vault.devices` (lista + revocados, con re-emisión del `REVOKED` firmado).
270
-
271
- **No** atiende, y hoy solo existen contra el daemon `dotrino-vault`:
272
-
273
- | Falta | Qué implica |
274
- |---|---|
275
- | `vault.sign` | una máquina no puede pedirle a la maestra que firme por ella |
276
- | `vault.store` / `vault.get` | no hay store centralizado, ni edición de perfil, ni clave de contenido |
277
- | `vault.secrets` | `@dotrino/vault/config` (el reemplazo del `.env`) **no funciona** contra un dispositivo |
278
- | `vault.admin` | sin consola remota |
279
- | bitácora, cifrado en reposo, candado, multi-perfil | son del daemon; en el navegador dependen de `@dotrino/identity` |
280
-
281
- MIT · parte de [Dotrino](https://dotrino.com).
282
-
283
- ## Agente SSH con llaves en memoria (`dotrino-env ssh-agent`)
284
-
285
- La llave SSH es un secreto más del cajón (`SSH_KEY_*`, el archivo en base64). Al arrancar,
286
- el agente pide el cajón a la bóveda (con la aprobación del teléfono si el aparato la pide),
287
- carga las llaves en memoria y sirve el protocolo de `ssh-agent`; en el disco no queda nada.
288
- `dotrino-env ssh-agent --ns ssh` imprime el `SSH_AUTH_SOCK`. Como librería:
289
- `loadPrivateKey`, `publicLine`, `signSsh` (`src/sshKeys.js`) y `startSshAgent` (`src/sshAgent.js`).