@agentvault/agentvault 0.13.9 → 0.13.11
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/dist/channel.d.ts +8 -0
- package/dist/channel.d.ts.map +1 -1
- package/dist/cli.js +189 -29
- package/dist/cli.js.map +2 -2
- package/dist/index.js +189 -29
- package/dist/index.js.map +2 -2
- package/package.json +9 -1
package/dist/channel.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare class SecureChannel extends EventEmitter {
|
|
|
35
35
|
private _telemetryReporter;
|
|
36
36
|
/** Topic ID from the most recent inbound message — used as fallback for replies. */
|
|
37
37
|
private _lastIncomingTopicId;
|
|
38
|
+
/** Rate-limit: last resync_request timestamp per conversation (5-min cooldown). */
|
|
39
|
+
private _lastResyncRequest;
|
|
38
40
|
private static readonly PING_INTERVAL_MS;
|
|
39
41
|
private static readonly SILENCE_TIMEOUT_MS;
|
|
40
42
|
private static readonly POLL_FALLBACK_INTERVAL_MS;
|
|
@@ -227,6 +229,12 @@ export declare class SecureChannel extends EventEmitter {
|
|
|
227
229
|
* a new ratchet session.
|
|
228
230
|
*/
|
|
229
231
|
private _handleDeviceLinked;
|
|
232
|
+
/**
|
|
233
|
+
* Handle a resync_request from the owner (owner-initiated ratchet re-establishment).
|
|
234
|
+
* Re-derives shared secret via X3DH as responder, initializes fresh receiver ratchet,
|
|
235
|
+
* and sends resync_ack back with agent's public keys.
|
|
236
|
+
*/
|
|
237
|
+
private _handleResyncRequest;
|
|
230
238
|
/**
|
|
231
239
|
* Handle an incoming room message. Finds the pairwise conversation
|
|
232
240
|
* for the sender, decrypts, and emits a room_message event.
|
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQ3C,OAAO,EAWL,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EAMZ,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,QAAQ,EAER,UAAU,EAEX,MAAM,YAAY,CAAC;AAoDpB,qBAAa,aAAc,SAAQ,YAAY;
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -44897,6 +44897,7 @@ var init_ratchet = __esm({
|
|
|
44897
44897
|
serialize() {
|
|
44898
44898
|
const s2 = this.state;
|
|
44899
44899
|
return JSON.stringify({
|
|
44900
|
+
version: 1,
|
|
44900
44901
|
rootKey: libsodium_wrappers_default.to_hex(s2.rootKey),
|
|
44901
44902
|
sendingChain: s2.sendingChain ? {
|
|
44902
44903
|
chainKey: libsodium_wrappers_default.to_hex(s2.sendingChain.chainKey),
|
|
@@ -44924,33 +44925,59 @@ var init_ratchet = __esm({
|
|
|
44924
44925
|
});
|
|
44925
44926
|
}
|
|
44926
44927
|
static deserialize(json) {
|
|
44927
|
-
|
|
44928
|
-
|
|
44929
|
-
|
|
44930
|
-
|
|
44931
|
-
|
|
44932
|
-
|
|
44933
|
-
|
|
44934
|
-
|
|
44935
|
-
|
|
44936
|
-
|
|
44937
|
-
|
|
44938
|
-
|
|
44939
|
-
|
|
44940
|
-
|
|
44941
|
-
|
|
44942
|
-
|
|
44943
|
-
|
|
44944
|
-
|
|
44945
|
-
|
|
44946
|
-
|
|
44947
|
-
|
|
44948
|
-
|
|
44949
|
-
|
|
44950
|
-
|
|
44951
|
-
|
|
44952
|
-
|
|
44953
|
-
|
|
44928
|
+
let d2;
|
|
44929
|
+
try {
|
|
44930
|
+
d2 = JSON.parse(json);
|
|
44931
|
+
} catch {
|
|
44932
|
+
throw new Error("Ratchet state: corrupt JSON");
|
|
44933
|
+
}
|
|
44934
|
+
if (d2.version !== void 0 && d2.version !== 1) {
|
|
44935
|
+
throw new Error(`Ratchet state version ${d2.version} not supported`);
|
|
44936
|
+
}
|
|
44937
|
+
if (typeof d2.rootKey !== "string") {
|
|
44938
|
+
throw new Error("Ratchet state: missing required field rootKey");
|
|
44939
|
+
}
|
|
44940
|
+
const dhSend = d2.dhSendingKeypair;
|
|
44941
|
+
if (!dhSend || typeof dhSend.publicKey !== "string" || typeof dhSend.privateKey !== "string") {
|
|
44942
|
+
throw new Error("Ratchet state: missing required field dhSendingKeypair");
|
|
44943
|
+
}
|
|
44944
|
+
const idKp = d2.identityKeypair;
|
|
44945
|
+
if (!idKp || typeof idKp.publicKey !== "string" || typeof idKp.privateKey !== "string") {
|
|
44946
|
+
throw new Error("Ratchet state: missing required field identityKeypair");
|
|
44947
|
+
}
|
|
44948
|
+
try {
|
|
44949
|
+
return new _DoubleRatchet({
|
|
44950
|
+
rootKey: libsodium_wrappers_default.from_hex(d2.rootKey),
|
|
44951
|
+
sendingChain: d2.sendingChain ? {
|
|
44952
|
+
chainKey: libsodium_wrappers_default.from_hex(d2.sendingChain.chainKey),
|
|
44953
|
+
messageNumber: d2.sendingChain.messageNumber
|
|
44954
|
+
} : null,
|
|
44955
|
+
receivingChain: d2.receivingChain ? {
|
|
44956
|
+
chainKey: libsodium_wrappers_default.from_hex(d2.receivingChain.chainKey),
|
|
44957
|
+
messageNumber: d2.receivingChain.messageNumber
|
|
44958
|
+
} : null,
|
|
44959
|
+
dhSendingKeypair: {
|
|
44960
|
+
publicKey: libsodium_wrappers_default.from_hex(dhSend.publicKey),
|
|
44961
|
+
privateKey: libsodium_wrappers_default.from_hex(dhSend.privateKey)
|
|
44962
|
+
},
|
|
44963
|
+
dhReceivingPublicKey: d2.dhReceivingPublicKey ? libsodium_wrappers_default.from_hex(d2.dhReceivingPublicKey) : null,
|
|
44964
|
+
identityKeypair: {
|
|
44965
|
+
publicKey: libsodium_wrappers_default.from_hex(idKp.publicKey),
|
|
44966
|
+
privateKey: libsodium_wrappers_default.from_hex(idKp.privateKey)
|
|
44967
|
+
},
|
|
44968
|
+
previousSendingChainLength: d2.previousSendingChainLength,
|
|
44969
|
+
skippedKeys: d2.skippedKeys.map((sk) => ({
|
|
44970
|
+
dhPub: sk.dhPub,
|
|
44971
|
+
n: sk.n,
|
|
44972
|
+
messageKey: libsodium_wrappers_default.from_hex(sk.messageKey)
|
|
44973
|
+
}))
|
|
44974
|
+
});
|
|
44975
|
+
} catch (err) {
|
|
44976
|
+
if (err instanceof Error && err.message.startsWith("Ratchet state")) {
|
|
44977
|
+
throw err;
|
|
44978
|
+
}
|
|
44979
|
+
throw new Error(`Ratchet state: corrupt hex data \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
44980
|
+
}
|
|
44954
44981
|
}
|
|
44955
44982
|
};
|
|
44956
44983
|
}
|
|
@@ -45756,6 +45783,8 @@ var init_channel = __esm({
|
|
|
45756
45783
|
_telemetryReporter = null;
|
|
45757
45784
|
/** Topic ID from the most recent inbound message — used as fallback for replies. */
|
|
45758
45785
|
_lastIncomingTopicId;
|
|
45786
|
+
/** Rate-limit: last resync_request timestamp per conversation (5-min cooldown). */
|
|
45787
|
+
_lastResyncRequest = /* @__PURE__ */ new Map();
|
|
45759
45788
|
// Liveness detection: server sends app-level {"event":"ping"} every 30s.
|
|
45760
45789
|
// We check every 30s; if no data received in 90s (3 missed pings), connection is dead.
|
|
45761
45790
|
static PING_INTERVAL_MS = 3e4;
|
|
@@ -47014,6 +47043,13 @@ var init_channel = __esm({
|
|
|
47014
47043
|
await this._handleDeviceLinked(data.data);
|
|
47015
47044
|
return;
|
|
47016
47045
|
}
|
|
47046
|
+
if (data.event === "resync_request") {
|
|
47047
|
+
await this._handleResyncRequest(data.data);
|
|
47048
|
+
return;
|
|
47049
|
+
}
|
|
47050
|
+
if (data.event === "resync_ack") {
|
|
47051
|
+
return;
|
|
47052
|
+
}
|
|
47017
47053
|
if (data.event === "message") {
|
|
47018
47054
|
try {
|
|
47019
47055
|
await this._handleIncomingMessage(data.data);
|
|
@@ -47400,8 +47436,28 @@ var init_channel = __esm({
|
|
|
47400
47436
|
const session = this._sessions.get(convId);
|
|
47401
47437
|
if (!session) {
|
|
47402
47438
|
console.warn(
|
|
47403
|
-
`[SecureChannel] No session for conversation ${convId},
|
|
47439
|
+
`[SecureChannel] No session for conversation ${convId}, requesting resync`
|
|
47404
47440
|
);
|
|
47441
|
+
const RESYNC_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
47442
|
+
const lastResync = this._lastResyncRequest.get(convId) ?? 0;
|
|
47443
|
+
if (Date.now() - lastResync > RESYNC_COOLDOWN_MS && this._ws && this._persisted) {
|
|
47444
|
+
this._lastResyncRequest.set(convId, Date.now());
|
|
47445
|
+
this._ws.send(
|
|
47446
|
+
JSON.stringify({
|
|
47447
|
+
event: "resync_request",
|
|
47448
|
+
data: {
|
|
47449
|
+
conversation_id: convId,
|
|
47450
|
+
reason: "no_session",
|
|
47451
|
+
identity_public_key: this._persisted.identityKeypair.publicKey,
|
|
47452
|
+
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey
|
|
47453
|
+
}
|
|
47454
|
+
})
|
|
47455
|
+
);
|
|
47456
|
+
this.emit("resync_requested", {
|
|
47457
|
+
conversationId: convId,
|
|
47458
|
+
reason: "no_session"
|
|
47459
|
+
});
|
|
47460
|
+
}
|
|
47405
47461
|
return;
|
|
47406
47462
|
}
|
|
47407
47463
|
const encrypted = transportToEncryptedMessage({
|
|
@@ -47419,6 +47475,26 @@ var init_channel = __esm({
|
|
|
47419
47475
|
} catch (restoreErr) {
|
|
47420
47476
|
console.error("[SecureChannel] Ratchet restore failed:", restoreErr);
|
|
47421
47477
|
}
|
|
47478
|
+
const RESYNC_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
47479
|
+
const lastResync = this._lastResyncRequest.get(convId) ?? 0;
|
|
47480
|
+
if (Date.now() - lastResync > RESYNC_COOLDOWN_MS && this._ws && this._persisted) {
|
|
47481
|
+
this._lastResyncRequest.set(convId, Date.now());
|
|
47482
|
+
const idPubHex = this._persisted.identityKeypair.publicKey;
|
|
47483
|
+
const ephPubHex = this._persisted.ephemeralKeypair.publicKey;
|
|
47484
|
+
this._ws.send(
|
|
47485
|
+
JSON.stringify({
|
|
47486
|
+
event: "resync_request",
|
|
47487
|
+
data: {
|
|
47488
|
+
conversation_id: convId,
|
|
47489
|
+
reason: "decrypt_failure",
|
|
47490
|
+
identity_public_key: idPubHex,
|
|
47491
|
+
ephemeral_public_key: ephPubHex
|
|
47492
|
+
}
|
|
47493
|
+
})
|
|
47494
|
+
);
|
|
47495
|
+
console.log(`[SecureChannel] Sent resync_request for conv ${convId.slice(0, 8)}...`);
|
|
47496
|
+
this.emit("resync_requested", { conversationId: convId, reason: "decrypt_failure" });
|
|
47497
|
+
}
|
|
47422
47498
|
return;
|
|
47423
47499
|
}
|
|
47424
47500
|
this._sendAck(msgData.message_id);
|
|
@@ -47743,6 +47819,70 @@ ${messageText}`;
|
|
|
47743
47819
|
this.emit("error", err);
|
|
47744
47820
|
}
|
|
47745
47821
|
}
|
|
47822
|
+
/**
|
|
47823
|
+
* Handle a resync_request from the owner (owner-initiated ratchet re-establishment).
|
|
47824
|
+
* Re-derives shared secret via X3DH as responder, initializes fresh receiver ratchet,
|
|
47825
|
+
* and sends resync_ack back with agent's public keys.
|
|
47826
|
+
*/
|
|
47827
|
+
async _handleResyncRequest(data) {
|
|
47828
|
+
const convId = data.conversation_id;
|
|
47829
|
+
console.log(
|
|
47830
|
+
`[SecureChannel] Received resync_request for conv ${convId.slice(0, 8)}... (reason: ${data.reason ?? "unknown"})`
|
|
47831
|
+
);
|
|
47832
|
+
try {
|
|
47833
|
+
if (!this._persisted) {
|
|
47834
|
+
console.error("[SecureChannel] Cannot handle resync \u2014 no persisted state");
|
|
47835
|
+
return;
|
|
47836
|
+
}
|
|
47837
|
+
const identity = this._persisted.identityKeypair;
|
|
47838
|
+
const ephemeral = this._persisted.ephemeralKeypair;
|
|
47839
|
+
const sharedSecret = performX3DH({
|
|
47840
|
+
myIdentityPrivate: hexToBytes(identity.privateKey),
|
|
47841
|
+
myEphemeralPrivate: hexToBytes(ephemeral.privateKey),
|
|
47842
|
+
theirIdentityPublic: hexToBytes(data.identity_public_key),
|
|
47843
|
+
theirEphemeralPublic: hexToBytes(data.ephemeral_public_key),
|
|
47844
|
+
isInitiator: false
|
|
47845
|
+
});
|
|
47846
|
+
const ratchet = DoubleRatchet.initReceiver(sharedSecret, {
|
|
47847
|
+
publicKey: hexToBytes(identity.publicKey),
|
|
47848
|
+
privateKey: hexToBytes(identity.privateKey),
|
|
47849
|
+
keyType: "ed25519"
|
|
47850
|
+
});
|
|
47851
|
+
const existingSession = this._sessions.get(convId);
|
|
47852
|
+
const ownerDeviceId = data.sender_device_id ?? existingSession?.ownerDeviceId ?? "";
|
|
47853
|
+
this._sessions.set(convId, {
|
|
47854
|
+
ownerDeviceId,
|
|
47855
|
+
ratchet,
|
|
47856
|
+
activated: false
|
|
47857
|
+
// Wait for owner's encrypted session_init
|
|
47858
|
+
});
|
|
47859
|
+
this._persisted.sessions[convId] = {
|
|
47860
|
+
ownerDeviceId,
|
|
47861
|
+
ratchetState: ratchet.serialize(),
|
|
47862
|
+
activated: false
|
|
47863
|
+
};
|
|
47864
|
+
await this._persistState();
|
|
47865
|
+
if (this._ws) {
|
|
47866
|
+
this._ws.send(
|
|
47867
|
+
JSON.stringify({
|
|
47868
|
+
event: "resync_ack",
|
|
47869
|
+
data: {
|
|
47870
|
+
conversation_id: convId,
|
|
47871
|
+
identity_public_key: identity.publicKey,
|
|
47872
|
+
ephemeral_public_key: ephemeral.publicKey
|
|
47873
|
+
}
|
|
47874
|
+
})
|
|
47875
|
+
);
|
|
47876
|
+
}
|
|
47877
|
+
console.log(
|
|
47878
|
+
`[SecureChannel] Resync complete for conv ${convId.slice(0, 8)}... \u2014 waiting for owner session_init`
|
|
47879
|
+
);
|
|
47880
|
+
this.emit("resync_completed", { conversationId: convId });
|
|
47881
|
+
} catch (err) {
|
|
47882
|
+
console.error(`[SecureChannel] Resync failed for conv ${convId.slice(0, 8)}...:`, err);
|
|
47883
|
+
this.emit("error", err);
|
|
47884
|
+
}
|
|
47885
|
+
}
|
|
47746
47886
|
/**
|
|
47747
47887
|
* Handle an incoming room message. Finds the pairwise conversation
|
|
47748
47888
|
* for the sender, decrypts, and emits a room_message event.
|
|
@@ -47952,8 +48092,28 @@ ${messageText}`;
|
|
|
47952
48092
|
const session = this._sessions.get(msg.conversation_id);
|
|
47953
48093
|
if (!session) {
|
|
47954
48094
|
console.warn(
|
|
47955
|
-
`[SecureChannel] No session for conversation ${msg.conversation_id} during sync,
|
|
48095
|
+
`[SecureChannel] No session for conversation ${msg.conversation_id} during sync, requesting resync`
|
|
47956
48096
|
);
|
|
48097
|
+
const RESYNC_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
48098
|
+
const lastResync = this._lastResyncRequest.get(msg.conversation_id) ?? 0;
|
|
48099
|
+
if (Date.now() - lastResync > RESYNC_COOLDOWN_MS && this._ws && this._persisted) {
|
|
48100
|
+
this._lastResyncRequest.set(msg.conversation_id, Date.now());
|
|
48101
|
+
this._ws.send(
|
|
48102
|
+
JSON.stringify({
|
|
48103
|
+
event: "resync_request",
|
|
48104
|
+
data: {
|
|
48105
|
+
conversation_id: msg.conversation_id,
|
|
48106
|
+
reason: "no_session",
|
|
48107
|
+
identity_public_key: this._persisted.identityKeypair.publicKey,
|
|
48108
|
+
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey
|
|
48109
|
+
}
|
|
48110
|
+
})
|
|
48111
|
+
);
|
|
48112
|
+
this.emit("resync_requested", {
|
|
48113
|
+
conversationId: msg.conversation_id,
|
|
48114
|
+
reason: "no_session"
|
|
48115
|
+
});
|
|
48116
|
+
}
|
|
47957
48117
|
continue;
|
|
47958
48118
|
}
|
|
47959
48119
|
try {
|