@agentvault/claude-bridge 0.5.5 → 0.5.7

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/config.d.ts CHANGED
@@ -20,6 +20,9 @@ export interface BridgeConfig {
20
20
  /** Slice 2: arm the pinned room (roomFilter) for worker tools on room turns.
21
21
  * Off by default. Valid only with worker + roomFilter + workspaceDir. */
22
22
  armRoom: boolean;
23
+ /** Operator attestation that the agent process runs OS-isolated (own user / jail).
24
+ * Only when true is Bash permitted in worker mode (the OS boundary is the fence). */
25
+ osIsolated: boolean;
23
26
  }
24
27
  export declare function loadConfig(env: NodeJS.ProcessEnv, argv?: string[]): BridgeConfig;
25
28
  //# sourceMappingURL=config.d.ts.map
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ function loadConfig(env, argv = []) {
40
40
  const inviteToken = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "") || env.AV_INVITE_TOKEN || "";
41
41
  const worker = env.AV_WORKER === "1" || env.AV_WORKER === "true";
42
42
  const workspaceDir = env.AV_WORKSPACE_DIR || void 0;
43
+ const osIsolated = env.AV_WORKER_OS_ISOLATED === "1" || env.AV_WORKER_OS_ISOLATED === "true";
43
44
  const PERMISSION_MODES = ["auto", "acceptEdits", "bypassPermissions"];
44
45
  const permissionMode = env.AV_PERMISSION_MODE ?? "auto";
45
46
  if (!PERMISSION_MODES.includes(permissionMode)) {
@@ -84,7 +85,8 @@ function loadConfig(env, argv = []) {
84
85
  worker,
85
86
  workspaceDir,
86
87
  permissionMode,
87
- armRoom
88
+ armRoom,
89
+ osIsolated
88
90
  };
89
91
  }
90
92
  var CRED_FILES, BACKUP_FILE;
@@ -65024,145 +65026,140 @@ var init_channel = __esm2({
65024
65026
  }
65025
65027
  const raw = await loadState(this.config.dataDir);
65026
65028
  if (raw) {
65027
- await backupState(this.config.dataDir);
65028
- this._persisted = migratePersistedState(raw);
65029
- if (!this._persisted.messageHistory) {
65030
- this._persisted.messageHistory = [];
65031
- }
65032
- this._deviceId = this._persisted.deviceId;
65033
- this._deviceJwt = this._persisted.deviceJwt;
65034
- this._primaryConversationId = this._persisted.primaryConversationId;
65035
- this._fingerprint = this._persisted.fingerprint;
65036
- this._lastInboundRoomId = this._persisted.lastInboundRoomId;
65037
- if (this._persisted.seenMessageIds) {
65038
- this._seenMessageIds = new Set(this._persisted.seenMessageIds.slice(-_SecureChannel.SEEN_MSG_MAX));
65039
- }
65040
- if (this._persisted.seenA2AMessageIds) {
65041
- this._a2aSeenMessageIds = new Set(this._persisted.seenA2AMessageIds.slice(-_SecureChannel.A2A_SEEN_MAX));
65042
- }
65043
- if (this._persisted?.rooms) {
65044
- const roomConvIds = /* @__PURE__ */ new Set();
65045
- for (const room of Object.values(this._persisted.rooms)) {
65046
- for (const cid of room.conversationIds || []) {
65047
- roomConvIds.add(cid);
65048
- }
65049
- }
65050
- let cleanedCount = 0;
65051
- for (const cid of roomConvIds) {
65052
- if (this._persisted.sessions[cid]) {
65053
- delete this._persisted.sessions[cid];
65054
- cleanedCount++;
65055
- }
65056
- }
65057
- if (cleanedCount > 0) {
65058
- console.log(`[SecureChannel] Cleaned ${cleanedCount} stale DR room sessions`);
65029
+ await this._hydrateFromPersisted(raw);
65030
+ this._connect();
65031
+ return;
65032
+ }
65033
+ const restored = await restoreState(this.config.dataDir);
65034
+ if (restored) {
65035
+ console.log("[SecureChannel] Restored state from backup");
65036
+ const restoredRaw = await loadState(this.config.dataDir);
65037
+ if (restoredRaw) {
65038
+ await this._hydrateFromPersisted(restoredRaw);
65039
+ this._connect();
65040
+ return;
65041
+ }
65042
+ }
65043
+ await this._enroll();
65044
+ }
65045
+ /**
65046
+ * Hydrate all in-memory state from persisted creds. The single source of
65047
+ * truth for both the primary boot and the .bak-restore boot, so the two can
65048
+ * never drift (the drift is what caused #416: the backup branch used to skip
65049
+ * MLS group loading and dropped owner MLS DMs). Callers invoke _connect()
65050
+ * after this resolves.
65051
+ */
65052
+ async _hydrateFromPersisted(raw) {
65053
+ await backupState(this.config.dataDir);
65054
+ this._persisted = migratePersistedState(raw);
65055
+ if (!this._persisted.messageHistory) {
65056
+ this._persisted.messageHistory = [];
65057
+ }
65058
+ this._deviceId = this._persisted.deviceId;
65059
+ this._deviceJwt = this._persisted.deviceJwt;
65060
+ this._primaryConversationId = this._persisted.primaryConversationId;
65061
+ this._fingerprint = this._persisted.fingerprint;
65062
+ this._lastInboundRoomId = this._persisted.lastInboundRoomId;
65063
+ if (this._persisted.seenMessageIds) {
65064
+ this._seenMessageIds = new Set(this._persisted.seenMessageIds.slice(-_SecureChannel.SEEN_MSG_MAX));
65065
+ }
65066
+ if (this._persisted.seenA2AMessageIds) {
65067
+ this._a2aSeenMessageIds = new Set(this._persisted.seenA2AMessageIds.slice(-_SecureChannel.A2A_SEEN_MAX));
65068
+ }
65069
+ if (this._persisted?.rooms) {
65070
+ const roomConvIds = /* @__PURE__ */ new Set();
65071
+ for (const room of Object.values(this._persisted.rooms)) {
65072
+ for (const cid of room.conversationIds || []) {
65073
+ roomConvIds.add(cid);
65059
65074
  }
65060
65075
  }
65061
- for (const [convId, sessionData] of Object.entries(
65062
- this._persisted.sessions
65063
- )) {
65064
- if (sessionData.ratchetState) {
65065
- const ratchet = DoubleRatchet.deserialize(sessionData.ratchetState);
65066
- this._sessions.set(convId, {
65067
- ownerDeviceId: sessionData.ownerDeviceId,
65068
- ratchet,
65069
- activated: sessionData.activated ?? false,
65070
- epoch: sessionData.epoch
65071
- });
65076
+ let cleanedCount = 0;
65077
+ for (const cid of roomConvIds) {
65078
+ if (this._persisted.sessions[cid]) {
65079
+ delete this._persisted.sessions[cid];
65080
+ cleanedCount++;
65072
65081
  }
65073
65082
  }
65074
- if (this._persisted.conversationGroupIds) {
65075
- this._sessionGroupIds = new Map(Object.entries(this._persisted.conversationGroupIds));
65076
- console.log(`[SecureChannel] Restored ${this._sessionGroupIds.size} conversation\u2192group mappings`);
65083
+ if (cleanedCount > 0) {
65084
+ console.log(`[SecureChannel] Cleaned ${cleanedCount} stale DR room sessions`);
65077
65085
  }
65078
- if (this._persisted.rooms) {
65079
- for (const [roomId, room] of Object.entries(this._persisted.rooms)) {
65080
- const mlsGroupId = room.mlsGroupId;
65081
- if (mlsGroupId) {
65082
- try {
65083
- const mlsState = await loadMlsState(this.config.dataDir, mlsGroupId);
65084
- if (mlsState) {
65085
- const mgr = new MLSGroupManager();
65086
- mgr.importState(JSON.parse(mlsState));
65087
- this._mlsGroups.set(roomId, mgr);
65088
- }
65089
- } catch (mlsErr) {
65090
- console.warn(`[SecureChannel] Failed to restore MLS state for room ${roomId.slice(0, 8)}:`, mlsErr);
65091
- }
65092
- }
65093
- }
65086
+ }
65087
+ for (const [convId, sessionData] of Object.entries(
65088
+ this._persisted.sessions
65089
+ )) {
65090
+ if (sessionData.ratchetState) {
65091
+ const ratchet = DoubleRatchet.deserialize(sessionData.ratchetState);
65092
+ this._sessions.set(convId, {
65093
+ ownerDeviceId: sessionData.ownerDeviceId,
65094
+ ratchet,
65095
+ activated: sessionData.activated ?? false,
65096
+ epoch: sessionData.epoch
65097
+ });
65094
65098
  }
65095
- for (const [convId, entry] of Object.entries(this._persisted.mlsConversations ?? {})) {
65096
- if (entry.mlsGroupId) {
65099
+ }
65100
+ if (this._persisted.conversationGroupIds) {
65101
+ this._sessionGroupIds = new Map(Object.entries(this._persisted.conversationGroupIds));
65102
+ console.log(`[SecureChannel] Restored ${this._sessionGroupIds.size} conversation\u2192group mappings`);
65103
+ }
65104
+ if (this._persisted.rooms) {
65105
+ for (const [roomId, room] of Object.entries(this._persisted.rooms)) {
65106
+ const mlsGroupId = room.mlsGroupId;
65107
+ if (mlsGroupId) {
65097
65108
  try {
65098
- const mlsState = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
65109
+ const mlsState = await loadMlsState(this.config.dataDir, mlsGroupId);
65099
65110
  if (mlsState) {
65100
65111
  const mgr = new MLSGroupManager();
65101
65112
  mgr.importState(JSON.parse(mlsState));
65102
- this._mlsGroups.set(`conv:${convId}`, mgr);
65113
+ this._mlsGroups.set(roomId, mgr);
65103
65114
  }
65104
65115
  } catch (mlsErr) {
65105
- console.warn(`[SecureChannel] Failed to restore MLS state for conv ${convId.slice(0, 8)}:`, mlsErr);
65116
+ console.warn(`[SecureChannel] Failed to restore MLS state for room ${roomId.slice(0, 8)}:`, mlsErr);
65106
65117
  }
65107
65118
  }
65108
65119
  }
65109
- for (const [gid, entry] of Object.entries(this._persisted.mlsGroups ?? {})) {
65110
- if (!entry.mlsGroupId) continue;
65120
+ }
65121
+ for (const [convId, entry] of Object.entries(this._persisted.mlsConversations ?? {})) {
65122
+ if (entry.mlsGroupId) {
65111
65123
  try {
65112
- const stateJson = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
65113
- if (stateJson) {
65124
+ const mlsState = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
65125
+ if (mlsState) {
65114
65126
  const mgr = new MLSGroupManager();
65115
- mgr.importState(JSON.parse(stateJson));
65116
- this._mlsGroups.set(`1to1-group:${gid}`, mgr);
65117
- console.log(`[SecureChannel] Loaded shared MLS group for ${gid.slice(0, 8)} (epoch=${mgr.epoch})`);
65127
+ mgr.importState(JSON.parse(mlsState));
65128
+ this._mlsGroups.set(`conv:${convId}`, mgr);
65118
65129
  }
65119
- } catch (loadErr) {
65120
- console.warn(`[SecureChannel] Failed to load shared MLS group ${gid.slice(0, 8)}:`, loadErr);
65130
+ } catch (mlsErr) {
65131
+ console.warn(`[SecureChannel] Failed to restore MLS state for conv ${convId.slice(0, 8)}:`, mlsErr);
65121
65132
  }
65122
65133
  }
65123
- for (const [channelId, entry] of Object.entries(this._persisted.a2aChannels ?? {})) {
65124
- if (entry.mlsGroupId) {
65125
- try {
65126
- const mlsState = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
65127
- if (mlsState) {
65128
- const mgr = new MLSGroupManager();
65129
- mgr.importState(JSON.parse(mlsState));
65130
- this._mlsGroups.set(`a2a:${channelId}`, mgr);
65131
- }
65132
- } catch (mlsErr) {
65133
- console.warn(`[SecureChannel] Failed to restore MLS state for A2A ${channelId.slice(0, 8)}:`, mlsErr);
65134
- }
65134
+ }
65135
+ for (const [gid, entry] of Object.entries(this._persisted.mlsGroups ?? {})) {
65136
+ if (!entry.mlsGroupId) continue;
65137
+ try {
65138
+ const stateJson = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
65139
+ if (stateJson) {
65140
+ const mgr = new MLSGroupManager();
65141
+ mgr.importState(JSON.parse(stateJson));
65142
+ this._mlsGroups.set(`1to1-group:${gid}`, mgr);
65143
+ console.log(`[SecureChannel] Loaded shared MLS group for ${gid.slice(0, 8)} (epoch=${mgr.epoch})`);
65135
65144
  }
65145
+ } catch (loadErr) {
65146
+ console.warn(`[SecureChannel] Failed to load shared MLS group ${gid.slice(0, 8)}:`, loadErr);
65136
65147
  }
65137
- this._connect();
65138
- return;
65139
65148
  }
65140
- const restored = await restoreState(this.config.dataDir);
65141
- if (restored) {
65142
- console.log("[SecureChannel] Restored state from backup");
65143
- const restoredRaw = await loadState(this.config.dataDir);
65144
- if (restoredRaw) {
65145
- this._persisted = migratePersistedState(restoredRaw);
65146
- if (!this._persisted.messageHistory) this._persisted.messageHistory = [];
65147
- this._deviceId = this._persisted.deviceId;
65148
- this._deviceJwt = this._persisted.deviceJwt;
65149
- this._primaryConversationId = this._persisted.primaryConversationId;
65150
- this._fingerprint = this._persisted.fingerprint;
65151
- for (const [convId, sd] of Object.entries(this._persisted.sessions)) {
65152
- if (sd.ratchetState) {
65153
- this._sessions.set(convId, {
65154
- ownerDeviceId: sd.ownerDeviceId,
65155
- ratchet: DoubleRatchet.deserialize(sd.ratchetState),
65156
- activated: sd.activated ?? false,
65157
- epoch: sd.epoch
65158
- });
65149
+ for (const [channelId, entry] of Object.entries(this._persisted.a2aChannels ?? {})) {
65150
+ if (entry.mlsGroupId) {
65151
+ try {
65152
+ const mlsState = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
65153
+ if (mlsState) {
65154
+ const mgr = new MLSGroupManager();
65155
+ mgr.importState(JSON.parse(mlsState));
65156
+ this._mlsGroups.set(`a2a:${channelId}`, mgr);
65159
65157
  }
65158
+ } catch (mlsErr) {
65159
+ console.warn(`[SecureChannel] Failed to restore MLS state for A2A ${channelId.slice(0, 8)}:`, mlsErr);
65160
65160
  }
65161
- this._connect();
65162
- return;
65163
65161
  }
65164
65162
  }
65165
- await this._enroll();
65166
65163
  }
65167
65164
  /**
65168
65165
  * Fetch scan rules from the server and load them into the ScanEngine.
@@ -65502,7 +65499,7 @@ var init_channel = __esm2({
65502
65499
  */
65503
65500
  sendActivitySpan(spanData) {
65504
65501
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65505
- const pluginVersion = true ? "0.23.2" : "0.0.0-dev";
65502
+ const pluginVersion = true ? "0.23.3" : "0.0.0-dev";
65506
65503
  const agentName = this.config.agentName ?? "Agent";
65507
65504
  const resource = {
65508
65505
  "service.name": "agentvault-agent",
@@ -67119,7 +67116,7 @@ var init_channel = __esm2({
67119
67116
  agentVersion: this.config.agentVersion ?? "0.0.0",
67120
67117
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67121
67118
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67122
- pluginVersion: true ? "0.23.2" : "0.0.0-dev"
67119
+ pluginVersion: true ? "0.23.3" : "0.0.0-dev"
67123
67120
  });
67124
67121
  this._telemetryReporter.startAutoFlush(3e4);
67125
67122
  }
@@ -67429,7 +67426,7 @@ var init_channel = __esm2({
67429
67426
  agentVersion: this.config.agentVersion ?? "0.0.0",
67430
67427
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67431
67428
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67432
- pluginVersion: true ? "0.23.2" : "0.0.0-dev"
67429
+ pluginVersion: true ? "0.23.3" : "0.0.0-dev"
67433
67430
  });
67434
67431
  this._telemetryReporter.startAutoFlush(3e4);
67435
67432
  }
@@ -68298,13 +68295,13 @@ ${messageText}`;
68298
68295
  * Looks for OpenClaw workspace config, falls back to default path.
68299
68296
  */
68300
68297
  _resolveWorkspaceDir() {
68301
- const homedir2 = osHomedir();
68298
+ const homedir = osHomedir();
68302
68299
  const agentName = this.config.agentName;
68303
68300
  if (this._persisted?.agentRole === "lead") {
68304
- return join4(homedir2, ".openclaw", "workspace");
68301
+ return join4(homedir, ".openclaw", "workspace");
68305
68302
  }
68306
68303
  try {
68307
- const configPath = join4(homedir2, ".openclaw", "openclaw.json");
68304
+ const configPath = join4(homedir, ".openclaw", "openclaw.json");
68308
68305
  const raw = readFileSync(configPath, "utf-8");
68309
68306
  const config22 = JSON.parse(raw);
68310
68307
  const agents = config22?.agents?.list;
@@ -68318,9 +68315,9 @@ ${messageText}`;
68318
68315
  } catch {
68319
68316
  }
68320
68317
  if (agentName && agentName !== "CLI Agent" && agentName !== "OpenClaw Agent") {
68321
- return join4(homedir2, ".openclaw", `workspace-${agentName}`);
68318
+ return join4(homedir, ".openclaw", `workspace-${agentName}`);
68322
68319
  }
68323
- return join4(homedir2, ".openclaw", "workspace");
68320
+ return join4(homedir, ".openclaw", "workspace");
68324
68321
  }
68325
68322
  /**
68326
68323
  * Send a structured JSON reply to a specific conversation.
@@ -97176,7 +97173,7 @@ var init_index = __esm2({
97176
97173
  init_skill_invoker();
97177
97174
  await init_skill_telemetry();
97178
97175
  await init_policy_enforcer();
97179
- VERSION = true ? "0.23.2" : "0.0.0-dev";
97176
+ VERSION = true ? "0.23.3" : "0.0.0-dev";
97180
97177
  }
97181
97178
  });
97182
97179
  await init_index();
@@ -118480,52 +118477,75 @@ function Z_($10, Q4) {
118480
118477
 
118481
118478
  // src/worker-permission.ts
118482
118479
  import { realpathSync as realpathSync2 } from "node:fs";
118483
- import { resolve as resolve2, dirname, basename, sep } from "node:path";
118484
- import { homedir } from "node:os";
118480
+ import { resolve as resolve2, dirname, basename, sep, isAbsolute } from "node:path";
118485
118481
  var PATH_FIELDS = ["file_path", "path", "notebook_path"];
118486
118482
  function canonical(p2) {
118487
118483
  const abs = resolve2(p2);
118488
118484
  try {
118489
118485
  return realpathSync2(abs);
118490
118486
  } catch {
118491
- try {
118492
- return resolve2(realpathSync2(dirname(abs)), basename(abs));
118493
- } catch {
118494
- return abs;
118487
+ const suffix = [];
118488
+ let dir = abs;
118489
+ for (; ; ) {
118490
+ const parent2 = dirname(dir);
118491
+ suffix.unshift(basename(dir));
118492
+ if (parent2 === dir) return abs;
118493
+ try {
118494
+ return resolve2(realpathSync2(parent2), ...suffix);
118495
+ } catch {
118496
+ dir = parent2;
118497
+ }
118495
118498
  }
118496
118499
  }
118497
118500
  }
118498
- function targetsDataDir(_toolName, input, dataDir) {
118499
- const canonDir = canonical(dataDir);
118501
+ var FILE_TOOLS = /* @__PURE__ */ new Set(["Read", "Write", "Edit", "MultiEdit", "NotebookEdit", "Glob", "Grep"]);
118502
+ function pathsOf(input) {
118503
+ const out = [];
118500
118504
  for (const f7 of PATH_FIELDS) {
118501
118505
  const v5 = input[f7];
118502
- if (typeof v5 === "string") {
118503
- const canon = canonical(v5);
118504
- if (canon === canonDir || canon.startsWith(canonDir + sep)) return true;
118505
- }
118506
+ if (typeof v5 === "string" && v5.length > 0) out.push(canonical(v5));
118506
118507
  }
118507
- const cmd = input.command;
118508
- if (typeof cmd === "string") {
118509
- if (cmd.includes(canonDir)) return true;
118510
- const home = process.env.HOME || homedir();
118511
- if (home && (canonDir === home || canonDir.startsWith(home + sep))) {
118512
- const rel = canonDir.slice(home.length);
118513
- for (const prefix of ["~", "$HOME", "${HOME}"]) {
118514
- if (cmd.includes(prefix + rel)) return true;
118515
- }
118516
- }
118517
- }
118518
- return false;
118508
+ return out;
118509
+ }
118510
+ function within(canonTarget, canonRoot) {
118511
+ return canonTarget === canonRoot || canonTarget.startsWith(canonRoot + sep);
118512
+ }
118513
+ function globPatternEscapes(pattern) {
118514
+ if (typeof pattern !== "string" || pattern.length === 0) return true;
118515
+ if (!/^[A-Za-z0-9 _./*?-]+$/.test(pattern)) return true;
118516
+ if (isAbsolute(pattern)) return true;
118517
+ return pattern.split("/").some((seg) => seg === ".." || seg === "");
118519
118518
  }
118520
118519
  function gateDecision(toolName, input, opts) {
118521
118520
  if (toolName === ROOM_SAY_TOOL_NAME) return { deny: false };
118522
118521
  if (!opts.isToolTurn()) {
118523
118522
  return { deny: true, reason: "tools are disabled on this turn; reply with the say tool only" };
118524
118523
  }
118525
- if (targetsDataDir(toolName, input, opts.dataDir)) {
118526
- return { deny: true, reason: "access to the AgentVault key directory is not permitted" };
118524
+ if (toolName === "Bash") {
118525
+ return opts.osIsolated ? { deny: false } : { deny: true, reason: "Bash is disabled in worker mode unless the process is OS-isolated (set AV_WORKER_OS_ISOLATED=1)" };
118527
118526
  }
118528
- return { deny: false };
118527
+ if (FILE_TOOLS.has(toolName)) {
118528
+ if (!opts.workspaceDir) {
118529
+ return { deny: true, reason: "no workspace is configured; file tools are disabled" };
118530
+ }
118531
+ const paths = pathsOf(input);
118532
+ if (paths.length === 0) {
118533
+ return { deny: true, reason: "file tool call has no resolvable path; blocked (workspace-confined)" };
118534
+ }
118535
+ if (toolName === "Glob" && globPatternEscapes(input.pattern)) {
118536
+ return { deny: true, reason: "glob pattern must be workspace-relative" };
118537
+ }
118538
+ const wsRoot = canonical(opts.workspaceDir);
118539
+ if (!paths.every((p2) => within(p2, wsRoot))) {
118540
+ return { deny: true, reason: "file access is restricted to the workspace directory" };
118541
+ }
118542
+ const dataRoot = canonical(opts.dataDir);
118543
+ if (paths.some((p2) => within(p2, dataRoot))) {
118544
+ return { deny: true, reason: "access to the AgentVault key directory is not permitted" };
118545
+ }
118546
+ return { deny: false };
118547
+ }
118548
+ return { deny: true, reason: `tool ${toolName} is not permitted in worker mode` };
118529
118549
  }
118530
118550
  function makeWorkerPreToolUseHook(opts) {
118531
118551
  return {
@@ -132489,6 +132509,8 @@ var PersistentClaudeSession = class {
132489
132509
  }
132490
132510
  const gateOpts = {
132491
132511
  dataDir: this.opts.dataDir ?? "",
132512
+ workspaceDir: this.opts.workspaceDir,
132513
+ osIsolated: this.opts.osIsolated ?? false,
132492
132514
  // LOAD-BEARING: tools are allowed when this is an owner DM turn
132493
132515
  // (currentAutoReply) OR an armed-room turn (currentArmedGetter()). An UNARMED
132494
132516
  // room turn keeps both false → the gate denies all tools but room_say.
@@ -132510,6 +132532,9 @@ var PersistentClaudeSession = class {
132510
132532
  });
132511
132533
  }
132512
132534
  };
132535
+ console.error(
132536
+ `[worker-gate] allowlist active \u2014 workspace=${this.opts.workspaceDir ?? "(none: file tools disabled)"}, bash=${this.opts.osIsolated ? "enabled (OS-isolated)" : "disabled"}`
132537
+ );
132513
132538
  return {
132514
132539
  ...base,
132515
132540
  permissionMode: this.opts.permissionMode ?? "auto",
@@ -132918,7 +132943,7 @@ async function main() {
132918
132943
  "[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"
132919
132944
  );
132920
132945
  }
132921
- console.error(`[bridge] version: ${true ? "0.5.5" : "dev"}`);
132946
+ console.error(`[bridge] version: ${true ? "0.5.7" : "dev"}`);
132922
132947
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
132923
132948
  if (cfg.worker) {
132924
132949
  console.error(`[bridge] WORKER MODE \u2014 workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
@@ -132956,6 +132981,7 @@ async function main() {
132956
132981
  onObserve: (text) => console.error(`[bridge] observed (${text.length} chars, not sent)`),
132957
132982
  worker: cfg.worker,
132958
132983
  workspaceDir: cfg.workspaceDir,
132984
+ osIsolated: cfg.osIsolated,
132959
132985
  permissionMode: cfg.permissionMode,
132960
132986
  dataDir: cfg.dataDir,
132961
132987
  // Slice 2 Plan B: audit self-report context (only used on armed-room turns).
package/dist/session.d.ts CHANGED
@@ -60,6 +60,8 @@ export interface SessionOpts {
60
60
  worker?: boolean;
61
61
  /** Workspace directory to set as cwd in worker mode. */
62
62
  workspaceDir?: string;
63
+ /** True when the process is attested OS-isolated, gating whether Bash is allowed at all. */
64
+ osIsolated?: boolean;
63
65
  /** Claude permission mode for worker turns (default: "auto"). */
64
66
  permissionMode?: "auto" | "acceptEdits" | "bypassPermissions";
65
67
  /** AgentVault data directory — fenced off from worker tool access. */
@@ -8,6 +8,10 @@ import type { CanUseTool, HookCallbackMatcher } from "@anthropic-ai/claude-agent
8
8
  */
9
9
  export declare function makeWorkerPreToolUseHook(opts: {
10
10
  dataDir: string;
11
+ /** The confined workspace root; file tools are denied entirely when unset. */
12
+ workspaceDir?: string;
13
+ /** True when the process is attested OS-isolated, gating whether Bash is allowed at all. */
14
+ osIsolated: boolean;
11
15
  isToolTurn: () => boolean;
12
16
  /** Slice 2 Plan B: true when this turn comes from an armed room. When set,
13
17
  * each tool decision (allow AND deny) is forwarded to onToolDecision for
@@ -27,6 +31,10 @@ export declare function makeWorkerPreToolUseHook(opts: {
27
31
  */
28
32
  export declare function makeWorkerPermission(opts: {
29
33
  dataDir: string;
34
+ /** The confined workspace root; file tools are denied entirely when unset. */
35
+ workspaceDir?: string;
36
+ /** True when the process is attested OS-isolated, gating whether Bash is allowed at all. */
37
+ osIsolated: boolean;
30
38
  isToolTurn: () => boolean;
31
39
  }): CanUseTool;
32
40
  //# sourceMappingURL=worker-permission.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "type": "module",
5
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",