@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 +3 -0
- package/dist/index.js +65 -34
- package/dist/session.d.ts +2 -0
- package/dist/worker-permission.d.ts +8 -0
- package/package.json +1 -1
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
|
|
68298
|
+
const homedir = osHomedir();
|
|
68297
68299
|
const agentName = this.config.agentName;
|
|
68298
68300
|
if (this._persisted?.agentRole === "lead") {
|
|
68299
|
-
return join4(
|
|
68301
|
+
return join4(homedir, ".openclaw", "workspace");
|
|
68300
68302
|
}
|
|
68301
68303
|
try {
|
|
68302
|
-
const configPath = join4(
|
|
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(
|
|
68318
|
+
return join4(homedir, ".openclaw", `workspace-${agentName}`);
|
|
68317
68319
|
}
|
|
68318
|
-
return join4(
|
|
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
|
-
|
|
118487
|
-
|
|
118488
|
-
|
|
118489
|
-
|
|
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
|
-
|
|
118494
|
-
|
|
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
|
-
|
|
118503
|
-
|
|
118504
|
-
|
|
118505
|
-
|
|
118506
|
-
|
|
118507
|
-
|
|
118508
|
-
|
|
118509
|
-
|
|
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 (
|
|
118521
|
-
return { deny: true, reason: "
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|