@agentvault/claude-bridge 0.5.6 → 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;
@@ -68293,13 +68295,13 @@ ${messageText}`;
68293
68295
  * Looks for OpenClaw workspace config, falls back to default path.
68294
68296
  */
68295
68297
  _resolveWorkspaceDir() {
68296
- const homedir2 = osHomedir();
68298
+ const homedir = osHomedir();
68297
68299
  const agentName = this.config.agentName;
68298
68300
  if (this._persisted?.agentRole === "lead") {
68299
- return join4(homedir2, ".openclaw", "workspace");
68301
+ return join4(homedir, ".openclaw", "workspace");
68300
68302
  }
68301
68303
  try {
68302
- const configPath = join4(homedir2, ".openclaw", "openclaw.json");
68304
+ const configPath = join4(homedir, ".openclaw", "openclaw.json");
68303
68305
  const raw = readFileSync(configPath, "utf-8");
68304
68306
  const config22 = JSON.parse(raw);
68305
68307
  const agents = config22?.agents?.list;
@@ -68313,9 +68315,9 @@ ${messageText}`;
68313
68315
  } catch {
68314
68316
  }
68315
68317
  if (agentName && agentName !== "CLI Agent" && agentName !== "OpenClaw Agent") {
68316
- return join4(homedir2, ".openclaw", `workspace-${agentName}`);
68318
+ return join4(homedir, ".openclaw", `workspace-${agentName}`);
68317
68319
  }
68318
- return join4(homedir2, ".openclaw", "workspace");
68320
+ return join4(homedir, ".openclaw", "workspace");
68319
68321
  }
68320
68322
  /**
68321
68323
  * Send a structured JSON reply to a specific conversation.
@@ -118475,52 +118477,75 @@ function Z_($10, Q4) {
118475
118477
 
118476
118478
  // src/worker-permission.ts
118477
118479
  import { realpathSync as realpathSync2 } from "node:fs";
118478
- import { resolve as resolve2, dirname, basename, sep } from "node:path";
118479
- import { homedir } from "node:os";
118480
+ import { resolve as resolve2, dirname, basename, sep, isAbsolute } from "node:path";
118480
118481
  var PATH_FIELDS = ["file_path", "path", "notebook_path"];
118481
118482
  function canonical(p2) {
118482
118483
  const abs = resolve2(p2);
118483
118484
  try {
118484
118485
  return realpathSync2(abs);
118485
118486
  } catch {
118486
- try {
118487
- return resolve2(realpathSync2(dirname(abs)), basename(abs));
118488
- } catch {
118489
- 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
+ }
118490
118498
  }
118491
118499
  }
118492
118500
  }
118493
- function targetsDataDir(_toolName, input, dataDir) {
118494
- 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 = [];
118495
118504
  for (const f7 of PATH_FIELDS) {
118496
118505
  const v5 = input[f7];
118497
- if (typeof v5 === "string") {
118498
- const canon = canonical(v5);
118499
- if (canon === canonDir || canon.startsWith(canonDir + sep)) return true;
118500
- }
118506
+ if (typeof v5 === "string" && v5.length > 0) out.push(canonical(v5));
118501
118507
  }
118502
- const cmd = input.command;
118503
- if (typeof cmd === "string") {
118504
- if (cmd.includes(canonDir)) return true;
118505
- const home = process.env.HOME || homedir();
118506
- if (home && (canonDir === home || canonDir.startsWith(home + sep))) {
118507
- const rel = canonDir.slice(home.length);
118508
- for (const prefix of ["~", "$HOME", "${HOME}"]) {
118509
- if (cmd.includes(prefix + rel)) return true;
118510
- }
118511
- }
118512
- }
118513
- 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 === "");
118514
118518
  }
118515
118519
  function gateDecision(toolName, input, opts) {
118516
118520
  if (toolName === ROOM_SAY_TOOL_NAME) return { deny: false };
118517
118521
  if (!opts.isToolTurn()) {
118518
118522
  return { deny: true, reason: "tools are disabled on this turn; reply with the say tool only" };
118519
118523
  }
118520
- if (targetsDataDir(toolName, input, opts.dataDir)) {
118521
- 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)" };
118522
118526
  }
118523
- 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` };
118524
118549
  }
118525
118550
  function makeWorkerPreToolUseHook(opts) {
118526
118551
  return {
@@ -132484,6 +132509,8 @@ var PersistentClaudeSession = class {
132484
132509
  }
132485
132510
  const gateOpts = {
132486
132511
  dataDir: this.opts.dataDir ?? "",
132512
+ workspaceDir: this.opts.workspaceDir,
132513
+ osIsolated: this.opts.osIsolated ?? false,
132487
132514
  // LOAD-BEARING: tools are allowed when this is an owner DM turn
132488
132515
  // (currentAutoReply) OR an armed-room turn (currentArmedGetter()). An UNARMED
132489
132516
  // room turn keeps both false → the gate denies all tools but room_say.
@@ -132505,6 +132532,9 @@ var PersistentClaudeSession = class {
132505
132532
  });
132506
132533
  }
132507
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
+ );
132508
132538
  return {
132509
132539
  ...base,
132510
132540
  permissionMode: this.opts.permissionMode ?? "auto",
@@ -132913,7 +132943,7 @@ async function main() {
132913
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"
132914
132944
  );
132915
132945
  }
132916
- console.error(`[bridge] version: ${true ? "0.5.6" : "dev"}`);
132946
+ console.error(`[bridge] version: ${true ? "0.5.7" : "dev"}`);
132917
132947
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
132918
132948
  if (cfg.worker) {
132919
132949
  console.error(`[bridge] WORKER MODE \u2014 workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
@@ -132951,6 +132981,7 @@ async function main() {
132951
132981
  onObserve: (text) => console.error(`[bridge] observed (${text.length} chars, not sent)`),
132952
132982
  worker: cfg.worker,
132953
132983
  workspaceDir: cfg.workspaceDir,
132984
+ osIsolated: cfg.osIsolated,
132954
132985
  permissionMode: cfg.permissionMode,
132955
132986
  dataDir: cfg.dataDir,
132956
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.6",
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",