@dotrino/remote-agent 0.6.0 → 0.6.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 +2 -2
  2. package/src/agent.js +18 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/remote-agent",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Middleware compartido del ecosistema Dotrino para apps de agente remoto cifrado punto a punto (handshake E2E, emparejamiento SAS con vault, revocación, descubrimiento). Lo consumen dotrino-terminal (PTY) y dotrino-ia (chat con IA).",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -45,7 +45,7 @@
45
45
  "p2p"
46
46
  ],
47
47
  "peerDependencies": {
48
- "@dotrino/identity": ">=0.73.0",
48
+ "@dotrino/identity": ">=0.73.1",
49
49
  "@dotrino/proxy-client": ">=0.10.1",
50
50
  "@dotrino/vault": ">=0.30.0"
51
51
  },
package/src/agent.js CHANGED
@@ -22,7 +22,7 @@
22
22
  import fs from 'node:fs'
23
23
  import path from 'node:path'
24
24
  import { verifyChain, signWithDevice, verifyDeviceSig, verifyDelegation, pubkeyId } from '@dotrino/identity/capabilities'
25
- import { sealersOf } from '@dotrino/identity/acta'
25
+ import { sealersOf, memberCanSign } from '@dotrino/identity/acta'
26
26
  import { installNodeGlobals } from '../node-globals.js'
27
27
  import { makeEphemeral, deriveKey, seal, open } from '../e2e.js'
28
28
  import { HS, ACK, DATA, PING, PONG, ERROR, VMSG, SIGN_SCOPE, SESSION_TTL_MS, REVOKE_REFRESH_MS } from '../protocol.js'
@@ -178,9 +178,16 @@ export async function startRemoteAgent (opts = {}) {
178
178
  * que una segunda bóveda pueda emitir; lo que se fija es el perfil.
179
179
  */
180
180
  async function renewCertIfNeeded () {
181
- // ¿Nos quedamos atrás? Lo sabemos por el acta que la bóveda manda con cada respuesta.
181
+ // DOS MOTIVOS PARA PEDIR PAPEL NUEVO:
182
+ // · el nuestro es del MODELO VIEJO (lleva `exp` y no `seq`). Ese hay que cambiarlo sí
183
+ // o sí: se acepta por el repliegue de migración, pero muere en su fecha y con él la
184
+ // máquina. Sin esta rama la migración no terminaba nunca — el disparador comparaba
185
+ // `seq` y un papel viejo no tiene, así que nunca saltaba.
186
+ // · o el acta que hemos visto va por delante del papel: el dueño cambió permisos.
187
+ const legado = typeof link.cert?.seq !== 'number'
182
188
  const visto = link.actaSeq
183
- if (typeof visto !== 'number' || typeof link.cert?.seq !== 'number' || visto <= link.cert.seq) return
189
+ const atrasado = typeof visto === 'number' && typeof link.cert?.seq === 'number' && visto > link.cert.seq
190
+ if (!legado && !atrasado) return
184
191
  try {
185
192
  const res = await vaultRpc(VMSG.RENEW, VMSG.RENEWED, { op: 'renew', publickey: myPub, ts: Date.now() })
186
193
  const cert = res?.cert
@@ -251,6 +258,14 @@ export async function startRemoteAgent (opts = {}) {
251
258
  // qué decidir, así que no se decide que sí.
252
259
  const chk = await verifyChain({ data, signature, cert, expectedScope: SIGN_SCOPE, ...contextoActa(), revoked: revokedSet })
253
260
  if (!chk.ok) return send(from, { type: ERROR, error: 'no autorizado: ' + chk.reason })
261
+ // CERT ∩ ACTA. `verifyChain` comprueba QUIÉN EMITIÓ el papel; que el APARATO siga siendo
262
+ // miembro y pueda firmar lo dice el acta, y hay que preguntárselo aparte. Sin esto, un
263
+ // papel bien firmado por una selladora abría sesión aunque el acta no nombrara a ese
264
+ // aparato — o ya no lo nombrara. Bajo el modelo viejo daba igual, porque el papel ERA la
265
+ // autoridad y caducaba; con el papel atado al acta, el acta tiene que decidir.
266
+ if (!memberCanSign(link.acta, chk.device)) {
267
+ return send(from, { type: ERROR, error: 'no autorizado: acta — ese aparato no firma por este perfil' })
268
+ }
254
269
  if (data.op !== HS || typeof data.eph !== 'string') return send(from, { type: ERROR, error: 'handshake inválido' })
255
270
 
256
271
  const eph = await makeEphemeral()