@agenshield/broker 0.5.0 → 0.6.1
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/daemon-forward.d.ts +25 -0
- package/daemon-forward.d.ts.map +1 -0
- package/handlers/events-batch.d.ts +13 -0
- package/handlers/events-batch.d.ts.map +1 -0
- package/handlers/exec.d.ts.map +1 -1
- package/handlers/index.d.ts +2 -0
- package/handlers/index.d.ts.map +1 -1
- package/handlers/policy-check.d.ts +20 -0
- package/handlers/policy-check.d.ts.map +1 -0
- package/handlers/types.d.ts +1 -0
- package/handlers/types.d.ts.map +1 -1
- package/http-fallback.d.ts +3 -0
- package/http-fallback.d.ts.map +1 -1
- package/index.js +260 -26
- package/main.js +539 -57
- package/package.json +2 -2
- package/policies/builtin.d.ts +3 -1
- package/policies/builtin.d.ts.map +1 -1
- package/policies/command-allowlist.d.ts +7 -0
- package/policies/command-allowlist.d.ts.map +1 -1
- package/policies/enforcer.d.ts +5 -0
- package/policies/enforcer.d.ts.map +1 -1
- package/server.d.ts +3 -0
- package/server.d.ts.map +1 -1
- package/types.d.ts +4 -0
- package/types.d.ts.map +1 -1
package/main.js
CHANGED
|
@@ -251,6 +251,47 @@ function matchPattern(name, pattern) {
|
|
|
251
251
|
// libs/shield-broker/src/handlers/exec.ts
|
|
252
252
|
import * as path2 from "node:path";
|
|
253
253
|
import { spawn } from "node:child_process";
|
|
254
|
+
|
|
255
|
+
// libs/shield-broker/src/daemon-forward.ts
|
|
256
|
+
var DAEMON_RPC_TIMEOUT = 2e3;
|
|
257
|
+
async function forwardPolicyToDaemon(operation, target, daemonUrl) {
|
|
258
|
+
try {
|
|
259
|
+
const controller = new AbortController();
|
|
260
|
+
const timeout = setTimeout(() => controller.abort(), DAEMON_RPC_TIMEOUT);
|
|
261
|
+
const response = await fetch(`${daemonUrl}/rpc`, {
|
|
262
|
+
method: "POST",
|
|
263
|
+
headers: { "Content-Type": "application/json" },
|
|
264
|
+
body: JSON.stringify({
|
|
265
|
+
jsonrpc: "2.0",
|
|
266
|
+
id: `broker-fwd-${Date.now()}`,
|
|
267
|
+
method: "policy_check",
|
|
268
|
+
params: { operation, target }
|
|
269
|
+
}),
|
|
270
|
+
signal: controller.signal
|
|
271
|
+
});
|
|
272
|
+
clearTimeout(timeout);
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const json = await response.json();
|
|
277
|
+
if (json.error || !json.result) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const result = json.result;
|
|
281
|
+
if (result.policyId) {
|
|
282
|
+
return {
|
|
283
|
+
allowed: !!result.allowed,
|
|
284
|
+
policyId: result.policyId,
|
|
285
|
+
reason: result.reason
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// libs/shield-broker/src/handlers/exec.ts
|
|
254
295
|
var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
|
|
255
296
|
var DEFAULT_WORKSPACE = "/Users/clawagent/workspace";
|
|
256
297
|
var FS_COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -383,12 +424,16 @@ async function handleExec(params, context, deps) {
|
|
|
383
424
|
if (url) {
|
|
384
425
|
const networkCheck = await deps.policyEnforcer.check("http_request", { url }, context);
|
|
385
426
|
if (!networkCheck.allowed) {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
427
|
+
const daemonUrl = deps.daemonUrl || "http://127.0.0.1:5200";
|
|
428
|
+
const override = await forwardPolicyToDaemon("http_request", url, daemonUrl);
|
|
429
|
+
if (!override || !override.allowed) {
|
|
430
|
+
const reason = `URL not allowed: ${url} - ${networkCheck.reason}`;
|
|
431
|
+
deps.onExecDenied?.(command, reason);
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
error: { code: 1009, message: reason }
|
|
435
|
+
};
|
|
436
|
+
}
|
|
392
437
|
}
|
|
393
438
|
}
|
|
394
439
|
}
|
|
@@ -807,6 +852,86 @@ async function handleSkillUninstall(params, context, deps) {
|
|
|
807
852
|
}
|
|
808
853
|
}
|
|
809
854
|
|
|
855
|
+
// libs/shield-broker/src/handlers/policy-check.ts
|
|
856
|
+
var DEFAULT_DAEMON_URL = "http://127.0.0.1:5200";
|
|
857
|
+
async function handlePolicyCheck(params, context, deps) {
|
|
858
|
+
const { operation, target } = params;
|
|
859
|
+
if (!operation) {
|
|
860
|
+
return {
|
|
861
|
+
success: false,
|
|
862
|
+
error: { code: -32602, message: "Missing required parameter: operation" }
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
let checkParams;
|
|
866
|
+
switch (operation) {
|
|
867
|
+
case "http_request":
|
|
868
|
+
case "open_url":
|
|
869
|
+
checkParams = { url: target || "" };
|
|
870
|
+
break;
|
|
871
|
+
case "file_read":
|
|
872
|
+
case "file_write":
|
|
873
|
+
case "file_list":
|
|
874
|
+
checkParams = { path: target || "" };
|
|
875
|
+
break;
|
|
876
|
+
case "exec":
|
|
877
|
+
checkParams = { command: target || "" };
|
|
878
|
+
break;
|
|
879
|
+
case "secret_inject":
|
|
880
|
+
checkParams = { name: target || "" };
|
|
881
|
+
break;
|
|
882
|
+
default:
|
|
883
|
+
checkParams = { target: target || "" };
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
const result = await deps.policyEnforcer.check(operation, checkParams, context);
|
|
887
|
+
if (result.allowed) {
|
|
888
|
+
return {
|
|
889
|
+
success: true,
|
|
890
|
+
data: {
|
|
891
|
+
allowed: true,
|
|
892
|
+
policyId: result.policyId,
|
|
893
|
+
reason: result.reason
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const daemonUrl = deps.daemonUrl || DEFAULT_DAEMON_URL;
|
|
898
|
+
const daemonResult = await forwardPolicyToDaemon(operation, target || "", daemonUrl);
|
|
899
|
+
if (daemonResult && daemonResult.allowed) {
|
|
900
|
+
return { success: true, data: daemonResult };
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
success: true,
|
|
904
|
+
data: {
|
|
905
|
+
allowed: false,
|
|
906
|
+
policyId: result.policyId,
|
|
907
|
+
reason: result.reason
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// libs/shield-broker/src/handlers/events-batch.ts
|
|
913
|
+
async function handleEventsBatch(params, context, deps) {
|
|
914
|
+
const { events } = params;
|
|
915
|
+
const eventList = events || [];
|
|
916
|
+
for (const event of eventList) {
|
|
917
|
+
const entry = {
|
|
918
|
+
id: event.id || context.requestId,
|
|
919
|
+
timestamp: event.timestamp ? new Date(event.timestamp) : /* @__PURE__ */ new Date(),
|
|
920
|
+
operation: event.operation || "events_batch",
|
|
921
|
+
channel: "socket",
|
|
922
|
+
allowed: event.allowed ?? true,
|
|
923
|
+
target: event.target || "",
|
|
924
|
+
result: event.allowed === false ? "denied" : "success",
|
|
925
|
+
durationMs: 0
|
|
926
|
+
};
|
|
927
|
+
await deps.auditLogger.log(entry);
|
|
928
|
+
}
|
|
929
|
+
return {
|
|
930
|
+
success: true,
|
|
931
|
+
data: { received: eventList.length }
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
810
935
|
// libs/shield-broker/src/server.ts
|
|
811
936
|
var UnixSocketServer = class {
|
|
812
937
|
server = null;
|
|
@@ -814,12 +939,14 @@ var UnixSocketServer = class {
|
|
|
814
939
|
policyEnforcer;
|
|
815
940
|
auditLogger;
|
|
816
941
|
secretVault;
|
|
942
|
+
commandAllowlist;
|
|
817
943
|
connections = /* @__PURE__ */ new Set();
|
|
818
944
|
constructor(options) {
|
|
819
945
|
this.config = options.config;
|
|
820
946
|
this.policyEnforcer = options.policyEnforcer;
|
|
821
947
|
this.auditLogger = options.auditLogger;
|
|
822
948
|
this.secretVault = options.secretVault;
|
|
949
|
+
this.commandAllowlist = options.commandAllowlist;
|
|
823
950
|
}
|
|
824
951
|
/**
|
|
825
952
|
* Start the Unix socket server
|
|
@@ -923,20 +1050,29 @@ var UnixSocketServer = class {
|
|
|
923
1050
|
request.params,
|
|
924
1051
|
context
|
|
925
1052
|
);
|
|
926
|
-
|
|
1053
|
+
let finalPolicy = policyResult;
|
|
1054
|
+
if (!policyResult.allowed && request.method !== "policy_check") {
|
|
1055
|
+
const target = this.extractTarget(request);
|
|
1056
|
+
const daemonUrl = this.config.daemonUrl || "http://127.0.0.1:5200";
|
|
1057
|
+
const override = await forwardPolicyToDaemon(request.method, target, daemonUrl);
|
|
1058
|
+
if (override) {
|
|
1059
|
+
finalPolicy = override;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (!finalPolicy.allowed) {
|
|
927
1063
|
await this.auditLogger.log({
|
|
928
1064
|
id: requestId,
|
|
929
1065
|
timestamp: /* @__PURE__ */ new Date(),
|
|
930
1066
|
operation: request.method,
|
|
931
1067
|
channel: "socket",
|
|
932
1068
|
allowed: false,
|
|
933
|
-
policyId:
|
|
1069
|
+
policyId: finalPolicy.policyId,
|
|
934
1070
|
target: this.extractTarget(request),
|
|
935
1071
|
result: "denied",
|
|
936
|
-
errorMessage:
|
|
1072
|
+
errorMessage: finalPolicy.reason,
|
|
937
1073
|
durationMs: Date.now() - startTime
|
|
938
1074
|
});
|
|
939
|
-
return this.errorResponse(request.id, 1001,
|
|
1075
|
+
return this.errorResponse(request.id, 1001, finalPolicy.reason || "Policy denied");
|
|
940
1076
|
}
|
|
941
1077
|
const handler = this.getHandler(request.method);
|
|
942
1078
|
if (!handler) {
|
|
@@ -945,7 +1081,9 @@ var UnixSocketServer = class {
|
|
|
945
1081
|
const result = await handler(request.params, context, {
|
|
946
1082
|
policyEnforcer: this.policyEnforcer,
|
|
947
1083
|
auditLogger: this.auditLogger,
|
|
948
|
-
secretVault: this.secretVault
|
|
1084
|
+
secretVault: this.secretVault,
|
|
1085
|
+
commandAllowlist: this.commandAllowlist,
|
|
1086
|
+
daemonUrl: this.config.daemonUrl
|
|
949
1087
|
});
|
|
950
1088
|
await this.auditLogger.log({
|
|
951
1089
|
id: requestId,
|
|
@@ -953,7 +1091,7 @@ var UnixSocketServer = class {
|
|
|
953
1091
|
operation: request.method,
|
|
954
1092
|
channel: "socket",
|
|
955
1093
|
allowed: true,
|
|
956
|
-
policyId:
|
|
1094
|
+
policyId: finalPolicy.policyId,
|
|
957
1095
|
target: this.extractTarget(request),
|
|
958
1096
|
result: result.success ? "success" : "error",
|
|
959
1097
|
errorMessage: result.error?.message,
|
|
@@ -992,7 +1130,9 @@ var UnixSocketServer = class {
|
|
|
992
1130
|
secret_inject: handleSecretInject,
|
|
993
1131
|
ping: handlePing,
|
|
994
1132
|
skill_install: handleSkillInstall,
|
|
995
|
-
skill_uninstall: handleSkillUninstall
|
|
1133
|
+
skill_uninstall: handleSkillUninstall,
|
|
1134
|
+
policy_check: handlePolicyCheck,
|
|
1135
|
+
events_batch: handleEventsBatch
|
|
996
1136
|
};
|
|
997
1137
|
return handlerMap[method];
|
|
998
1138
|
}
|
|
@@ -1023,7 +1163,9 @@ var HTTP_ALLOWED_OPERATIONS = /* @__PURE__ */ new Set([
|
|
|
1023
1163
|
"file_read",
|
|
1024
1164
|
"file_list",
|
|
1025
1165
|
"open_url",
|
|
1026
|
-
"ping"
|
|
1166
|
+
"ping",
|
|
1167
|
+
"policy_check",
|
|
1168
|
+
"events_batch"
|
|
1027
1169
|
]);
|
|
1028
1170
|
var HTTP_DENIED_OPERATIONS = /* @__PURE__ */ new Set([
|
|
1029
1171
|
"exec",
|
|
@@ -1035,10 +1177,12 @@ var HttpFallbackServer = class {
|
|
|
1035
1177
|
config;
|
|
1036
1178
|
policyEnforcer;
|
|
1037
1179
|
auditLogger;
|
|
1180
|
+
commandAllowlist;
|
|
1038
1181
|
constructor(options) {
|
|
1039
1182
|
this.config = options.config;
|
|
1040
1183
|
this.policyEnforcer = options.policyEnforcer;
|
|
1041
1184
|
this.auditLogger = options.auditLogger;
|
|
1185
|
+
this.commandAllowlist = options.commandAllowlist;
|
|
1042
1186
|
}
|
|
1043
1187
|
/**
|
|
1044
1188
|
* Start the HTTP fallback server
|
|
@@ -1159,20 +1303,29 @@ var HttpFallbackServer = class {
|
|
|
1159
1303
|
request.params,
|
|
1160
1304
|
context
|
|
1161
1305
|
);
|
|
1162
|
-
|
|
1306
|
+
let finalPolicy = policyResult;
|
|
1307
|
+
if (!policyResult.allowed && request.method !== "policy_check") {
|
|
1308
|
+
const target = this.extractTarget(request);
|
|
1309
|
+
const daemonUrl = this.config.daemonUrl || "http://127.0.0.1:5200";
|
|
1310
|
+
const override = await forwardPolicyToDaemon(request.method, target, daemonUrl);
|
|
1311
|
+
if (override) {
|
|
1312
|
+
finalPolicy = override;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
if (!finalPolicy.allowed) {
|
|
1163
1316
|
await this.auditLogger.log({
|
|
1164
1317
|
id: requestId,
|
|
1165
1318
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1166
1319
|
operation: request.method,
|
|
1167
1320
|
channel: "http",
|
|
1168
1321
|
allowed: false,
|
|
1169
|
-
policyId:
|
|
1322
|
+
policyId: finalPolicy.policyId,
|
|
1170
1323
|
target: this.extractTarget(request),
|
|
1171
1324
|
result: "denied",
|
|
1172
|
-
errorMessage:
|
|
1325
|
+
errorMessage: finalPolicy.reason,
|
|
1173
1326
|
durationMs: Date.now() - startTime
|
|
1174
1327
|
});
|
|
1175
|
-
return this.errorResponse(request.id, 1001,
|
|
1328
|
+
return this.errorResponse(request.id, 1001, finalPolicy.reason || "Policy denied");
|
|
1176
1329
|
}
|
|
1177
1330
|
const handler = this.getHandler(request.method);
|
|
1178
1331
|
if (!handler) {
|
|
@@ -1181,8 +1334,10 @@ var HttpFallbackServer = class {
|
|
|
1181
1334
|
const result = await handler(request.params, context, {
|
|
1182
1335
|
policyEnforcer: this.policyEnforcer,
|
|
1183
1336
|
auditLogger: this.auditLogger,
|
|
1184
|
-
secretVault: null
|
|
1337
|
+
secretVault: null,
|
|
1185
1338
|
// Not available over HTTP
|
|
1339
|
+
commandAllowlist: this.commandAllowlist,
|
|
1340
|
+
daemonUrl: this.config.daemonUrl
|
|
1186
1341
|
});
|
|
1187
1342
|
await this.auditLogger.log({
|
|
1188
1343
|
id: requestId,
|
|
@@ -1190,7 +1345,7 @@ var HttpFallbackServer = class {
|
|
|
1190
1345
|
operation: request.method,
|
|
1191
1346
|
channel: "http",
|
|
1192
1347
|
allowed: true,
|
|
1193
|
-
policyId:
|
|
1348
|
+
policyId: finalPolicy.policyId,
|
|
1194
1349
|
target: this.extractTarget(request),
|
|
1195
1350
|
result: result.success ? "success" : "error",
|
|
1196
1351
|
errorMessage: result.error?.message,
|
|
@@ -1224,7 +1379,9 @@ var HttpFallbackServer = class {
|
|
|
1224
1379
|
file_read: handleFileRead,
|
|
1225
1380
|
file_list: handleFileList,
|
|
1226
1381
|
open_url: handleOpenUrl,
|
|
1227
|
-
ping: handlePing
|
|
1382
|
+
ping: handlePing,
|
|
1383
|
+
policy_check: handlePolicyCheck,
|
|
1384
|
+
events_batch: handleEventsBatch
|
|
1228
1385
|
};
|
|
1229
1386
|
return handlerMap[method];
|
|
1230
1387
|
}
|
|
@@ -1263,6 +1420,34 @@ var PolicyEnforcer = class {
|
|
|
1263
1420
|
this.policies = options.defaultPolicies;
|
|
1264
1421
|
this.loadPolicies();
|
|
1265
1422
|
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Normalize a policy rule — infer operations from target when missing,
|
|
1425
|
+
* default priority to 0.
|
|
1426
|
+
*/
|
|
1427
|
+
normalizeRule(rule) {
|
|
1428
|
+
const normalized = { ...rule };
|
|
1429
|
+
if (!normalized.priority && normalized.priority !== 0) {
|
|
1430
|
+
normalized.priority = 0;
|
|
1431
|
+
}
|
|
1432
|
+
if (normalized.operations && normalized.operations.length > 0) {
|
|
1433
|
+
return normalized;
|
|
1434
|
+
}
|
|
1435
|
+
switch (normalized.target) {
|
|
1436
|
+
case "url":
|
|
1437
|
+
normalized.operations = ["http_request", "open_url"];
|
|
1438
|
+
break;
|
|
1439
|
+
case "command":
|
|
1440
|
+
normalized.operations = ["exec"];
|
|
1441
|
+
break;
|
|
1442
|
+
case "skill":
|
|
1443
|
+
normalized.operations = ["skill_install", "skill_uninstall"];
|
|
1444
|
+
break;
|
|
1445
|
+
default:
|
|
1446
|
+
normalized.operations = ["*"];
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
return normalized;
|
|
1450
|
+
}
|
|
1266
1451
|
/**
|
|
1267
1452
|
* Load policies from disk
|
|
1268
1453
|
*/
|
|
@@ -1275,7 +1460,7 @@ var PolicyEnforcer = class {
|
|
|
1275
1460
|
this.policies = {
|
|
1276
1461
|
...this.policies,
|
|
1277
1462
|
...loaded,
|
|
1278
|
-
rules: [...this.policies.rules, ...loaded.rules || []]
|
|
1463
|
+
rules: [...this.policies.rules, ...(loaded.rules || []).map((r) => this.normalizeRule(r))]
|
|
1279
1464
|
};
|
|
1280
1465
|
this.lastLoad = Date.now();
|
|
1281
1466
|
} catch (error) {
|
|
@@ -1291,7 +1476,7 @@ var PolicyEnforcer = class {
|
|
|
1291
1476
|
const content = fs4.readFileSync(path4.join(customDir, file), "utf-8");
|
|
1292
1477
|
const custom = JSON.parse(content);
|
|
1293
1478
|
if (custom.rules) {
|
|
1294
|
-
this.policies.rules.push(...custom.rules);
|
|
1479
|
+
this.policies.rules.push(...custom.rules.map((r) => this.normalizeRule(r)));
|
|
1295
1480
|
}
|
|
1296
1481
|
}
|
|
1297
1482
|
}
|
|
@@ -1341,6 +1526,12 @@ var PolicyEnforcer = class {
|
|
|
1341
1526
|
if (!constraintResult.allowed) {
|
|
1342
1527
|
return constraintResult;
|
|
1343
1528
|
}
|
|
1529
|
+
if (["file_read", "file_write", "file_list"].includes(operation) && this.policies.fsConstraints) {
|
|
1530
|
+
return { allowed: true, reason: "Allowed by file system constraints" };
|
|
1531
|
+
}
|
|
1532
|
+
if (operation === "http_request" && this.policies.networkConstraints) {
|
|
1533
|
+
return { allowed: true, reason: "Allowed by network constraints" };
|
|
1534
|
+
}
|
|
1344
1535
|
return {
|
|
1345
1536
|
allowed: this.policies.defaultAction === "allow",
|
|
1346
1537
|
reason: this.policies.defaultAction === "deny" ? "No matching allow policy" : void 0
|
|
@@ -1520,6 +1711,28 @@ var BuiltinPolicies = [
|
|
|
1520
1711
|
enabled: true,
|
|
1521
1712
|
priority: 1e3
|
|
1522
1713
|
},
|
|
1714
|
+
// Allow interceptor policy checks (internal RPC — must not be subject to policy gate)
|
|
1715
|
+
{
|
|
1716
|
+
id: "builtin-allow-policy-check",
|
|
1717
|
+
name: "Allow interceptor policy checks",
|
|
1718
|
+
action: "allow",
|
|
1719
|
+
target: "command",
|
|
1720
|
+
operations: ["policy_check"],
|
|
1721
|
+
patterns: ["*"],
|
|
1722
|
+
enabled: true,
|
|
1723
|
+
priority: 1e3
|
|
1724
|
+
},
|
|
1725
|
+
// Allow interceptor event reporting (internal RPC)
|
|
1726
|
+
{
|
|
1727
|
+
id: "builtin-allow-events-batch",
|
|
1728
|
+
name: "Allow interceptor event reporting",
|
|
1729
|
+
action: "allow",
|
|
1730
|
+
target: "command",
|
|
1731
|
+
operations: ["events_batch"],
|
|
1732
|
+
patterns: ["*"],
|
|
1733
|
+
enabled: true,
|
|
1734
|
+
priority: 1e3
|
|
1735
|
+
},
|
|
1523
1736
|
// Allow skill installation/uninstallation (daemon management operations)
|
|
1524
1737
|
{
|
|
1525
1738
|
id: "builtin-allow-skill-management",
|
|
@@ -1540,9 +1753,13 @@ var BuiltinPolicies = [
|
|
|
1540
1753
|
operations: ["http_request"],
|
|
1541
1754
|
patterns: [
|
|
1542
1755
|
"http://localhost:*",
|
|
1756
|
+
"http://localhost:*/**",
|
|
1543
1757
|
"http://127.0.0.1:*",
|
|
1758
|
+
"http://127.0.0.1:*/**",
|
|
1544
1759
|
"https://localhost:*",
|
|
1545
|
-
"https://
|
|
1760
|
+
"https://localhost:*/**",
|
|
1761
|
+
"https://127.0.0.1:*",
|
|
1762
|
+
"https://127.0.0.1:*/**"
|
|
1546
1763
|
],
|
|
1547
1764
|
enabled: true,
|
|
1548
1765
|
priority: 100
|
|
@@ -1642,10 +1859,15 @@ var BuiltinPolicies = [
|
|
|
1642
1859
|
target: "url",
|
|
1643
1860
|
operations: ["http_request"],
|
|
1644
1861
|
patterns: [
|
|
1862
|
+
"https://api.anthropic.com",
|
|
1645
1863
|
"https://api.anthropic.com/**",
|
|
1864
|
+
"https://api.openai.com",
|
|
1646
1865
|
"https://api.openai.com/**",
|
|
1866
|
+
"https://api.cohere.ai",
|
|
1647
1867
|
"https://api.cohere.ai/**",
|
|
1868
|
+
"https://generativelanguage.googleapis.com",
|
|
1648
1869
|
"https://generativelanguage.googleapis.com/**",
|
|
1870
|
+
"https://api.mistral.ai",
|
|
1649
1871
|
"https://api.mistral.ai/**"
|
|
1650
1872
|
],
|
|
1651
1873
|
enabled: true,
|
|
@@ -1659,10 +1881,15 @@ var BuiltinPolicies = [
|
|
|
1659
1881
|
target: "url",
|
|
1660
1882
|
operations: ["http_request"],
|
|
1661
1883
|
patterns: [
|
|
1884
|
+
"https://registry.npmjs.org",
|
|
1662
1885
|
"https://registry.npmjs.org/**",
|
|
1886
|
+
"https://pypi.org",
|
|
1663
1887
|
"https://pypi.org/**",
|
|
1888
|
+
"https://files.pythonhosted.org",
|
|
1664
1889
|
"https://files.pythonhosted.org/**",
|
|
1890
|
+
"https://crates.io",
|
|
1665
1891
|
"https://crates.io/**",
|
|
1892
|
+
"https://rubygems.org",
|
|
1666
1893
|
"https://rubygems.org/**"
|
|
1667
1894
|
],
|
|
1668
1895
|
enabled: true,
|
|
@@ -1676,23 +1903,28 @@ var BuiltinPolicies = [
|
|
|
1676
1903
|
target: "url",
|
|
1677
1904
|
operations: ["http_request"],
|
|
1678
1905
|
patterns: [
|
|
1906
|
+
"https://github.com",
|
|
1679
1907
|
"https://github.com/**",
|
|
1908
|
+
"https://api.github.com",
|
|
1680
1909
|
"https://api.github.com/**",
|
|
1910
|
+
"https://raw.githubusercontent.com",
|
|
1681
1911
|
"https://raw.githubusercontent.com/**",
|
|
1912
|
+
"https://gist.github.com",
|
|
1682
1913
|
"https://gist.github.com/**"
|
|
1683
1914
|
],
|
|
1684
1915
|
enabled: true,
|
|
1685
1916
|
priority: 50
|
|
1686
1917
|
}
|
|
1687
1918
|
];
|
|
1688
|
-
function getDefaultPolicies() {
|
|
1919
|
+
function getDefaultPolicies(options) {
|
|
1920
|
+
const agentHome = options?.agentHome || process.env["AGENSHIELD_AGENT_HOME"] || "/Users/clawagent";
|
|
1689
1921
|
return {
|
|
1690
1922
|
version: "1.0.0",
|
|
1691
1923
|
defaultAction: "deny",
|
|
1692
1924
|
rules: [...BuiltinPolicies],
|
|
1693
1925
|
fsConstraints: {
|
|
1694
1926
|
allowedPaths: [
|
|
1695
|
-
|
|
1927
|
+
agentHome,
|
|
1696
1928
|
"/tmp/agenshield"
|
|
1697
1929
|
],
|
|
1698
1930
|
deniedPatterns: [
|
|
@@ -1719,9 +1951,190 @@ function getDefaultPolicies() {
|
|
|
1719
1951
|
};
|
|
1720
1952
|
}
|
|
1721
1953
|
|
|
1722
|
-
// libs/shield-broker/src/
|
|
1954
|
+
// libs/shield-broker/src/policies/command-allowlist.ts
|
|
1723
1955
|
import * as fs5 from "node:fs";
|
|
1724
1956
|
import * as path5 from "node:path";
|
|
1957
|
+
var BUILTIN_COMMANDS = {
|
|
1958
|
+
git: ["/usr/bin/git", "/opt/homebrew/bin/git", "/usr/local/bin/git"],
|
|
1959
|
+
ssh: ["/usr/bin/ssh"],
|
|
1960
|
+
scp: ["/usr/bin/scp"],
|
|
1961
|
+
rsync: ["/usr/bin/rsync", "/opt/homebrew/bin/rsync"],
|
|
1962
|
+
brew: ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"],
|
|
1963
|
+
npm: ["/opt/homebrew/bin/npm", "/usr/local/bin/npm"],
|
|
1964
|
+
npx: ["/opt/homebrew/bin/npx", "/usr/local/bin/npx"],
|
|
1965
|
+
pip: ["/usr/bin/pip", "/usr/local/bin/pip", "/opt/homebrew/bin/pip"],
|
|
1966
|
+
pip3: ["/usr/bin/pip3", "/usr/local/bin/pip3", "/opt/homebrew/bin/pip3"],
|
|
1967
|
+
node: ["/opt/homebrew/bin/node", "/usr/local/bin/node"],
|
|
1968
|
+
python: ["/usr/bin/python", "/usr/local/bin/python", "/opt/homebrew/bin/python"],
|
|
1969
|
+
python3: ["/usr/bin/python3", "/usr/local/bin/python3", "/opt/homebrew/bin/python3"],
|
|
1970
|
+
ls: ["/bin/ls"],
|
|
1971
|
+
cat: ["/bin/cat"],
|
|
1972
|
+
grep: ["/usr/bin/grep"],
|
|
1973
|
+
find: ["/usr/bin/find"],
|
|
1974
|
+
mkdir: ["/bin/mkdir"],
|
|
1975
|
+
cp: ["/bin/cp"],
|
|
1976
|
+
mv: ["/bin/mv"],
|
|
1977
|
+
rm: ["/bin/rm"],
|
|
1978
|
+
touch: ["/usr/bin/touch"],
|
|
1979
|
+
chmod: ["/bin/chmod"],
|
|
1980
|
+
head: ["/usr/bin/head"],
|
|
1981
|
+
tail: ["/usr/bin/tail"],
|
|
1982
|
+
wc: ["/usr/bin/wc"],
|
|
1983
|
+
sort: ["/usr/bin/sort"],
|
|
1984
|
+
uniq: ["/usr/bin/uniq"],
|
|
1985
|
+
sed: ["/usr/bin/sed"],
|
|
1986
|
+
awk: ["/usr/bin/awk"],
|
|
1987
|
+
tar: ["/usr/bin/tar"],
|
|
1988
|
+
curl: ["/usr/bin/curl"],
|
|
1989
|
+
wget: ["/usr/local/bin/wget", "/opt/homebrew/bin/wget"]
|
|
1990
|
+
};
|
|
1991
|
+
var CommandAllowlist = class {
|
|
1992
|
+
configPath;
|
|
1993
|
+
dynamicCommands = /* @__PURE__ */ new Map();
|
|
1994
|
+
lastLoad = 0;
|
|
1995
|
+
reloadInterval = 3e4;
|
|
1996
|
+
// 30 seconds
|
|
1997
|
+
constructor(configPath) {
|
|
1998
|
+
this.configPath = configPath;
|
|
1999
|
+
this.load();
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Load dynamic commands from disk
|
|
2003
|
+
*/
|
|
2004
|
+
load() {
|
|
2005
|
+
if (!fs5.existsSync(this.configPath)) {
|
|
2006
|
+
this.lastLoad = Date.now();
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
try {
|
|
2010
|
+
const content = fs5.readFileSync(this.configPath, "utf-8");
|
|
2011
|
+
const config = JSON.parse(content);
|
|
2012
|
+
this.dynamicCommands.clear();
|
|
2013
|
+
for (const cmd of config.commands || []) {
|
|
2014
|
+
this.dynamicCommands.set(cmd.name, cmd);
|
|
2015
|
+
}
|
|
2016
|
+
this.lastLoad = Date.now();
|
|
2017
|
+
} catch {
|
|
2018
|
+
this.lastLoad = Date.now();
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* Reload dynamic commands if stale
|
|
2023
|
+
*/
|
|
2024
|
+
maybeReload() {
|
|
2025
|
+
if (Date.now() - this.lastLoad > this.reloadInterval) {
|
|
2026
|
+
this.load();
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Persist dynamic commands to disk
|
|
2031
|
+
*/
|
|
2032
|
+
save() {
|
|
2033
|
+
const dir = path5.dirname(this.configPath);
|
|
2034
|
+
if (!fs5.existsSync(dir)) {
|
|
2035
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
2036
|
+
}
|
|
2037
|
+
const config = {
|
|
2038
|
+
version: "1.0.0",
|
|
2039
|
+
commands: Array.from(this.dynamicCommands.values())
|
|
2040
|
+
};
|
|
2041
|
+
fs5.writeFileSync(this.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Add a dynamic command
|
|
2045
|
+
*/
|
|
2046
|
+
add(cmd) {
|
|
2047
|
+
this.dynamicCommands.set(cmd.name, cmd);
|
|
2048
|
+
this.save();
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Remove a dynamic command
|
|
2052
|
+
*/
|
|
2053
|
+
remove(name) {
|
|
2054
|
+
const existed = this.dynamicCommands.delete(name);
|
|
2055
|
+
if (existed) {
|
|
2056
|
+
this.save();
|
|
2057
|
+
}
|
|
2058
|
+
return existed;
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Get a dynamic command by name
|
|
2062
|
+
*/
|
|
2063
|
+
get(name) {
|
|
2064
|
+
return this.dynamicCommands.get(name);
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* List all commands (builtin + dynamic)
|
|
2068
|
+
*/
|
|
2069
|
+
list() {
|
|
2070
|
+
const result = [];
|
|
2071
|
+
for (const [name, paths] of Object.entries(BUILTIN_COMMANDS)) {
|
|
2072
|
+
result.push({
|
|
2073
|
+
name,
|
|
2074
|
+
paths,
|
|
2075
|
+
addedAt: "",
|
|
2076
|
+
addedBy: "builtin",
|
|
2077
|
+
builtin: true
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
for (const cmd of this.dynamicCommands.values()) {
|
|
2081
|
+
result.push({ ...cmd, builtin: false });
|
|
2082
|
+
}
|
|
2083
|
+
return result;
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* List only dynamic commands
|
|
2087
|
+
*/
|
|
2088
|
+
listDynamic() {
|
|
2089
|
+
return Array.from(this.dynamicCommands.values());
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Check if a command name conflicts with a builtin
|
|
2093
|
+
*/
|
|
2094
|
+
isBuiltin(name) {
|
|
2095
|
+
return name in BUILTIN_COMMANDS;
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Resolve a command name to an absolute path.
|
|
2099
|
+
* Checks builtin commands first, then dynamic commands.
|
|
2100
|
+
* Validates that the resolved path exists on disk.
|
|
2101
|
+
* Returns null if the command is not allowed.
|
|
2102
|
+
*/
|
|
2103
|
+
resolve(command) {
|
|
2104
|
+
this.maybeReload();
|
|
2105
|
+
if (path5.isAbsolute(command)) {
|
|
2106
|
+
for (const paths of Object.values(BUILTIN_COMMANDS)) {
|
|
2107
|
+
if (paths.includes(command) && fs5.existsSync(command)) {
|
|
2108
|
+
return command;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
for (const cmd of this.dynamicCommands.values()) {
|
|
2112
|
+
if (cmd.paths.includes(command) && fs5.existsSync(command)) {
|
|
2113
|
+
return command;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
return null;
|
|
2117
|
+
}
|
|
2118
|
+
const basename3 = path5.basename(command);
|
|
2119
|
+
const builtinPaths = BUILTIN_COMMANDS[basename3];
|
|
2120
|
+
if (builtinPaths) {
|
|
2121
|
+
for (const p of builtinPaths) {
|
|
2122
|
+
if (fs5.existsSync(p)) return p;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
const dynamicCmd = this.dynamicCommands.get(basename3);
|
|
2126
|
+
if (dynamicCmd && dynamicCmd.paths.length > 0) {
|
|
2127
|
+
for (const p of dynamicCmd.paths) {
|
|
2128
|
+
if (fs5.existsSync(p)) return p;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2135
|
+
// libs/shield-broker/src/audit/logger.ts
|
|
2136
|
+
import * as fs6 from "node:fs";
|
|
2137
|
+
import * as path6 from "node:path";
|
|
1725
2138
|
var AuditLogger = class {
|
|
1726
2139
|
logPath;
|
|
1727
2140
|
logLevel;
|
|
@@ -1746,15 +2159,15 @@ var AuditLogger = class {
|
|
|
1746
2159
|
* Initialize the write stream
|
|
1747
2160
|
*/
|
|
1748
2161
|
initializeStream() {
|
|
1749
|
-
const dir =
|
|
1750
|
-
if (!
|
|
1751
|
-
|
|
2162
|
+
const dir = path6.dirname(this.logPath);
|
|
2163
|
+
if (!fs6.existsSync(dir)) {
|
|
2164
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
1752
2165
|
}
|
|
1753
|
-
if (
|
|
1754
|
-
const stats =
|
|
2166
|
+
if (fs6.existsSync(this.logPath)) {
|
|
2167
|
+
const stats = fs6.statSync(this.logPath);
|
|
1755
2168
|
this.currentSize = stats.size;
|
|
1756
2169
|
}
|
|
1757
|
-
this.writeStream =
|
|
2170
|
+
this.writeStream = fs6.createWriteStream(this.logPath, {
|
|
1758
2171
|
flags: "a",
|
|
1759
2172
|
encoding: "utf-8"
|
|
1760
2173
|
});
|
|
@@ -1773,16 +2186,16 @@ var AuditLogger = class {
|
|
|
1773
2186
|
for (let i = this.maxFiles - 1; i >= 1; i--) {
|
|
1774
2187
|
const oldPath = `${this.logPath}.${i}`;
|
|
1775
2188
|
const newPath = `${this.logPath}.${i + 1}`;
|
|
1776
|
-
if (
|
|
2189
|
+
if (fs6.existsSync(oldPath)) {
|
|
1777
2190
|
if (i === this.maxFiles - 1) {
|
|
1778
|
-
|
|
2191
|
+
fs6.unlinkSync(oldPath);
|
|
1779
2192
|
} else {
|
|
1780
|
-
|
|
2193
|
+
fs6.renameSync(oldPath, newPath);
|
|
1781
2194
|
}
|
|
1782
2195
|
}
|
|
1783
2196
|
}
|
|
1784
|
-
if (
|
|
1785
|
-
|
|
2197
|
+
if (fs6.existsSync(this.logPath)) {
|
|
2198
|
+
fs6.renameSync(this.logPath, `${this.logPath}.1`);
|
|
1786
2199
|
}
|
|
1787
2200
|
this.currentSize = 0;
|
|
1788
2201
|
this.initializeStream();
|
|
@@ -1855,10 +2268,10 @@ var AuditLogger = class {
|
|
|
1855
2268
|
async query(options) {
|
|
1856
2269
|
const results = [];
|
|
1857
2270
|
const limit = options.limit || 1e3;
|
|
1858
|
-
if (!
|
|
2271
|
+
if (!fs6.existsSync(this.logPath)) {
|
|
1859
2272
|
return results;
|
|
1860
2273
|
}
|
|
1861
|
-
const content =
|
|
2274
|
+
const content = fs6.readFileSync(this.logPath, "utf-8");
|
|
1862
2275
|
const lines = content.trim().split("\n");
|
|
1863
2276
|
for (const line of lines.reverse()) {
|
|
1864
2277
|
if (results.length >= limit) break;
|
|
@@ -1896,7 +2309,7 @@ var AuditLogger = class {
|
|
|
1896
2309
|
};
|
|
1897
2310
|
|
|
1898
2311
|
// libs/shield-broker/src/secrets/vault.ts
|
|
1899
|
-
import * as
|
|
2312
|
+
import * as fs7 from "node:fs/promises";
|
|
1900
2313
|
import * as crypto from "node:crypto";
|
|
1901
2314
|
var SecretVault = class {
|
|
1902
2315
|
vaultPath;
|
|
@@ -1918,11 +2331,11 @@ var SecretVault = class {
|
|
|
1918
2331
|
async loadOrCreateKey() {
|
|
1919
2332
|
const keyPath = this.vaultPath.replace(".enc", ".key");
|
|
1920
2333
|
try {
|
|
1921
|
-
const keyData = await
|
|
2334
|
+
const keyData = await fs7.readFile(keyPath);
|
|
1922
2335
|
return keyData;
|
|
1923
2336
|
} catch {
|
|
1924
2337
|
const key = crypto.randomBytes(32);
|
|
1925
|
-
await
|
|
2338
|
+
await fs7.writeFile(keyPath, key, { mode: 384 });
|
|
1926
2339
|
return key;
|
|
1927
2340
|
}
|
|
1928
2341
|
}
|
|
@@ -1931,7 +2344,7 @@ var SecretVault = class {
|
|
|
1931
2344
|
*/
|
|
1932
2345
|
async load() {
|
|
1933
2346
|
try {
|
|
1934
|
-
const content = await
|
|
2347
|
+
const content = await fs7.readFile(this.vaultPath, "utf-8");
|
|
1935
2348
|
this.data = JSON.parse(content);
|
|
1936
2349
|
} catch {
|
|
1937
2350
|
this.data = {
|
|
@@ -1945,7 +2358,7 @@ var SecretVault = class {
|
|
|
1945
2358
|
*/
|
|
1946
2359
|
async save() {
|
|
1947
2360
|
if (!this.data) return;
|
|
1948
|
-
await
|
|
2361
|
+
await fs7.writeFile(
|
|
1949
2362
|
this.vaultPath,
|
|
1950
2363
|
JSON.stringify(this.data, null, 2),
|
|
1951
2364
|
{ mode: 384 }
|
|
@@ -2063,14 +2476,30 @@ var SecretVault = class {
|
|
|
2063
2476
|
};
|
|
2064
2477
|
|
|
2065
2478
|
// libs/shield-broker/src/main.ts
|
|
2066
|
-
import * as
|
|
2067
|
-
import * as
|
|
2479
|
+
import * as fs8 from "node:fs";
|
|
2480
|
+
import * as path7 from "node:path";
|
|
2481
|
+
var PROXIED_COMMANDS = [
|
|
2482
|
+
"curl",
|
|
2483
|
+
"wget",
|
|
2484
|
+
"git",
|
|
2485
|
+
"ssh",
|
|
2486
|
+
"scp",
|
|
2487
|
+
"rsync",
|
|
2488
|
+
"brew",
|
|
2489
|
+
"npm",
|
|
2490
|
+
"npx",
|
|
2491
|
+
"pip",
|
|
2492
|
+
"pip3",
|
|
2493
|
+
"open-url",
|
|
2494
|
+
"shieldctl",
|
|
2495
|
+
"agenco"
|
|
2496
|
+
];
|
|
2068
2497
|
function loadConfig() {
|
|
2069
2498
|
const configPath = process.env["AGENSHIELD_CONFIG"] || "/opt/agenshield/config/shield.json";
|
|
2070
2499
|
let fileConfig = {};
|
|
2071
|
-
if (
|
|
2500
|
+
if (fs8.existsSync(configPath)) {
|
|
2072
2501
|
try {
|
|
2073
|
-
const content =
|
|
2502
|
+
const content = fs8.readFileSync(configPath, "utf-8");
|
|
2074
2503
|
fileConfig = JSON.parse(content);
|
|
2075
2504
|
} catch (error) {
|
|
2076
2505
|
console.warn(`Warning: Failed to load config from ${configPath}:`, error);
|
|
@@ -2091,16 +2520,18 @@ function loadConfig() {
|
|
|
2091
2520
|
failOpen: process.env["AGENSHIELD_FAIL_OPEN"] === "true" || (fileConfig.failOpen ?? false),
|
|
2092
2521
|
socketMode: fileConfig.socketMode || 438,
|
|
2093
2522
|
socketOwner: fileConfig.socketOwner || "clawbroker",
|
|
2094
|
-
socketGroup: fileConfig.socketGroup || "clawshield"
|
|
2523
|
+
socketGroup: fileConfig.socketGroup || "clawshield",
|
|
2524
|
+
agentHome: process.env["AGENSHIELD_AGENT_HOME"] || fileConfig.agentHome,
|
|
2525
|
+
daemonUrl: process.env["AGENSHIELD_DAEMON_URL"] || fileConfig.daemonUrl || "http://127.0.0.1:5200"
|
|
2095
2526
|
};
|
|
2096
2527
|
}
|
|
2097
2528
|
function ensureDirectories(config) {
|
|
2098
|
-
const socketDir =
|
|
2099
|
-
const auditDir =
|
|
2529
|
+
const socketDir = path7.dirname(config.socketPath);
|
|
2530
|
+
const auditDir = path7.dirname(config.auditLogPath);
|
|
2100
2531
|
for (const dir of [socketDir, auditDir, config.policiesPath]) {
|
|
2101
|
-
if (!
|
|
2532
|
+
if (!fs8.existsSync(dir)) {
|
|
2102
2533
|
try {
|
|
2103
|
-
|
|
2534
|
+
fs8.mkdirSync(dir, { recursive: true, mode: 493 });
|
|
2104
2535
|
} catch (error) {
|
|
2105
2536
|
if (error.code !== "EEXIST") {
|
|
2106
2537
|
console.warn(`Warning: Could not create directory ${dir}:`, error);
|
|
@@ -2109,6 +2540,47 @@ function ensureDirectories(config) {
|
|
|
2109
2540
|
}
|
|
2110
2541
|
}
|
|
2111
2542
|
}
|
|
2543
|
+
function ensureProxiedCommandWrappers(binDir) {
|
|
2544
|
+
if (!fs8.existsSync(binDir)) {
|
|
2545
|
+
try {
|
|
2546
|
+
fs8.mkdirSync(binDir, { recursive: true, mode: 493 });
|
|
2547
|
+
} catch {
|
|
2548
|
+
console.warn(`[broker] cannot create bin dir ${binDir}`);
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
const shieldExecPath = "/opt/agenshield/bin/shield-exec";
|
|
2553
|
+
const hasShieldExec = fs8.existsSync(shieldExecPath);
|
|
2554
|
+
let installed = 0;
|
|
2555
|
+
for (const cmd of PROXIED_COMMANDS) {
|
|
2556
|
+
const wrapperPath = path7.join(binDir, cmd);
|
|
2557
|
+
if (fs8.existsSync(wrapperPath)) continue;
|
|
2558
|
+
if (hasShieldExec) {
|
|
2559
|
+
try {
|
|
2560
|
+
fs8.symlinkSync(shieldExecPath, wrapperPath);
|
|
2561
|
+
installed++;
|
|
2562
|
+
continue;
|
|
2563
|
+
} catch {
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
try {
|
|
2567
|
+
const script = [
|
|
2568
|
+
"#!/bin/bash",
|
|
2569
|
+
`# ${cmd} - AgenShield proxy (auto-generated)`,
|
|
2570
|
+
"if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi",
|
|
2571
|
+
`exec /opt/agenshield/bin/shield-client exec ${cmd} "$@"`,
|
|
2572
|
+
""
|
|
2573
|
+
].join("\n");
|
|
2574
|
+
fs8.writeFileSync(wrapperPath, script, { mode: 493 });
|
|
2575
|
+
installed++;
|
|
2576
|
+
} catch {
|
|
2577
|
+
console.warn(`[broker] cannot write wrapper for ${cmd}`);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
if (installed > 0) {
|
|
2581
|
+
console.log(`[broker] installed ${installed} command wrappers in ${binDir}`);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2112
2584
|
async function main() {
|
|
2113
2585
|
console.log(`AgenShield Broker starting at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
2114
2586
|
console.log(`PID: ${process.pid}, UID: ${process.getuid?.()}, GID: ${process.getgid?.()}`);
|
|
@@ -2126,6 +2598,8 @@ async function main() {
|
|
|
2126
2598
|
console.log(`Socket owner: ${config.socketOwner}, group: ${config.socketGroup}`);
|
|
2127
2599
|
console.log(`HTTP Fallback: ${config.httpEnabled ? `${config.httpHost}:${config.httpPort}` : "disabled"}`);
|
|
2128
2600
|
console.log(`Policies: ${config.policiesPath}`);
|
|
2601
|
+
console.log(`Agent Home: ${config.agentHome || "(env fallback)"}`);
|
|
2602
|
+
console.log(`Daemon URL: ${config.daemonUrl || "(default)"}`);
|
|
2129
2603
|
console.log(`Log Level: ${config.logLevel}`);
|
|
2130
2604
|
try {
|
|
2131
2605
|
ensureDirectories(config);
|
|
@@ -2139,17 +2613,24 @@ async function main() {
|
|
|
2139
2613
|
});
|
|
2140
2614
|
const policyEnforcer = new PolicyEnforcer({
|
|
2141
2615
|
policiesPath: config.policiesPath,
|
|
2142
|
-
defaultPolicies: getDefaultPolicies(),
|
|
2616
|
+
defaultPolicies: getDefaultPolicies({ agentHome: config.agentHome }),
|
|
2143
2617
|
failOpen: config.failOpen
|
|
2144
2618
|
});
|
|
2145
2619
|
const secretVault = new SecretVault({
|
|
2146
2620
|
vaultPath: "/etc/agenshield/vault.enc"
|
|
2147
2621
|
});
|
|
2622
|
+
const commandAllowlist = new CommandAllowlist(
|
|
2623
|
+
"/opt/agenshield/config/allowed-commands.json"
|
|
2624
|
+
);
|
|
2625
|
+
if (config.agentHome) {
|
|
2626
|
+
ensureProxiedCommandWrappers(path7.join(config.agentHome, "bin"));
|
|
2627
|
+
}
|
|
2148
2628
|
const socketServer = new UnixSocketServer({
|
|
2149
2629
|
config,
|
|
2150
2630
|
policyEnforcer,
|
|
2151
2631
|
auditLogger,
|
|
2152
|
-
secretVault
|
|
2632
|
+
secretVault,
|
|
2633
|
+
commandAllowlist
|
|
2153
2634
|
});
|
|
2154
2635
|
await socketServer.start();
|
|
2155
2636
|
console.log(`Unix socket server listening on ${config.socketPath}`);
|
|
@@ -2158,7 +2639,8 @@ async function main() {
|
|
|
2158
2639
|
httpServer = new HttpFallbackServer({
|
|
2159
2640
|
config,
|
|
2160
2641
|
policyEnforcer,
|
|
2161
|
-
auditLogger
|
|
2642
|
+
auditLogger,
|
|
2643
|
+
commandAllowlist
|
|
2162
2644
|
});
|
|
2163
2645
|
await httpServer.start();
|
|
2164
2646
|
console.log(`HTTP fallback server listening on ${config.httpHost}:${config.httpPort}`);
|