@agentvault/claude-bridge 0.7.5 → 0.7.6

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/bridge.d.ts CHANGED
@@ -34,6 +34,9 @@ export interface ArmingSnapshot {
34
34
  * its ABSENCE is how we identify a true owner↔agent 1:1 DM. */
35
35
  export interface MessageMeta {
36
36
  roomId?: string;
37
+ /** #621 — set by SecureChannel when the message decrypted under the owner's
38
+ * 1:1 material. Gates the full-access DM lane; see the `message` handler. */
39
+ ownerDm1to1?: boolean;
37
40
  }
38
41
  /** #392 room hush (advisory) — emitted by SecureChannel from the backend's
39
42
  * `room_hushed` event. ``hushedUntil`` is an ISO string, or null when cleared. */
package/dist/index.js CHANGED
@@ -452,12 +452,13 @@ import { join as join11 } from "node:path";
452
452
 
453
453
  // ../plugin/dist/index.js
454
454
  import * as nc from "node:crypto";
455
- import { mkdir, readFile, writeFile, rm } from "node:fs/promises";
455
+ import { chmod, mkdir, writeFile } from "node:fs/promises";
456
+ import { readFile, rm } from "node:fs/promises";
456
457
  import { join } from "node:path";
457
- import { mkdir as mkdir2, readFile as readFile2, rename, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
458
+ import { readFile as readFile2, rename, rm as rm2 } from "node:fs/promises";
458
459
  import { join as join2 } from "node:path";
459
460
  import { Readable } from "node:stream";
460
- import { readdir, readFile as readFile3, writeFile as writeFile3, rename as rename2, stat, unlink, mkdir as mkdir3 } from "node:fs/promises";
461
+ import { readdir, readFile as readFile3, writeFile as writeFile2, rename as rename2, stat, unlink, mkdir as mkdir2 } from "node:fs/promises";
461
462
  import { join as join3 } from "node:path";
462
463
  import { randomUUID } from "node:crypto";
463
464
  import { EventEmitter } from "node:events";
@@ -465,7 +466,7 @@ import { createServer } from "node:http";
465
466
  import { randomUUID as randomUUID2, createHash } from "node:crypto";
466
467
  import { readFileSync } from "node:fs";
467
468
  import { homedir as osHomedir } from "node:os";
468
- import { writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
469
+ import { writeFile as writeFile3, mkdir as mkdir3 } from "node:fs/promises";
469
470
  import { join as join4 } from "node:path";
470
471
  import { readFile as readFile4 } from "node:fs/promises";
471
472
  import WebSocket2 from "ws";
@@ -63664,16 +63665,39 @@ var init_dist = __esm2({
63664
63665
  init_mls_delivery_order();
63665
63666
  }
63666
63667
  });
63668
+ async function ensureSecureDir(dir) {
63669
+ await mkdir(dir, { recursive: true, mode: DIR_MODE });
63670
+ try {
63671
+ await chmod(dir, DIR_MODE);
63672
+ } catch {
63673
+ }
63674
+ }
63675
+ async function writeSecureFile(filePath, data, flag) {
63676
+ await writeFile(filePath, data, { encoding: "utf-8", mode: FILE_MODE, ...flag ? { flag } : {} });
63677
+ try {
63678
+ await chmod(filePath, FILE_MODE);
63679
+ } catch {
63680
+ }
63681
+ }
63682
+ var DIR_MODE;
63683
+ var FILE_MODE;
63684
+ var init_secure_file = __esm2({
63685
+ "src/secure-file.ts"() {
63686
+ "use strict";
63687
+ DIR_MODE = 448;
63688
+ FILE_MODE = 384;
63689
+ }
63690
+ });
63667
63691
  function syncLockPath(dataDir, channelId) {
63668
63692
  const safe = channelId.replace(/[^a-zA-Z0-9_-]/g, "_");
63669
63693
  return join(dataDir, `mls-a2a-sync-${safe}.lock`);
63670
63694
  }
63671
63695
  async function acquireA2ASyncLock(dataDir, channelId) {
63672
- await mkdir(dataDir, { recursive: true, mode: DIR_MODE });
63696
+ await ensureSecureDir(dataDir);
63673
63697
  const lockFile = syncLockPath(dataDir, channelId);
63674
63698
  const content = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
63675
63699
  try {
63676
- await writeFile(lockFile, content, { encoding: "utf-8", mode: FILE_MODE, flag: "wx" });
63700
+ await writeSecureFile(lockFile, content, "wx");
63677
63701
  return true;
63678
63702
  } catch (err) {
63679
63703
  if (err.code !== "EEXIST") throw err;
@@ -63684,7 +63708,7 @@ async function acquireA2ASyncLock(dataDir, channelId) {
63684
63708
  await rm(lockFile).catch(() => {
63685
63709
  });
63686
63710
  try {
63687
- await writeFile(lockFile, content, { encoding: "utf-8", mode: FILE_MODE, flag: "wx" });
63711
+ await writeSecureFile(lockFile, content, "wx");
63688
63712
  return true;
63689
63713
  } catch {
63690
63714
  return false;
@@ -63714,7 +63738,7 @@ async function hasPendingWelcome(dataDir, groupId) {
63714
63738
  }
63715
63739
  }
63716
63740
  async function savePendingKpBundle(dataDir, groupId, kp, serializePublicFn) {
63717
- await mkdir(dataDir, { recursive: true, mode: DIR_MODE });
63741
+ await ensureSecureDir(dataDir);
63718
63742
  const serialized = {
63719
63743
  publicPackageBytes: Buffer.from(serializePublicFn(kp.publicPackage)).toString("base64"),
63720
63744
  privatePackage: {
@@ -63723,7 +63747,7 @@ async function savePendingKpBundle(dataDir, groupId, kp, serializePublicFn) {
63723
63747
  signaturePrivateKey: Buffer.from(kp.privatePackage.signaturePrivateKey).toString("base64")
63724
63748
  }
63725
63749
  };
63726
- await writeFile(pendingKpPath(dataDir, groupId), JSON.stringify(serialized), { encoding: "utf-8", mode: FILE_MODE });
63750
+ await writeSecureFile(pendingKpPath(dataDir, groupId), JSON.stringify(serialized));
63727
63751
  }
63728
63752
  async function loadPendingKpBundle(dataDir, groupId) {
63729
63753
  try {
@@ -63748,9 +63772,9 @@ async function clearPendingWelcome(dataDir, groupId) {
63748
63772
  }
63749
63773
  }
63750
63774
  async function saveMlsState(dataDir, groupId, state) {
63751
- await mkdir(dataDir, { recursive: true, mode: DIR_MODE });
63775
+ await ensureSecureDir(dataDir);
63752
63776
  const filePath = join(dataDir, mlsFileName(groupId));
63753
- await writeFile(filePath, state, { encoding: "utf-8", mode: FILE_MODE });
63777
+ await writeSecureFile(filePath, state);
63754
63778
  }
63755
63779
  async function loadMlsState(dataDir, groupId) {
63756
63780
  try {
@@ -63767,15 +63791,12 @@ async function deleteMlsState(dataDir, groupId) {
63767
63791
  } catch {
63768
63792
  }
63769
63793
  }
63770
- var FILE_MODE;
63771
- var DIR_MODE;
63772
63794
  var SYNC_LOCK_STALE_MS;
63773
63795
  var pendingKpPath;
63774
63796
  var init_mls_state = __esm2({
63775
63797
  "src/mls-state.ts"() {
63776
63798
  "use strict";
63777
- FILE_MODE = 384;
63778
- DIR_MODE = 448;
63799
+ init_secure_file();
63779
63800
  SYNC_LOCK_STALE_MS = 5 * 60 * 1e3;
63780
63801
  pendingKpPath = (dataDir, groupId) => join(dataDir, `mls-kp-pending-${groupId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
63781
63802
  }
@@ -63926,9 +63947,9 @@ var init_crypto_helpers = __esm2({
63926
63947
  }
63927
63948
  });
63928
63949
  async function saveState(dataDir, state) {
63929
- await mkdir2(dataDir, { recursive: true, mode: DIR_MODE2 });
63950
+ await ensureSecureDir(dataDir);
63930
63951
  const filePath = join2(dataDir, STATE_FILE);
63931
- await writeFile2(filePath, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: FILE_MODE2 });
63952
+ await writeSecureFile(filePath, JSON.stringify(state, null, 2));
63932
63953
  try {
63933
63954
  await rm2(join2(dataDir, LEGACY_STATE_FILE));
63934
63955
  } catch {
@@ -63945,8 +63966,8 @@ async function loadState(dataDir) {
63945
63966
  try {
63946
63967
  const raw = await readFile2(legacyPath, "utf-8");
63947
63968
  const parsed = JSON.parse(raw);
63948
- await mkdir2(dataDir, { recursive: true, mode: DIR_MODE2 });
63949
- await writeFile2(filePath, JSON.stringify(parsed, null, 2), { encoding: "utf-8", mode: FILE_MODE2 });
63969
+ await ensureSecureDir(dataDir);
63970
+ await writeSecureFile(filePath, JSON.stringify(parsed, null, 2));
63950
63971
  await rm2(legacyPath);
63951
63972
  return parsed;
63952
63973
  } catch {
@@ -63969,7 +63990,7 @@ async function backupState(dataDir) {
63969
63990
  const tmp = join2(dataDir, `${STATE_FILE}.bak.tmp`);
63970
63991
  try {
63971
63992
  const data = await readFile2(src, "utf-8");
63972
- await writeFile2(tmp, data, { encoding: "utf-8", mode: FILE_MODE2 });
63993
+ await writeSecureFile(tmp, data);
63973
63994
  await rename(tmp, bak);
63974
63995
  } catch {
63975
63996
  }
@@ -63984,8 +64005,8 @@ async function restoreState(dataDir) {
63984
64005
  if (!parsed || !parsed.deviceId || !parsed.deviceJwt || !parsed.sessions || Object.keys(parsed.sessions).length === 0) {
63985
64006
  return false;
63986
64007
  }
63987
- await mkdir2(dataDir, { recursive: true, mode: DIR_MODE2 });
63988
- await writeFile2(join2(dataDir, STATE_FILE), data, { encoding: "utf-8", mode: FILE_MODE2 });
64008
+ await ensureSecureDir(dataDir);
64009
+ await writeSecureFile(join2(dataDir, STATE_FILE), data);
63989
64010
  return true;
63990
64011
  } catch {
63991
64012
  return false;
@@ -64048,16 +64069,13 @@ async function clearState(dataDir) {
64048
64069
  }
64049
64070
  var STATE_FILE;
64050
64071
  var LEGACY_STATE_FILE;
64051
- var DIR_MODE2;
64052
- var FILE_MODE2;
64053
64072
  var init_state = __esm2({
64054
64073
  async "src/state.ts"() {
64055
64074
  "use strict";
64056
64075
  await init_dist();
64076
+ init_secure_file();
64057
64077
  STATE_FILE = "agentvault.json";
64058
64078
  LEGACY_STATE_FILE = "secure-channel.json";
64059
- DIR_MODE2 = 448;
64060
- FILE_MODE2 = 384;
64061
64079
  }
64062
64080
  });
64063
64081
  async function enrollDevice(apiUrl, inviteToken, identityPkHex, ephemeralPkHex, proofHex, platform) {
@@ -64486,11 +64504,11 @@ async function handleWorkspaceUpload(data, workspaceDir) {
64486
64504
  if (!verified) {
64487
64505
  return { status: "error", error: "Invalid signature \u2014 file may have been tampered with" };
64488
64506
  }
64489
- await mkdir3(workspaceDir, { recursive: true });
64507
+ await mkdir2(workspaceDir, { recursive: true });
64490
64508
  const targetPath = join3(workspaceDir, data.filename);
64491
64509
  const tempPath = join3(workspaceDir, `.tmp_${randomUUID()}_${data.filename}`);
64492
64510
  try {
64493
- await writeFile3(tempPath, data.content, "utf-8");
64511
+ await writeFile2(tempPath, data.content, "utf-8");
64494
64512
  await rename2(tempPath, targetPath);
64495
64513
  } catch (err) {
64496
64514
  try {
@@ -65669,7 +65687,7 @@ var init_channel = __esm2({
65669
65687
  */
65670
65688
  sendActivitySpan(spanData) {
65671
65689
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65672
- const pluginVersion = true ? "0.23.17" : "0.0.0-dev";
65690
+ const pluginVersion = true ? "0.23.18" : "0.0.0-dev";
65673
65691
  const agentName = this.config.agentName ?? "Agent";
65674
65692
  const resource = {
65675
65693
  "service.name": "agentvault-agent",
@@ -67534,7 +67552,7 @@ var init_channel = __esm2({
67534
67552
  agentVersion: this.config.agentVersion ?? "0.0.0",
67535
67553
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67536
67554
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67537
- pluginVersion: true ? "0.23.17" : "0.0.0-dev"
67555
+ pluginVersion: true ? "0.23.18" : "0.0.0-dev"
67538
67556
  });
67539
67557
  this._telemetryReporter.startAutoFlush(3e4);
67540
67558
  }
@@ -67857,7 +67875,7 @@ var init_channel = __esm2({
67857
67875
  agentVersion: this.config.agentVersion ?? "0.0.0",
67858
67876
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67859
67877
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67860
- pluginVersion: true ? "0.23.17" : "0.0.0-dev"
67878
+ pluginVersion: true ? "0.23.18" : "0.0.0-dev"
67861
67879
  });
67862
67880
  this._telemetryReporter.startAutoFlush(3e4);
67863
67881
  }
@@ -68341,6 +68359,10 @@ var init_channel = __esm2({
68341
68359
  conversationId: convId ?? this._primaryConversationId,
68342
68360
  timestamp: data.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
68343
68361
  topicId,
68362
+ // #621: positive attestation that this decrypted under the OWNER'S 1:1
68363
+ // material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
68364
+ // that a roomId was absent. The bridge's full-access DM lane requires it.
68365
+ ownerDm1to1: true,
68344
68366
  messageType
68345
68367
  };
68346
68368
  this.emit("message", text, metadata);
@@ -68583,7 +68605,7 @@ ${messageText}`;
68583
68605
  */
68584
68606
  async _downloadAndDecryptAttachment(info) {
68585
68607
  const attachDir = join4(this.config.dataDir, "attachments");
68586
- await mkdir4(attachDir, { recursive: true });
68608
+ await mkdir3(attachDir, { recursive: true });
68587
68609
  const url22 = `${this.config.apiUrl}${info.blobUrl}`;
68588
68610
  const res = await fetch(url22, {
68589
68611
  headers: { Authorization: `Bearer ${this._deviceJwt}` }
@@ -68601,7 +68623,7 @@ ${messageText}`;
68601
68623
  const fileNonce = base64ToBytes(info.fileNonce);
68602
68624
  const decrypted = decryptFile(encryptedData, fileKey, fileNonce);
68603
68625
  const filePath = join4(attachDir, info.filename);
68604
- await writeFile4(filePath, decrypted);
68626
+ await writeFile3(filePath, decrypted);
68605
68627
  console.log(`[SecureChannel] Attachment saved: ${filePath} (${decrypted.length} bytes)`);
68606
68628
  return { filePath, decrypted };
68607
68629
  }
@@ -69621,7 +69643,7 @@ ${messageText}`;
69621
69643
  console.warn("[SecureChannel] KeyPackage pool replenish after join failed:", err);
69622
69644
  });
69623
69645
  console.log(`[SecureChannel] Welcome joined: group=${groupId?.slice(0, 8)} kpSource=${kpSource} welcomeLen=${welcomeBytes.length} candidates=${candidateCount} poolRemaining=${this._pendingKpBundles.length}`);
69624
- if (conversationGroupId) {
69646
+ if (conversationGroupId && !data.room_id) {
69625
69647
  const key = `1to1-group:${conversationGroupId}`;
69626
69648
  this._mlsGroups.set(key, mgr);
69627
69649
  await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mgr.exportState()));
@@ -70442,7 +70464,11 @@ ${messageText}`;
70442
70464
  messageId: msg.message_id,
70443
70465
  conversationId: msg.conversation_id,
70444
70466
  timestamp: msg.created_at,
70445
- topicId
70467
+ topicId,
70468
+ // #621: positive attestation that this decrypted under the OWNER'S 1:1
70469
+ // material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
70470
+ // that a roomId was absent. The bridge's full-access DM lane requires it.
70471
+ ownerDm1to1: true
70446
70472
  };
70447
70473
  this.emit("message", messageText, metadata);
70448
70474
  Promise.resolve(this.config.onMessage?.(messageText, metadata)).catch((err) => {
@@ -70774,7 +70800,11 @@ ${messageText}`;
70774
70800
  messageId: msg.id,
70775
70801
  conversationId: msg.conversation_id,
70776
70802
  timestamp: msg.created_at,
70777
- topicId
70803
+ topicId,
70804
+ // #621: positive attestation that this decrypted under the OWNER'S 1:1
70805
+ // material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
70806
+ // that a roomId was absent. The bridge's full-access DM lane requires it.
70807
+ ownerDm1to1: true
70778
70808
  };
70779
70809
  this.emit("message", messageText, metadata);
70780
70810
  Promise.resolve(this.config.onMessage?.(messageText, metadata)).catch((err) => {
@@ -71055,7 +71085,11 @@ ${messageText}`;
71055
71085
  messageId: msg.id,
71056
71086
  conversationId: msg.conversation_id,
71057
71087
  timestamp: msg.created_at,
71058
- topicId
71088
+ topicId,
71089
+ // #621: positive attestation that this decrypted under the OWNER'S 1:1
71090
+ // material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
71091
+ // that a roomId was absent. The bridge's full-access DM lane requires it.
71092
+ ownerDm1to1: true
71059
71093
  };
71060
71094
  this.emit("message", messageText, metadata);
71061
71095
  Promise.resolve(this.config.onMessage?.(messageText, metadata)).catch((err) => {
@@ -71658,6 +71692,11 @@ var init_fetch_interceptor = __esm2({
71658
71692
  traceStore = new AsyncLocalStorage();
71659
71693
  }
71660
71694
  });
71695
+ var init_tool_audit = __esm2({
71696
+ "src/tool-audit.ts"() {
71697
+ "use strict";
71698
+ }
71699
+ });
71661
71700
  var isUsingManagedRoutes;
71662
71701
  var init_openclaw_entry = __esm2({
71663
71702
  "src/openclaw-entry.ts"() {
@@ -71667,6 +71706,7 @@ var init_openclaw_entry = __esm2({
71667
71706
  init_http_handlers();
71668
71707
  init_openclaw_compat();
71669
71708
  init_lifecycle();
71709
+ init_tool_audit();
71670
71710
  init_types();
71671
71711
  isUsingManagedRoutes = false;
71672
71712
  }
@@ -97718,7 +97758,7 @@ var init_index = __esm2({
97718
97758
  init_skill_invoker();
97719
97759
  await init_skill_telemetry();
97720
97760
  await init_policy_enforcer();
97721
- VERSION = true ? "0.23.17" : "0.0.0-dev";
97761
+ VERSION = true ? "0.23.18" : "0.0.0-dev";
97722
97762
  }
97723
97763
  });
97724
97764
  await init_index();
@@ -119029,8 +119069,8 @@ import { mkdirSync as mkdirSync3, writeFileSync, rmSync as rmSync2, readdirSync
119029
119069
  import { join as join6 } from "node:path";
119030
119070
  import { homedir, hostname as hostname3 } from "node:os";
119031
119071
  var TRUST_SUBDIR = "host-trust";
119032
- var DIR_MODE3 = 448;
119033
- var FILE_MODE3 = 384;
119072
+ var DIR_MODE2 = 448;
119073
+ var FILE_MODE2 = 384;
119034
119074
  var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
119035
119075
  var IDENTITY_FILES = ["agentvault.json", "secure-channel.json", "agentvault.json.bak"];
119036
119076
  var HostTrustError = class extends Error {
@@ -119057,15 +119097,15 @@ function grant(deviceId, root3) {
119057
119097
  const id = sanitize(deviceId);
119058
119098
  const dir = trustDir(root3);
119059
119099
  const p2 = join6(dir, id);
119060
- mkdirSync3(dir, { recursive: true, mode: DIR_MODE3 });
119100
+ mkdirSync3(dir, { recursive: true, mode: DIR_MODE2 });
119061
119101
  writeFileSync(
119062
119102
  p2,
119063
119103
  `granted_at=${(/* @__PURE__ */ new Date()).toISOString()} host=${hostname3()}
119064
119104
  `,
119065
- { mode: FILE_MODE3 }
119105
+ { mode: FILE_MODE2 }
119066
119106
  );
119067
- chmodSync2(dir, DIR_MODE3);
119068
- chmodSync2(p2, FILE_MODE3);
119107
+ chmodSync2(dir, DIR_MODE2);
119108
+ chmodSync2(p2, FILE_MODE2);
119069
119109
  }
119070
119110
  function revoke(deviceId, root3) {
119071
119111
  const id = sanitize(deviceId);
@@ -133839,9 +133879,14 @@ function wireBridge(channel, session, target, opts = {}) {
133839
133879
  });
133840
133880
  channel.on("message", (text, metadata) => {
133841
133881
  if (metadata?.roomId) return;
133842
- log("inbound 1:1 DM from owner");
133882
+ const ownerAttested = metadata?.ownerDm1to1 === true;
133883
+ if (!ownerAttested) {
133884
+ log("inbound 1:1 DM WITHOUT owner attestation \u2014 delivering to the locked listener (no tools)");
133885
+ } else {
133886
+ log("inbound 1:1 DM from owner");
133887
+ }
133843
133888
  target.setDm();
133844
- session.push(text, target.snapshotReply(channel, log), { autoReplyOnText: true });
133889
+ session.push(text, target.snapshotReply(channel, log), { autoReplyOnText: ownerAttested });
133845
133890
  });
133846
133891
  const workerCapable = !!opts.workspaceDir;
133847
133892
  const osIsolated = opts.osIsolated !== void 0 ? opts.osIsolated : process.env.AV_WORKER_OS_ISOLATED === "1" || process.env.AV_WORKER_OS_ISOLATED === "true";
@@ -133982,7 +134027,7 @@ async function main() {
133982
134027
  "[bridge] warning: passing the invite token on the command line is visible to other local users via 'ps'. Prefer: AV_INVITE_TOKEN=\u2026 npx @agentvault/claude-bridge"
133983
134028
  );
133984
134029
  }
133985
- console.error(`[bridge] version: ${true ? "0.7.5" : "dev"}`);
134030
+ console.error(`[bridge] version: ${true ? "0.7.6" : "dev"}`);
133986
134031
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
133987
134032
  console.error(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
133988
134033
  if (cfg.armRoom) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "type": "module",
5
- "description": "AgentVault Claude Bridge \u2014 daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
5
+ "description": "AgentVault Claude Bridge daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {