@dotrino/vaultd 0.106.0 → 0.106.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/vault.js +47 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.106.0",
3
+ "version": "0.106.2",
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
@@ -1330,7 +1330,30 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1330
1330
  sock.on('error', cerrar)
1331
1331
  })
1332
1332
  server.on('error', (e) => log('[vault] the local desk could not open (' + e.message + '): services will use the proxy'))
1333
- try { fs.mkdirSync(socketDir(), { recursive: true, mode: 0o700 }) } catch (_) {}
1333
+ // EL DIRECTORIO ES EL CONTROL, no el modo del socket.
1334
+ //
1335
+ // `mkdir` con `mode` NO corrige un directorio que ya existe, y sin `XDG_RUNTIME_DIR`
1336
+ // —un servicio de sistema, un contenedor— esto cae en `/tmp`, donde cualquiera puede
1337
+ // haberlo creado antes con permiso de escritura para todos. Quien sea dueño del
1338
+ // directorio puede borrar el socket y poner el suyo: los servicios de esta máquina
1339
+ // acabarían hablando con él. No leería nada (la respuesta va firmada por una llave que
1340
+ // nombra el acta y el cliente la comprueba), pero los deja sin arrancar.
1341
+ //
1342
+ // Y el `chmod 0600` de abajo llega DESPUÉS de `listen`, así que por sí solo deja una
1343
+ // rendija. Lo que la cierra de verdad es que el directorio sea nuestro y solo nuestro.
1344
+ const dir = socketDir()
1345
+ try { fs.mkdirSync(dir, { recursive: true, mode: 0o700 }) } catch (_) {}
1346
+ try {
1347
+ const st = fs.statSync(dir)
1348
+ const ajeno = typeof process.getuid === 'function' && st.uid !== process.getuid()
1349
+ const abierto = (st.mode & 0o077) !== 0
1350
+ if (ajeno || abierto) {
1351
+ return log(`[vault] the local desk stays closed: ${dir} is ${ajeno ? 'owned by another user' : 'writable by others'} ` +
1352
+ '— services on this machine will use the proxy instead')
1353
+ }
1354
+ } catch (e) {
1355
+ return log('[vault] the local desk stays closed: could not check ' + dir + ' (' + e.message + ')')
1356
+ }
1334
1357
  try { fs.rmSync(socketPath, { force: true }) } catch (_) {}
1335
1358
  server.listen(socketPath, () => {
1336
1359
  try { fs.chmodSync(socketPath, 0o600) } catch (_) {}
@@ -1900,6 +1923,29 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
1900
1923
  audit('rejected', { what: 'renounce', reason: 'signature' })
1901
1924
  return reply(from, { type: MSG.ERROR, error: 'renounce: the signature is not the member own' })
1902
1925
  }
1926
+ // UNA RENUNCIA QUE NO QUITA NADA NO SE HONRA, y esto es lo que corta el replay barato.
1927
+ //
1928
+ // El registro va firmado y lleva `ts`, pero nadie lo miraba: cualquiera que lo viera
1929
+ // pasar por el proxio podía volver a mandarlo cuando quisiera. Como `absorbRenounce`
1930
+ // SELLA UN ACTA NUEVA cada vez, repetirlo hacía subir el `seq` y empujar a todos los
1931
+ // aparatos sin que hubiera cambiado nada — y dejaba en la bitácora una renuncia con el
1932
+ // nombre del aparato, que es lo peor: la bitácora es la prueba, y ahí decía algo que
1933
+ // ese aparato no hizo.
1934
+ //
1935
+ // Mirando lo que el acta dice HOY, un registro repetido no quita nada y se para aquí.
1936
+ // Lo que esto NO cierra, y queda anotado: si el dueño vuelve a conceder ese permiso, el
1937
+ // mismo registro viejo lo quita otra vez. Cerrarlo del todo pide que el acta recuerde
1938
+ // la fecha de la última renuncia por miembro y rechace las que no sean posteriores.
1939
+ const actual = (await identity.profileActa?.().catch(() => null))?.acta || null
1940
+ if (!actual) {
1941
+ audit('rejected', { what: 'renounce', reason: 'sin-acta' })
1942
+ return reply(from, { type: MSG.ERROR, error: 'renounce: this vault has no record to apply it to' })
1943
+ }
1944
+ const tiene = new Set(Acta.effectiveCaps(actual, record.member) || [])
1945
+ if (!(record.caps || []).some((c) => tiene.has(c))) {
1946
+ audit('rejected', { what: 'renounce', reason: 'nada-que-quitar' })
1947
+ return reply(from, { type: MSG.ERROR, error: 'renounce: that member no longer holds any of those capabilities' })
1948
+ }
1903
1949
  try {
1904
1950
  const r = await identity.absorbRenounce(record)
1905
1951
  const device = await deviceIdOf(record.member).catch(() => null)