@agenshield/broker 0.1.0

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.
Files changed (57) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +141 -0
  3. package/audit/logger.d.ts +69 -0
  4. package/audit/logger.d.ts.map +1 -0
  5. package/client/broker-client.d.ts +91 -0
  6. package/client/broker-client.d.ts.map +1 -0
  7. package/client/index.d.ts +8 -0
  8. package/client/index.d.ts.map +1 -0
  9. package/client/index.js +222 -0
  10. package/client/shield-client.d.ts +8 -0
  11. package/client/shield-client.d.ts.map +1 -0
  12. package/client/shield-client.js +410 -0
  13. package/handlers/exec.d.ts +13 -0
  14. package/handlers/exec.d.ts.map +1 -0
  15. package/handlers/file.d.ts +20 -0
  16. package/handlers/file.d.ts.map +1 -0
  17. package/handlers/http.d.ts +9 -0
  18. package/handlers/http.d.ts.map +1 -0
  19. package/handlers/index.d.ts +12 -0
  20. package/handlers/index.d.ts.map +1 -0
  21. package/handlers/open-url.d.ts +9 -0
  22. package/handlers/open-url.d.ts.map +1 -0
  23. package/handlers/ping.d.ts +9 -0
  24. package/handlers/ping.d.ts.map +1 -0
  25. package/handlers/secret-inject.d.ts +9 -0
  26. package/handlers/secret-inject.d.ts.map +1 -0
  27. package/handlers/skill-install.d.ts +17 -0
  28. package/handlers/skill-install.d.ts.map +1 -0
  29. package/handlers/types.d.ts +28 -0
  30. package/handlers/types.d.ts.map +1 -0
  31. package/http-fallback.d.ts +54 -0
  32. package/http-fallback.d.ts.map +1 -0
  33. package/index.d.ts +18 -0
  34. package/index.d.ts.map +1 -0
  35. package/index.js +2636 -0
  36. package/main.d.ts +8 -0
  37. package/main.d.ts.map +1 -0
  38. package/main.js +2136 -0
  39. package/package.json +34 -0
  40. package/policies/builtin.d.ts +15 -0
  41. package/policies/builtin.d.ts.map +1 -0
  42. package/policies/command-allowlist.d.ts +62 -0
  43. package/policies/command-allowlist.d.ts.map +1 -0
  44. package/policies/enforcer.d.ts +98 -0
  45. package/policies/enforcer.d.ts.map +1 -0
  46. package/policies/index.d.ts +8 -0
  47. package/policies/index.d.ts.map +1 -0
  48. package/seatbelt/generator.d.ts +39 -0
  49. package/seatbelt/generator.d.ts.map +1 -0
  50. package/seatbelt/templates.d.ts +36 -0
  51. package/seatbelt/templates.d.ts.map +1 -0
  52. package/secrets/vault.d.ts +67 -0
  53. package/secrets/vault.d.ts.map +1 -0
  54. package/server.d.ts +54 -0
  55. package/server.d.ts.map +1 -0
  56. package/types.d.ts +285 -0
  57. package/types.d.ts.map +1 -0
package/main.js ADDED
@@ -0,0 +1,2136 @@
1
+ #!/usr/bin/env node
2
+
3
+ // libs/shield-broker/src/server.ts
4
+ import * as net from "node:net";
5
+ import * as fs3 from "node:fs";
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ // libs/shield-broker/src/handlers/http.ts
9
+ async function handleHttpRequest(params, context, deps) {
10
+ const startTime = Date.now();
11
+ try {
12
+ const {
13
+ url,
14
+ method = "GET",
15
+ headers = {},
16
+ body,
17
+ timeout = 3e4,
18
+ followRedirects = true
19
+ } = params;
20
+ if (!url) {
21
+ return {
22
+ success: false,
23
+ error: { code: 1003, message: "URL is required" }
24
+ };
25
+ }
26
+ let parsedUrl;
27
+ try {
28
+ parsedUrl = new URL(url);
29
+ } catch {
30
+ return {
31
+ success: false,
32
+ error: { code: 1003, message: "Invalid URL" }
33
+ };
34
+ }
35
+ const controller = new AbortController();
36
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
37
+ try {
38
+ const response = await fetch(url, {
39
+ method,
40
+ headers,
41
+ body: body ? String(body) : void 0,
42
+ signal: controller.signal,
43
+ redirect: followRedirects ? "follow" : "manual"
44
+ });
45
+ clearTimeout(timeoutId);
46
+ const responseHeaders = {};
47
+ response.headers.forEach((value, key) => {
48
+ responseHeaders[key] = value;
49
+ });
50
+ const responseBody = await response.text();
51
+ return {
52
+ success: true,
53
+ data: {
54
+ status: response.status,
55
+ statusText: response.statusText,
56
+ headers: responseHeaders,
57
+ body: responseBody
58
+ },
59
+ audit: {
60
+ duration: Date.now() - startTime,
61
+ bytesTransferred: responseBody.length
62
+ }
63
+ };
64
+ } catch (error) {
65
+ clearTimeout(timeoutId);
66
+ if (error.name === "AbortError") {
67
+ return {
68
+ success: false,
69
+ error: { code: 1004, message: "Request timeout" }
70
+ };
71
+ }
72
+ return {
73
+ success: false,
74
+ error: { code: 1004, message: `Network error: ${error.message}` }
75
+ };
76
+ }
77
+ } catch (error) {
78
+ return {
79
+ success: false,
80
+ error: { code: 1004, message: `Handler error: ${error.message}` }
81
+ };
82
+ }
83
+ }
84
+
85
+ // libs/shield-broker/src/handlers/file.ts
86
+ import * as fs from "node:fs/promises";
87
+ import * as path from "node:path";
88
+ async function handleFileRead(params, context, deps) {
89
+ const startTime = Date.now();
90
+ try {
91
+ const { path: filePath, encoding = "utf-8" } = params;
92
+ if (!filePath) {
93
+ return {
94
+ success: false,
95
+ error: { code: 1003, message: "Path is required" }
96
+ };
97
+ }
98
+ const absolutePath = path.resolve(filePath);
99
+ try {
100
+ await fs.access(absolutePath, fs.constants.R_OK);
101
+ } catch {
102
+ return {
103
+ success: false,
104
+ error: { code: 1005, message: `File not found or not readable: ${absolutePath}` }
105
+ };
106
+ }
107
+ const stats = await fs.stat(absolutePath);
108
+ if (!stats.isFile()) {
109
+ return {
110
+ success: false,
111
+ error: { code: 1005, message: "Path is not a file" }
112
+ };
113
+ }
114
+ const content = await fs.readFile(absolutePath, { encoding });
115
+ return {
116
+ success: true,
117
+ data: {
118
+ content,
119
+ size: stats.size,
120
+ mtime: stats.mtime.toISOString()
121
+ },
122
+ audit: {
123
+ duration: Date.now() - startTime,
124
+ bytesTransferred: stats.size
125
+ }
126
+ };
127
+ } catch (error) {
128
+ return {
129
+ success: false,
130
+ error: { code: 1005, message: `File read error: ${error.message}` }
131
+ };
132
+ }
133
+ }
134
+ async function handleFileWrite(params, context, deps) {
135
+ const startTime = Date.now();
136
+ try {
137
+ const {
138
+ path: filePath,
139
+ content,
140
+ encoding = "utf-8",
141
+ mode
142
+ } = params;
143
+ if (!filePath) {
144
+ return {
145
+ success: false,
146
+ error: { code: 1003, message: "Path is required" }
147
+ };
148
+ }
149
+ if (content === void 0) {
150
+ return {
151
+ success: false,
152
+ error: { code: 1003, message: "Content is required" }
153
+ };
154
+ }
155
+ const absolutePath = path.resolve(filePath);
156
+ const parentDir = path.dirname(absolutePath);
157
+ await fs.mkdir(parentDir, { recursive: true });
158
+ const buffer = Buffer.from(content, encoding);
159
+ await fs.writeFile(absolutePath, buffer, { mode });
160
+ return {
161
+ success: true,
162
+ data: {
163
+ bytesWritten: buffer.length,
164
+ path: absolutePath
165
+ },
166
+ audit: {
167
+ duration: Date.now() - startTime,
168
+ bytesTransferred: buffer.length
169
+ }
170
+ };
171
+ } catch (error) {
172
+ return {
173
+ success: false,
174
+ error: { code: 1005, message: `File write error: ${error.message}` }
175
+ };
176
+ }
177
+ }
178
+ async function handleFileList(params, context, deps) {
179
+ const startTime = Date.now();
180
+ try {
181
+ const { path: dirPath, recursive = false, pattern } = params;
182
+ if (!dirPath) {
183
+ return {
184
+ success: false,
185
+ error: { code: 1003, message: "Path is required" }
186
+ };
187
+ }
188
+ const absolutePath = path.resolve(dirPath);
189
+ try {
190
+ await fs.access(absolutePath, fs.constants.R_OK);
191
+ } catch {
192
+ return {
193
+ success: false,
194
+ error: { code: 1005, message: `Directory not found or not readable: ${absolutePath}` }
195
+ };
196
+ }
197
+ const stats = await fs.stat(absolutePath);
198
+ if (!stats.isDirectory()) {
199
+ return {
200
+ success: false,
201
+ error: { code: 1005, message: "Path is not a directory" }
202
+ };
203
+ }
204
+ const entries = await listDirectory(absolutePath, recursive, pattern);
205
+ return {
206
+ success: true,
207
+ data: { entries },
208
+ audit: {
209
+ duration: Date.now() - startTime
210
+ }
211
+ };
212
+ } catch (error) {
213
+ return {
214
+ success: false,
215
+ error: { code: 1005, message: `File list error: ${error.message}` }
216
+ };
217
+ }
218
+ }
219
+ async function listDirectory(dirPath, recursive, pattern) {
220
+ const entries = [];
221
+ const items = await fs.readdir(dirPath, { withFileTypes: true });
222
+ for (const item of items) {
223
+ const itemPath = path.join(dirPath, item.name);
224
+ if (pattern && !matchPattern(item.name, pattern)) {
225
+ continue;
226
+ }
227
+ try {
228
+ const stats = await fs.stat(itemPath);
229
+ entries.push({
230
+ name: item.name,
231
+ path: itemPath,
232
+ type: item.isDirectory() ? "directory" : item.isSymbolicLink() ? "symlink" : "file",
233
+ size: stats.size,
234
+ mtime: stats.mtime.toISOString()
235
+ });
236
+ if (recursive && item.isDirectory()) {
237
+ const subEntries = await listDirectory(itemPath, recursive, pattern);
238
+ entries.push(...subEntries);
239
+ }
240
+ } catch {
241
+ }
242
+ }
243
+ return entries;
244
+ }
245
+ function matchPattern(name, pattern) {
246
+ const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
247
+ const regex = new RegExp(`^${regexPattern}$`, "i");
248
+ return regex.test(name);
249
+ }
250
+
251
+ // libs/shield-broker/src/handlers/exec.ts
252
+ import * as path2 from "node:path";
253
+ import { spawn } from "node:child_process";
254
+ var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
255
+ var DEFAULT_WORKSPACE = "/Users/clawagent/workspace";
256
+ var FS_COMMANDS = /* @__PURE__ */ new Set([
257
+ "rm",
258
+ "cp",
259
+ "mv",
260
+ "mkdir",
261
+ "touch",
262
+ "chmod",
263
+ "cat",
264
+ "ls",
265
+ "find",
266
+ "head",
267
+ "tail",
268
+ "tar",
269
+ "sed",
270
+ "awk",
271
+ "sort",
272
+ "uniq",
273
+ "wc",
274
+ "grep"
275
+ ]);
276
+ var HTTP_EXEC_COMMANDS = /* @__PURE__ */ new Set(["curl", "wget"]);
277
+ var HTTP_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
278
+ "-X",
279
+ "--request",
280
+ "-H",
281
+ "--header",
282
+ "-d",
283
+ "--data",
284
+ "--data-raw",
285
+ "--data-binary",
286
+ "--data-urlencode",
287
+ "-o",
288
+ "--output",
289
+ "-u",
290
+ "--user",
291
+ "-A",
292
+ "--user-agent",
293
+ "-e",
294
+ "--referer",
295
+ "-b",
296
+ "--cookie",
297
+ "-c",
298
+ "--cookie-jar",
299
+ "--connect-timeout",
300
+ "--max-time",
301
+ "-w",
302
+ "--write-out",
303
+ "-T",
304
+ "--upload-file",
305
+ "--resolve",
306
+ "--cacert",
307
+ "--cert",
308
+ "--key"
309
+ ]);
310
+ function validateFsPaths(args, cwd, allowedPaths) {
311
+ for (let i = 0; i < args.length; i++) {
312
+ const arg = args[i];
313
+ if (arg.startsWith("-")) {
314
+ continue;
315
+ }
316
+ const resolved = path2.isAbsolute(arg) ? path2.resolve(arg) : path2.resolve(cwd, arg);
317
+ const isAllowed = allowedPaths.some((allowed) => resolved.startsWith(allowed));
318
+ if (!isAllowed) {
319
+ return {
320
+ valid: false,
321
+ reason: "Path not in allowed directories",
322
+ violatingPath: resolved
323
+ };
324
+ }
325
+ }
326
+ return { valid: true };
327
+ }
328
+ function extractUrlFromArgs(args) {
329
+ for (let i = 0; i < args.length; i++) {
330
+ const arg = args[i];
331
+ if (arg.startsWith("-")) {
332
+ if (HTTP_FLAGS_WITH_VALUE.has(arg)) {
333
+ i++;
334
+ }
335
+ continue;
336
+ }
337
+ return arg;
338
+ }
339
+ return null;
340
+ }
341
+ async function handleExec(params, context, deps) {
342
+ const startTime = Date.now();
343
+ try {
344
+ const {
345
+ command,
346
+ args = [],
347
+ cwd,
348
+ env,
349
+ timeout = 3e4
350
+ } = params;
351
+ if (!command) {
352
+ return {
353
+ success: false,
354
+ error: { code: 1003, message: "Command is required" }
355
+ };
356
+ }
357
+ const resolvedCommand = deps.commandAllowlist.resolve(command);
358
+ if (!resolvedCommand) {
359
+ const reason = `Command not allowed: ${command}`;
360
+ deps.onExecDenied?.(command, reason);
361
+ return {
362
+ success: false,
363
+ error: { code: 1007, message: reason }
364
+ };
365
+ }
366
+ const commandBasename = path2.basename(resolvedCommand);
367
+ const effectiveCwd = cwd || DEFAULT_WORKSPACE;
368
+ if (FS_COMMANDS.has(commandBasename)) {
369
+ const policies = deps.policyEnforcer.getPolicies();
370
+ const allowedPaths = policies.fsConstraints?.allowedPaths || [DEFAULT_WORKSPACE];
371
+ const fsResult = validateFsPaths(args, effectiveCwd, allowedPaths);
372
+ if (!fsResult.valid) {
373
+ const reason = `${fsResult.reason}: ${fsResult.violatingPath}`;
374
+ deps.onExecDenied?.(command, reason);
375
+ return {
376
+ success: false,
377
+ error: { code: 1008, message: `Path not allowed: ${fsResult.violatingPath} - ${fsResult.reason}` }
378
+ };
379
+ }
380
+ }
381
+ if (HTTP_EXEC_COMMANDS.has(commandBasename)) {
382
+ const url = extractUrlFromArgs(args);
383
+ if (url) {
384
+ const networkCheck = await deps.policyEnforcer.check("http_request", { url }, context);
385
+ if (!networkCheck.allowed) {
386
+ const reason = `URL not allowed: ${url} - ${networkCheck.reason}`;
387
+ deps.onExecDenied?.(command, reason);
388
+ return {
389
+ success: false,
390
+ error: { code: 1009, message: reason }
391
+ };
392
+ }
393
+ }
394
+ }
395
+ const effectiveTimeout = HTTP_EXEC_COMMANDS.has(commandBasename) ? Math.max(timeout, 3e5) : timeout;
396
+ const result = await executeCommand({
397
+ command: resolvedCommand,
398
+ args,
399
+ cwd: effectiveCwd,
400
+ env,
401
+ timeout: effectiveTimeout,
402
+ shell: false
403
+ // Always force shell: false to prevent injection
404
+ });
405
+ const duration = Date.now() - startTime;
406
+ deps.onExecMonitor?.({
407
+ command: commandBasename,
408
+ args,
409
+ cwd: effectiveCwd,
410
+ exitCode: result.exitCode,
411
+ allowed: true,
412
+ duration,
413
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
414
+ });
415
+ return {
416
+ success: true,
417
+ data: result,
418
+ audit: {
419
+ duration
420
+ }
421
+ };
422
+ } catch (error) {
423
+ return {
424
+ success: false,
425
+ error: { code: 1006, message: `Exec error: ${error.message}` }
426
+ };
427
+ }
428
+ }
429
+ async function executeCommand(options) {
430
+ return new Promise((resolve3, reject) => {
431
+ const { command, args = [], cwd, env, timeout = 3e4 } = options;
432
+ const shell = false;
433
+ let stdout = "";
434
+ let stderr = "";
435
+ let stdoutSize = 0;
436
+ let stderrSize = 0;
437
+ const proc = spawn(command, args, {
438
+ cwd,
439
+ env: env ? { ...process.env, ...env } : process.env,
440
+ shell,
441
+ timeout
442
+ });
443
+ proc.stdout?.on("data", (data) => {
444
+ const chunk = data.toString();
445
+ if (stdoutSize + chunk.length <= MAX_OUTPUT_SIZE) {
446
+ stdout += chunk;
447
+ stdoutSize += chunk.length;
448
+ }
449
+ });
450
+ proc.stderr?.on("data", (data) => {
451
+ const chunk = data.toString();
452
+ if (stderrSize + chunk.length <= MAX_OUTPUT_SIZE) {
453
+ stderr += chunk;
454
+ stderrSize += chunk.length;
455
+ }
456
+ });
457
+ proc.on("error", (error) => {
458
+ reject(error);
459
+ });
460
+ proc.on("close", (code, signal) => {
461
+ resolve3({
462
+ exitCode: code ?? -1,
463
+ stdout,
464
+ stderr,
465
+ signal: signal ?? void 0
466
+ });
467
+ });
468
+ const timeoutId = setTimeout(() => {
469
+ proc.kill("SIGTERM");
470
+ setTimeout(() => {
471
+ if (!proc.killed) {
472
+ proc.kill("SIGKILL");
473
+ }
474
+ }, 5e3);
475
+ }, timeout);
476
+ proc.on("exit", () => {
477
+ clearTimeout(timeoutId);
478
+ });
479
+ });
480
+ }
481
+
482
+ // libs/shield-broker/src/handlers/open-url.ts
483
+ import { exec } from "node:child_process";
484
+ import { promisify } from "node:util";
485
+ var execAsync = promisify(exec);
486
+ async function handleOpenUrl(params, context, deps) {
487
+ const startTime = Date.now();
488
+ try {
489
+ const { url, browser } = params;
490
+ if (!url) {
491
+ return {
492
+ success: false,
493
+ error: { code: 1003, message: "URL is required" }
494
+ };
495
+ }
496
+ try {
497
+ new URL(url);
498
+ } catch {
499
+ return {
500
+ success: false,
501
+ error: { code: 1003, message: "Invalid URL" }
502
+ };
503
+ }
504
+ const parsedUrl = new URL(url);
505
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
506
+ return {
507
+ success: false,
508
+ error: { code: 1003, message: "Only http/https URLs are allowed" }
509
+ };
510
+ }
511
+ const command = browser ? `open -a "${browser}" "${url}"` : `open "${url}"`;
512
+ try {
513
+ await execAsync(command, { timeout: 1e4 });
514
+ return {
515
+ success: true,
516
+ data: { opened: true },
517
+ audit: {
518
+ duration: Date.now() - startTime
519
+ }
520
+ };
521
+ } catch (error) {
522
+ return {
523
+ success: false,
524
+ error: {
525
+ code: 1006,
526
+ message: `Failed to open URL: ${error.message}`
527
+ }
528
+ };
529
+ }
530
+ } catch (error) {
531
+ return {
532
+ success: false,
533
+ error: { code: 1006, message: `Handler error: ${error.message}` }
534
+ };
535
+ }
536
+ }
537
+
538
+ // libs/shield-broker/src/handlers/secret-inject.ts
539
+ async function handleSecretInject(params, context, deps) {
540
+ const startTime = Date.now();
541
+ try {
542
+ const { name, targetEnv } = params;
543
+ if (!name) {
544
+ return {
545
+ success: false,
546
+ error: { code: 1003, message: "Secret name is required" }
547
+ };
548
+ }
549
+ if (context.channel !== "socket") {
550
+ return {
551
+ success: false,
552
+ error: { code: 1008, message: "Secret injection only allowed via Unix socket" }
553
+ };
554
+ }
555
+ const secret = await deps.secretVault.get(name);
556
+ if (!secret) {
557
+ return {
558
+ success: false,
559
+ error: { code: 1007, message: `Secret not found: ${name}` }
560
+ };
561
+ }
562
+ return {
563
+ success: true,
564
+ data: {
565
+ value: secret.value,
566
+ injected: true
567
+ },
568
+ audit: {
569
+ duration: Date.now() - startTime
570
+ }
571
+ };
572
+ } catch (error) {
573
+ return {
574
+ success: false,
575
+ error: { code: 1007, message: `Secret inject error: ${error.message}` }
576
+ };
577
+ }
578
+ }
579
+
580
+ // libs/shield-broker/src/handlers/ping.ts
581
+ var VERSION = "0.1.0";
582
+ async function handlePing(params, context, deps) {
583
+ const { echo } = params;
584
+ return {
585
+ success: true,
586
+ data: {
587
+ pong: true,
588
+ echo,
589
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
590
+ version: VERSION
591
+ },
592
+ audit: {
593
+ duration: 0
594
+ }
595
+ };
596
+ }
597
+
598
+ // libs/shield-broker/src/handlers/skill-install.ts
599
+ import * as fs2 from "node:fs/promises";
600
+ import * as path3 from "node:path";
601
+ import { execSync } from "node:child_process";
602
+ function isValidSlug(slug) {
603
+ const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
604
+ return validPattern.test(slug) && !slug.includes("..") && !slug.includes("/");
605
+ }
606
+ function createWrapperContent(slug, skillDir) {
607
+ return `#!/bin/bash
608
+ # Auto-generated wrapper for skill: ${slug}
609
+ # This script runs the skill via openclaw-pkg
610
+
611
+ set -e
612
+
613
+ SKILL_DIR="${skillDir}"
614
+
615
+ # Check if skill directory exists
616
+ if [ ! -d "$SKILL_DIR" ]; then
617
+ echo "Error: Skill directory not found: $SKILL_DIR" >&2
618
+ exit 1
619
+ fi
620
+
621
+ # Find and execute the main skill file
622
+ if [ -f "$SKILL_DIR/skill.md" ]; then
623
+ exec openclaw-pkg run "$SKILL_DIR/skill.md" "$@"
624
+ elif [ -f "$SKILL_DIR/index.js" ]; then
625
+ exec node "$SKILL_DIR/index.js" "$@"
626
+ elif [ -f "$SKILL_DIR/main.py" ]; then
627
+ exec python3 "$SKILL_DIR/main.py" "$@"
628
+ else
629
+ echo "Error: No entry point found in $SKILL_DIR" >&2
630
+ exit 1
631
+ fi
632
+ `;
633
+ }
634
+ async function handleSkillInstall(params, context, deps) {
635
+ const startTime = Date.now();
636
+ try {
637
+ const {
638
+ slug,
639
+ files,
640
+ createWrapper = true,
641
+ agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent",
642
+ socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield"
643
+ } = params;
644
+ if (!slug || !isValidSlug(slug)) {
645
+ return {
646
+ success: false,
647
+ error: { code: 1003, message: `Invalid skill slug: ${slug}. Must be alphanumeric with dashes/underscores.` }
648
+ };
649
+ }
650
+ if (!Array.isArray(files) || files.length === 0) {
651
+ return {
652
+ success: false,
653
+ error: { code: 1003, message: "Files array is required and must not be empty" }
654
+ };
655
+ }
656
+ for (const file of files) {
657
+ if (!file.name || typeof file.name !== "string") {
658
+ return {
659
+ success: false,
660
+ error: { code: 1003, message: "Each file must have a name" }
661
+ };
662
+ }
663
+ if (file.name.includes("..") || file.name.startsWith("/")) {
664
+ return {
665
+ success: false,
666
+ error: { code: 1003, message: `Invalid file name: ${file.name}` }
667
+ };
668
+ }
669
+ }
670
+ const skillsDir = path3.join(agentHome, ".openclaw", "skills");
671
+ const skillDir = path3.join(skillsDir, slug);
672
+ const binDir = path3.join(agentHome, "bin");
673
+ await fs2.mkdir(skillDir, { recursive: true });
674
+ let filesWritten = 0;
675
+ for (const file of files) {
676
+ const filePath = path3.join(skillDir, file.name);
677
+ const fileDir = path3.dirname(filePath);
678
+ await fs2.mkdir(fileDir, { recursive: true });
679
+ const content = file.base64 ? Buffer.from(file.content, "base64") : file.content;
680
+ await fs2.writeFile(filePath, content, { mode: file.mode });
681
+ filesWritten++;
682
+ }
683
+ try {
684
+ execSync(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
685
+ execSync(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
686
+ } catch (err) {
687
+ console.warn(`[SkillInstall] chown failed (may be expected in dev): ${err.message}`);
688
+ }
689
+ let wrapperPath;
690
+ if (createWrapper) {
691
+ wrapperPath = path3.join(binDir, slug);
692
+ await fs2.mkdir(binDir, { recursive: true });
693
+ const wrapperContent = createWrapperContent(slug, skillDir);
694
+ await fs2.writeFile(wrapperPath, wrapperContent, { mode: 493 });
695
+ try {
696
+ execSync(`chown root:${socketGroup} "${wrapperPath}"`, { stdio: "pipe" });
697
+ execSync(`chmod 755 "${wrapperPath}"`, { stdio: "pipe" });
698
+ } catch (err) {
699
+ console.warn(`[SkillInstall] wrapper chown failed: ${err.message}`);
700
+ }
701
+ }
702
+ return {
703
+ success: true,
704
+ data: {
705
+ installed: true,
706
+ skillDir,
707
+ wrapperPath,
708
+ filesWritten
709
+ },
710
+ audit: {
711
+ duration: Date.now() - startTime,
712
+ bytesTransferred: files.reduce((sum, f) => sum + (f.content?.length || 0), 0)
713
+ }
714
+ };
715
+ } catch (error) {
716
+ return {
717
+ success: false,
718
+ error: { code: 1005, message: `Skill installation failed: ${error.message}` }
719
+ };
720
+ }
721
+ }
722
+ async function handleSkillUninstall(params, context, deps) {
723
+ const startTime = Date.now();
724
+ try {
725
+ const {
726
+ slug,
727
+ agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent",
728
+ removeWrapper = true
729
+ } = params;
730
+ if (!slug || !isValidSlug(slug)) {
731
+ return {
732
+ success: false,
733
+ error: { code: 1003, message: `Invalid skill slug: ${slug}` }
734
+ };
735
+ }
736
+ const skillsDir = path3.join(agentHome, ".openclaw", "skills");
737
+ const skillDir = path3.join(skillsDir, slug);
738
+ const binDir = path3.join(agentHome, "bin");
739
+ const wrapperPath = path3.join(binDir, slug);
740
+ let skillExists = false;
741
+ try {
742
+ await fs2.access(skillDir);
743
+ skillExists = true;
744
+ } catch {
745
+ }
746
+ if (skillExists) {
747
+ await fs2.rm(skillDir, { recursive: true, force: true });
748
+ }
749
+ let wrapperRemoved = false;
750
+ if (removeWrapper) {
751
+ try {
752
+ await fs2.access(wrapperPath);
753
+ await fs2.unlink(wrapperPath);
754
+ wrapperRemoved = true;
755
+ } catch {
756
+ }
757
+ }
758
+ return {
759
+ success: true,
760
+ data: {
761
+ uninstalled: true,
762
+ skillDir,
763
+ wrapperRemoved
764
+ },
765
+ audit: {
766
+ duration: Date.now() - startTime
767
+ }
768
+ };
769
+ } catch (error) {
770
+ return {
771
+ success: false,
772
+ error: { code: 1005, message: `Skill uninstallation failed: ${error.message}` }
773
+ };
774
+ }
775
+ }
776
+
777
+ // libs/shield-broker/src/server.ts
778
+ var UnixSocketServer = class {
779
+ server = null;
780
+ config;
781
+ policyEnforcer;
782
+ auditLogger;
783
+ secretVault;
784
+ connections = /* @__PURE__ */ new Set();
785
+ constructor(options) {
786
+ this.config = options.config;
787
+ this.policyEnforcer = options.policyEnforcer;
788
+ this.auditLogger = options.auditLogger;
789
+ this.secretVault = options.secretVault;
790
+ }
791
+ /**
792
+ * Start the Unix socket server
793
+ */
794
+ async start() {
795
+ if (fs3.existsSync(this.config.socketPath)) {
796
+ fs3.unlinkSync(this.config.socketPath);
797
+ }
798
+ return new Promise((resolve3, reject) => {
799
+ this.server = net.createServer((socket) => {
800
+ this.handleConnection(socket);
801
+ });
802
+ this.server.on("error", (error) => {
803
+ reject(error);
804
+ });
805
+ this.server.listen(this.config.socketPath, () => {
806
+ try {
807
+ fs3.chmodSync(this.config.socketPath, this.config.socketMode);
808
+ } catch (error) {
809
+ console.warn("Warning: Could not set socket permissions:", error);
810
+ }
811
+ resolve3();
812
+ });
813
+ });
814
+ }
815
+ /**
816
+ * Stop the Unix socket server
817
+ */
818
+ async stop() {
819
+ for (const socket of this.connections) {
820
+ socket.destroy();
821
+ }
822
+ this.connections.clear();
823
+ return new Promise((resolve3) => {
824
+ if (this.server) {
825
+ this.server.close(() => {
826
+ if (fs3.existsSync(this.config.socketPath)) {
827
+ try {
828
+ fs3.unlinkSync(this.config.socketPath);
829
+ } catch {
830
+ }
831
+ }
832
+ resolve3();
833
+ });
834
+ } else {
835
+ resolve3();
836
+ }
837
+ });
838
+ }
839
+ /**
840
+ * Handle a new client connection
841
+ */
842
+ handleConnection(socket) {
843
+ this.connections.add(socket);
844
+ let buffer = "";
845
+ socket.on("data", async (data) => {
846
+ buffer += data.toString();
847
+ let newlineIndex;
848
+ while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
849
+ const line = buffer.slice(0, newlineIndex);
850
+ buffer = buffer.slice(newlineIndex + 1);
851
+ if (line.trim()) {
852
+ const response = await this.processRequest(line, socket);
853
+ socket.write(JSON.stringify(response) + "\n");
854
+ }
855
+ }
856
+ });
857
+ socket.on("close", () => {
858
+ this.connections.delete(socket);
859
+ });
860
+ socket.on("error", (error) => {
861
+ console.error("Socket error:", error);
862
+ this.connections.delete(socket);
863
+ });
864
+ }
865
+ /**
866
+ * Process a JSON-RPC request
867
+ */
868
+ async processRequest(line, socket) {
869
+ const requestId = randomUUID();
870
+ const startTime = Date.now();
871
+ try {
872
+ let request;
873
+ try {
874
+ request = JSON.parse(line);
875
+ } catch {
876
+ return this.errorResponse(null, -32700, "Parse error");
877
+ }
878
+ if (request.jsonrpc !== "2.0" || !request.method || request.id === void 0) {
879
+ return this.errorResponse(request.id, -32600, "Invalid Request");
880
+ }
881
+ const context = {
882
+ requestId,
883
+ channel: "socket",
884
+ timestamp: /* @__PURE__ */ new Date(),
885
+ config: this.config
886
+ // Socket credentials would be extracted here on supported platforms
887
+ };
888
+ const policyResult = await this.policyEnforcer.check(
889
+ request.method,
890
+ request.params,
891
+ context
892
+ );
893
+ if (!policyResult.allowed) {
894
+ await this.auditLogger.log({
895
+ id: requestId,
896
+ timestamp: /* @__PURE__ */ new Date(),
897
+ operation: request.method,
898
+ channel: "socket",
899
+ allowed: false,
900
+ policyId: policyResult.policyId,
901
+ target: this.extractTarget(request),
902
+ result: "denied",
903
+ errorMessage: policyResult.reason,
904
+ durationMs: Date.now() - startTime
905
+ });
906
+ return this.errorResponse(request.id, 1001, policyResult.reason || "Policy denied");
907
+ }
908
+ const handler = this.getHandler(request.method);
909
+ if (!handler) {
910
+ return this.errorResponse(request.id, -32601, "Method not found");
911
+ }
912
+ const result = await handler(request.params, context, {
913
+ policyEnforcer: this.policyEnforcer,
914
+ auditLogger: this.auditLogger,
915
+ secretVault: this.secretVault
916
+ });
917
+ await this.auditLogger.log({
918
+ id: requestId,
919
+ timestamp: /* @__PURE__ */ new Date(),
920
+ operation: request.method,
921
+ channel: "socket",
922
+ allowed: true,
923
+ policyId: policyResult.policyId,
924
+ target: this.extractTarget(request),
925
+ result: result.success ? "success" : "error",
926
+ errorMessage: result.error?.message,
927
+ durationMs: Date.now() - startTime,
928
+ metadata: result.audit
929
+ });
930
+ if (result.success) {
931
+ return {
932
+ jsonrpc: "2.0",
933
+ id: request.id,
934
+ result: result.data
935
+ };
936
+ } else {
937
+ return this.errorResponse(
938
+ request.id,
939
+ result.error?.code || -32e3,
940
+ result.error?.message || "Unknown error"
941
+ );
942
+ }
943
+ } catch (error) {
944
+ console.error("Request processing error:", error);
945
+ return this.errorResponse(null, -32603, "Internal error");
946
+ }
947
+ }
948
+ /**
949
+ * Get the handler for an operation type
950
+ */
951
+ getHandler(method) {
952
+ const handlerMap = {
953
+ http_request: handleHttpRequest,
954
+ file_read: handleFileRead,
955
+ file_write: handleFileWrite,
956
+ file_list: handleFileList,
957
+ exec: handleExec,
958
+ open_url: handleOpenUrl,
959
+ secret_inject: handleSecretInject,
960
+ ping: handlePing,
961
+ skill_install: handleSkillInstall,
962
+ skill_uninstall: handleSkillUninstall
963
+ };
964
+ return handlerMap[method];
965
+ }
966
+ /**
967
+ * Extract target from request for audit logging
968
+ */
969
+ extractTarget(request) {
970
+ const params = request.params || {};
971
+ return params["url"] || params["path"] || params["command"] || params["name"] || request.method;
972
+ }
973
+ /**
974
+ * Create an error response
975
+ */
976
+ errorResponse(id, code, message) {
977
+ return {
978
+ jsonrpc: "2.0",
979
+ id: id ?? 0,
980
+ error: { code, message }
981
+ };
982
+ }
983
+ };
984
+
985
+ // libs/shield-broker/src/http-fallback.ts
986
+ import * as http from "node:http";
987
+ import { randomUUID as randomUUID2 } from "node:crypto";
988
+ var HTTP_ALLOWED_OPERATIONS = /* @__PURE__ */ new Set([
989
+ "http_request",
990
+ "file_read",
991
+ "file_list",
992
+ "open_url",
993
+ "ping"
994
+ ]);
995
+ var HTTP_DENIED_OPERATIONS = /* @__PURE__ */ new Set([
996
+ "exec",
997
+ "file_write",
998
+ "secret_inject"
999
+ ]);
1000
+ var HttpFallbackServer = class {
1001
+ server = null;
1002
+ config;
1003
+ policyEnforcer;
1004
+ auditLogger;
1005
+ constructor(options) {
1006
+ this.config = options.config;
1007
+ this.policyEnforcer = options.policyEnforcer;
1008
+ this.auditLogger = options.auditLogger;
1009
+ }
1010
+ /**
1011
+ * Start the HTTP fallback server
1012
+ */
1013
+ async start() {
1014
+ return new Promise((resolve3, reject) => {
1015
+ this.server = http.createServer((req, res) => {
1016
+ this.handleRequest(req, res);
1017
+ });
1018
+ this.server.on("error", (error) => {
1019
+ reject(error);
1020
+ });
1021
+ const listenHost = this.config.httpHost === "localhost" ? "127.0.0.1" : this.config.httpHost;
1022
+ this.server.listen(this.config.httpPort, listenHost, () => {
1023
+ resolve3();
1024
+ });
1025
+ });
1026
+ }
1027
+ /**
1028
+ * Stop the HTTP fallback server
1029
+ */
1030
+ async stop() {
1031
+ return new Promise((resolve3) => {
1032
+ if (this.server) {
1033
+ this.server.close(() => {
1034
+ resolve3();
1035
+ });
1036
+ } else {
1037
+ resolve3();
1038
+ }
1039
+ });
1040
+ }
1041
+ /**
1042
+ * Handle an HTTP request
1043
+ */
1044
+ async handleRequest(req, res) {
1045
+ if (req.method !== "POST" || req.url !== "/rpc") {
1046
+ if (req.method === "GET" && req.url === "/health") {
1047
+ res.writeHead(200, { "Content-Type": "application/json" });
1048
+ res.end(JSON.stringify({ status: "ok", version: "0.1.0" }));
1049
+ return;
1050
+ }
1051
+ res.writeHead(404, { "Content-Type": "application/json" });
1052
+ res.end(JSON.stringify({ error: "Not found" }));
1053
+ return;
1054
+ }
1055
+ const remoteAddr = req.socket.remoteAddress;
1056
+ if (!this.isLocalhost(remoteAddr)) {
1057
+ res.writeHead(403, { "Content-Type": "application/json" });
1058
+ res.end(JSON.stringify({ error: "Access denied: localhost only" }));
1059
+ return;
1060
+ }
1061
+ let body = "";
1062
+ for await (const chunk of req) {
1063
+ body += chunk;
1064
+ if (body.length > 10 * 1024 * 1024) {
1065
+ res.writeHead(413, { "Content-Type": "application/json" });
1066
+ res.end(JSON.stringify({ error: "Request too large" }));
1067
+ return;
1068
+ }
1069
+ }
1070
+ const response = await this.processRequest(body);
1071
+ res.writeHead(200, { "Content-Type": "application/json" });
1072
+ res.end(JSON.stringify(response));
1073
+ }
1074
+ /**
1075
+ * Check if address is localhost
1076
+ */
1077
+ isLocalhost(address) {
1078
+ if (!address) return false;
1079
+ return address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1" || address === "localhost";
1080
+ }
1081
+ /**
1082
+ * Process a JSON-RPC request
1083
+ */
1084
+ async processRequest(body) {
1085
+ const requestId = randomUUID2();
1086
+ const startTime = Date.now();
1087
+ try {
1088
+ let request;
1089
+ try {
1090
+ request = JSON.parse(body);
1091
+ } catch {
1092
+ return this.errorResponse(null, -32700, "Parse error");
1093
+ }
1094
+ if (request.jsonrpc !== "2.0" || !request.method || request.id === void 0) {
1095
+ return this.errorResponse(request.id, -32600, "Invalid Request");
1096
+ }
1097
+ if (HTTP_DENIED_OPERATIONS.has(request.method)) {
1098
+ await this.auditLogger.log({
1099
+ id: requestId,
1100
+ timestamp: /* @__PURE__ */ new Date(),
1101
+ operation: request.method,
1102
+ channel: "http",
1103
+ allowed: false,
1104
+ target: this.extractTarget(request),
1105
+ result: "denied",
1106
+ errorMessage: "Operation not allowed over HTTP fallback",
1107
+ durationMs: Date.now() - startTime
1108
+ });
1109
+ return this.errorResponse(
1110
+ request.id,
1111
+ 1008,
1112
+ `Operation '${request.method}' not allowed over HTTP. Use Unix socket.`
1113
+ );
1114
+ }
1115
+ if (!HTTP_ALLOWED_OPERATIONS.has(request.method)) {
1116
+ return this.errorResponse(request.id, -32601, "Method not found");
1117
+ }
1118
+ const context = {
1119
+ requestId,
1120
+ channel: "http",
1121
+ timestamp: /* @__PURE__ */ new Date(),
1122
+ config: this.config
1123
+ };
1124
+ const policyResult = await this.policyEnforcer.check(
1125
+ request.method,
1126
+ request.params,
1127
+ context
1128
+ );
1129
+ if (!policyResult.allowed) {
1130
+ await this.auditLogger.log({
1131
+ id: requestId,
1132
+ timestamp: /* @__PURE__ */ new Date(),
1133
+ operation: request.method,
1134
+ channel: "http",
1135
+ allowed: false,
1136
+ policyId: policyResult.policyId,
1137
+ target: this.extractTarget(request),
1138
+ result: "denied",
1139
+ errorMessage: policyResult.reason,
1140
+ durationMs: Date.now() - startTime
1141
+ });
1142
+ return this.errorResponse(request.id, 1001, policyResult.reason || "Policy denied");
1143
+ }
1144
+ const handler = this.getHandler(request.method);
1145
+ if (!handler) {
1146
+ return this.errorResponse(request.id, -32601, "Method not found");
1147
+ }
1148
+ const result = await handler(request.params, context, {
1149
+ policyEnforcer: this.policyEnforcer,
1150
+ auditLogger: this.auditLogger,
1151
+ secretVault: null
1152
+ // Not available over HTTP
1153
+ });
1154
+ await this.auditLogger.log({
1155
+ id: requestId,
1156
+ timestamp: /* @__PURE__ */ new Date(),
1157
+ operation: request.method,
1158
+ channel: "http",
1159
+ allowed: true,
1160
+ policyId: policyResult.policyId,
1161
+ target: this.extractTarget(request),
1162
+ result: result.success ? "success" : "error",
1163
+ errorMessage: result.error?.message,
1164
+ durationMs: Date.now() - startTime,
1165
+ metadata: result.audit
1166
+ });
1167
+ if (result.success) {
1168
+ return {
1169
+ jsonrpc: "2.0",
1170
+ id: request.id,
1171
+ result: result.data
1172
+ };
1173
+ } else {
1174
+ return this.errorResponse(
1175
+ request.id,
1176
+ result.error?.code || -32e3,
1177
+ result.error?.message || "Unknown error"
1178
+ );
1179
+ }
1180
+ } catch (error) {
1181
+ console.error("Request processing error:", error);
1182
+ return this.errorResponse(null, -32603, "Internal error");
1183
+ }
1184
+ }
1185
+ /**
1186
+ * Get the handler for an operation type
1187
+ */
1188
+ getHandler(method) {
1189
+ const handlerMap = {
1190
+ http_request: handleHttpRequest,
1191
+ file_read: handleFileRead,
1192
+ file_list: handleFileList,
1193
+ open_url: handleOpenUrl,
1194
+ ping: handlePing
1195
+ };
1196
+ return handlerMap[method];
1197
+ }
1198
+ /**
1199
+ * Extract target from request for audit logging
1200
+ */
1201
+ extractTarget(request) {
1202
+ const params = request.params || {};
1203
+ return params["url"] || params["path"] || params["command"] || params["name"] || request.method;
1204
+ }
1205
+ /**
1206
+ * Create an error response
1207
+ */
1208
+ errorResponse(id, code, message) {
1209
+ return {
1210
+ jsonrpc: "2.0",
1211
+ id: id ?? 0,
1212
+ error: { code, message }
1213
+ };
1214
+ }
1215
+ };
1216
+
1217
+ // libs/shield-broker/src/policies/enforcer.ts
1218
+ import * as fs4 from "node:fs";
1219
+ import * as path4 from "node:path";
1220
+ var PolicyEnforcer = class {
1221
+ policies;
1222
+ policiesPath;
1223
+ failOpen;
1224
+ lastLoad = 0;
1225
+ reloadInterval = 6e4;
1226
+ // 1 minute
1227
+ constructor(options) {
1228
+ this.policiesPath = options.policiesPath;
1229
+ this.failOpen = options.failOpen;
1230
+ this.policies = options.defaultPolicies;
1231
+ this.loadPolicies();
1232
+ }
1233
+ /**
1234
+ * Load policies from disk
1235
+ */
1236
+ loadPolicies() {
1237
+ const configFile = path4.join(this.policiesPath, "default.json");
1238
+ if (fs4.existsSync(configFile)) {
1239
+ try {
1240
+ const content = fs4.readFileSync(configFile, "utf-8");
1241
+ const loaded = JSON.parse(content);
1242
+ this.policies = {
1243
+ ...this.policies,
1244
+ ...loaded,
1245
+ rules: [...this.policies.rules, ...loaded.rules || []]
1246
+ };
1247
+ this.lastLoad = Date.now();
1248
+ } catch (error) {
1249
+ console.warn("Warning: Failed to load policies:", error);
1250
+ }
1251
+ }
1252
+ const customDir = path4.join(this.policiesPath, "custom");
1253
+ if (fs4.existsSync(customDir)) {
1254
+ try {
1255
+ const files = fs4.readdirSync(customDir);
1256
+ for (const file of files) {
1257
+ if (file.endsWith(".json")) {
1258
+ const content = fs4.readFileSync(path4.join(customDir, file), "utf-8");
1259
+ const custom = JSON.parse(content);
1260
+ if (custom.rules) {
1261
+ this.policies.rules.push(...custom.rules);
1262
+ }
1263
+ }
1264
+ }
1265
+ } catch (error) {
1266
+ console.warn("Warning: Failed to load custom policies:", error);
1267
+ }
1268
+ }
1269
+ this.policies.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
1270
+ }
1271
+ /**
1272
+ * Maybe reload policies if stale
1273
+ */
1274
+ maybeReload() {
1275
+ if (Date.now() - this.lastLoad > this.reloadInterval) {
1276
+ this.loadPolicies();
1277
+ }
1278
+ }
1279
+ /**
1280
+ * Check if an operation is allowed
1281
+ */
1282
+ async check(operation, params, context) {
1283
+ this.maybeReload();
1284
+ try {
1285
+ const target = this.extractTarget(operation, params);
1286
+ for (const rule of this.policies.rules) {
1287
+ if (!rule.enabled) continue;
1288
+ if (!rule.operations.includes(operation) && !rule.operations.includes("*")) {
1289
+ continue;
1290
+ }
1291
+ const matches = this.matchesPatterns(target, rule.patterns);
1292
+ if (matches) {
1293
+ if (rule.action === "deny" || rule.action === "approval") {
1294
+ return {
1295
+ allowed: false,
1296
+ policyId: rule.id,
1297
+ reason: `Denied by policy: ${rule.name}`
1298
+ };
1299
+ } else if (rule.action === "allow") {
1300
+ return {
1301
+ allowed: true,
1302
+ policyId: rule.id
1303
+ };
1304
+ }
1305
+ }
1306
+ }
1307
+ const constraintResult = this.checkConstraints(operation, params);
1308
+ if (!constraintResult.allowed) {
1309
+ return constraintResult;
1310
+ }
1311
+ return {
1312
+ allowed: this.policies.defaultAction === "allow",
1313
+ reason: this.policies.defaultAction === "deny" ? "No matching allow policy" : void 0
1314
+ };
1315
+ } catch (error) {
1316
+ console.error("Policy check error:", error);
1317
+ return {
1318
+ allowed: this.failOpen,
1319
+ reason: this.failOpen ? "Policy check failed, failing open" : "Policy check failed"
1320
+ };
1321
+ }
1322
+ }
1323
+ /**
1324
+ * Extract target from operation params
1325
+ */
1326
+ extractTarget(operation, params) {
1327
+ switch (operation) {
1328
+ case "http_request":
1329
+ return params["url"] || "";
1330
+ case "file_read":
1331
+ case "file_write":
1332
+ case "file_list":
1333
+ return params["path"] || "";
1334
+ case "exec":
1335
+ return `${params["command"] || ""} ${(params["args"] || []).join(" ")}`;
1336
+ case "open_url":
1337
+ return params["url"] || "";
1338
+ case "secret_inject":
1339
+ return params["name"] || "";
1340
+ default:
1341
+ return "";
1342
+ }
1343
+ }
1344
+ /**
1345
+ * Check if target matches any patterns
1346
+ */
1347
+ matchesPatterns(target, patterns) {
1348
+ for (const pattern of patterns) {
1349
+ if (this.matchPattern(target, pattern)) {
1350
+ return true;
1351
+ }
1352
+ }
1353
+ return false;
1354
+ }
1355
+ /**
1356
+ * Match a single pattern (supports glob-like matching)
1357
+ */
1358
+ matchPattern(target, pattern) {
1359
+ const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
1360
+ const regex = new RegExp(`^${regexPattern}$`, "i");
1361
+ return regex.test(target);
1362
+ }
1363
+ /**
1364
+ * Check operation-specific constraints
1365
+ */
1366
+ checkConstraints(operation, params) {
1367
+ if (["file_read", "file_write", "file_list"].includes(operation)) {
1368
+ const filePath = params["path"];
1369
+ if (filePath && this.policies.fsConstraints) {
1370
+ const { allowedPaths, deniedPatterns } = this.policies.fsConstraints;
1371
+ for (const pattern of deniedPatterns || []) {
1372
+ if (this.matchPattern(filePath, pattern)) {
1373
+ return {
1374
+ allowed: false,
1375
+ reason: `File path matches denied pattern: ${pattern}`
1376
+ };
1377
+ }
1378
+ }
1379
+ if (allowedPaths && allowedPaths.length > 0) {
1380
+ const isAllowed = allowedPaths.some(
1381
+ (allowed) => filePath.startsWith(allowed)
1382
+ );
1383
+ if (!isAllowed) {
1384
+ return {
1385
+ allowed: false,
1386
+ reason: "File path not in allowed directories"
1387
+ };
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+ if (operation === "http_request") {
1393
+ const url = params["url"];
1394
+ if (url && this.policies.networkConstraints) {
1395
+ try {
1396
+ const parsedUrl = new URL(url);
1397
+ const host = parsedUrl.hostname;
1398
+ const port = parseInt(parsedUrl.port) || (parsedUrl.protocol === "https:" ? 443 : 80);
1399
+ const { allowedHosts, deniedHosts, allowedPorts } = this.policies.networkConstraints;
1400
+ for (const pattern of deniedHosts || []) {
1401
+ if (pattern === "*" || this.matchPattern(host, pattern)) {
1402
+ const isAllowed = (allowedHosts || []).some(
1403
+ (allowed) => this.matchPattern(host, allowed)
1404
+ );
1405
+ if (!isAllowed) {
1406
+ return {
1407
+ allowed: false,
1408
+ reason: `Host '${host}' is not allowed`
1409
+ };
1410
+ }
1411
+ }
1412
+ }
1413
+ if (allowedPorts && allowedPorts.length > 0 && !allowedPorts.includes(port)) {
1414
+ return {
1415
+ allowed: false,
1416
+ reason: `Port ${port} is not in allowed ports`
1417
+ };
1418
+ }
1419
+ } catch {
1420
+ return {
1421
+ allowed: false,
1422
+ reason: "Invalid URL"
1423
+ };
1424
+ }
1425
+ }
1426
+ }
1427
+ if (operation === "exec") {
1428
+ const command = params["command"] || "";
1429
+ const args = params["args"] || [];
1430
+ const shellMetachars = /[;&|`$(){}[\]<>!\\]/;
1431
+ if (shellMetachars.test(command)) {
1432
+ return {
1433
+ allowed: false,
1434
+ reason: `Shell metacharacters not allowed in command: ${command}`
1435
+ };
1436
+ }
1437
+ for (const arg of args) {
1438
+ if (typeof arg === "string" && shellMetachars.test(arg) && !arg.startsWith("-")) {
1439
+ if (arg.includes("|") || arg.includes(";") || arg.includes("`") || arg.includes("$(")) {
1440
+ return {
1441
+ allowed: false,
1442
+ reason: `Suspicious argument rejected: ${arg}`
1443
+ };
1444
+ }
1445
+ }
1446
+ }
1447
+ }
1448
+ return { allowed: true };
1449
+ }
1450
+ /**
1451
+ * Get all configured policies
1452
+ */
1453
+ getPolicies() {
1454
+ this.maybeReload();
1455
+ return this.policies;
1456
+ }
1457
+ /**
1458
+ * Add a policy rule at runtime
1459
+ */
1460
+ addRule(rule) {
1461
+ this.policies.rules.push(rule);
1462
+ this.policies.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
1463
+ }
1464
+ /**
1465
+ * Remove a policy rule
1466
+ */
1467
+ removeRule(id) {
1468
+ const index = this.policies.rules.findIndex((r) => r.id === id);
1469
+ if (index >= 0) {
1470
+ this.policies.rules.splice(index, 1);
1471
+ return true;
1472
+ }
1473
+ return false;
1474
+ }
1475
+ };
1476
+
1477
+ // libs/shield-broker/src/policies/builtin.ts
1478
+ var BuiltinPolicies = [
1479
+ // Always allow ping (health check for broker availability)
1480
+ {
1481
+ id: "builtin-allow-ping",
1482
+ name: "Allow ping health checks",
1483
+ action: "allow",
1484
+ target: "command",
1485
+ operations: ["ping"],
1486
+ patterns: ["*"],
1487
+ enabled: true,
1488
+ priority: 1e3
1489
+ },
1490
+ // Allow skill installation/uninstallation (daemon management operations)
1491
+ {
1492
+ id: "builtin-allow-skill-management",
1493
+ name: "Allow skill management operations",
1494
+ action: "allow",
1495
+ target: "command",
1496
+ operations: ["skill_install", "skill_uninstall"],
1497
+ patterns: ["*"],
1498
+ enabled: true,
1499
+ priority: 1e3
1500
+ },
1501
+ // Allow localhost connections (for broker communication)
1502
+ {
1503
+ id: "builtin-allow-localhost",
1504
+ name: "Allow localhost connections",
1505
+ action: "allow",
1506
+ target: "url",
1507
+ operations: ["http_request"],
1508
+ patterns: [
1509
+ "http://localhost:*",
1510
+ "http://127.0.0.1:*",
1511
+ "https://localhost:*",
1512
+ "https://127.0.0.1:*"
1513
+ ],
1514
+ enabled: true,
1515
+ priority: 100
1516
+ },
1517
+ // Deny access to sensitive files
1518
+ {
1519
+ id: "builtin-deny-secrets",
1520
+ name: "Deny access to secret files",
1521
+ action: "deny",
1522
+ target: "command",
1523
+ operations: ["file_read", "file_write"],
1524
+ patterns: [
1525
+ "**/.env",
1526
+ "**/.env.*",
1527
+ "**/secrets.json",
1528
+ "**/secrets.yaml",
1529
+ "**/secrets.yml",
1530
+ "**/*.key",
1531
+ "**/*.pem",
1532
+ "**/*.p12",
1533
+ "**/id_rsa",
1534
+ "**/id_ed25519",
1535
+ "**/.ssh/*",
1536
+ "**/credentials.json",
1537
+ "**/service-account*.json"
1538
+ ],
1539
+ enabled: true,
1540
+ priority: 200
1541
+ },
1542
+ // Deny access to system files
1543
+ {
1544
+ id: "builtin-deny-system",
1545
+ name: "Deny access to system files",
1546
+ action: "deny",
1547
+ target: "command",
1548
+ operations: ["file_read", "file_write"],
1549
+ patterns: [
1550
+ "/etc/passwd",
1551
+ "/etc/shadow",
1552
+ "/etc/sudoers",
1553
+ "/etc/ssh/*",
1554
+ "/root/**",
1555
+ "/var/run/docker.sock"
1556
+ ],
1557
+ enabled: true,
1558
+ priority: 200
1559
+ },
1560
+ // Deny dangerous commands
1561
+ {
1562
+ id: "builtin-deny-dangerous-commands",
1563
+ name: "Deny dangerous commands",
1564
+ action: "deny",
1565
+ target: "command",
1566
+ operations: ["exec"],
1567
+ patterns: [
1568
+ "rm -rf /*",
1569
+ "rm -rf /",
1570
+ "dd if=*",
1571
+ "mkfs.*",
1572
+ "chmod -R 777 /*",
1573
+ "curl * | sh",
1574
+ "curl * | bash",
1575
+ "wget * | sh",
1576
+ "wget * | bash",
1577
+ "* > /dev/sda*",
1578
+ "shutdown*",
1579
+ "reboot*",
1580
+ "init 0",
1581
+ "init 6"
1582
+ ],
1583
+ enabled: true,
1584
+ priority: 300
1585
+ },
1586
+ // Deny network tools that bypass proxy
1587
+ {
1588
+ id: "builtin-deny-network-bypass",
1589
+ name: "Deny direct network tools",
1590
+ action: "deny",
1591
+ target: "command",
1592
+ operations: ["exec"],
1593
+ patterns: [
1594
+ "nc *",
1595
+ "netcat *",
1596
+ "ncat *",
1597
+ "socat *",
1598
+ "telnet *",
1599
+ "nmap *"
1600
+ ],
1601
+ enabled: true,
1602
+ priority: 150
1603
+ },
1604
+ // Allow common AI API endpoints
1605
+ {
1606
+ id: "builtin-allow-ai-apis",
1607
+ name: "Allow common AI API endpoints",
1608
+ action: "allow",
1609
+ target: "url",
1610
+ operations: ["http_request"],
1611
+ patterns: [
1612
+ "https://api.anthropic.com/**",
1613
+ "https://api.openai.com/**",
1614
+ "https://api.cohere.ai/**",
1615
+ "https://generativelanguage.googleapis.com/**",
1616
+ "https://api.mistral.ai/**"
1617
+ ],
1618
+ enabled: true,
1619
+ priority: 50
1620
+ },
1621
+ // Allow common package registries
1622
+ {
1623
+ id: "builtin-allow-registries",
1624
+ name: "Allow package registries",
1625
+ action: "allow",
1626
+ target: "url",
1627
+ operations: ["http_request"],
1628
+ patterns: [
1629
+ "https://registry.npmjs.org/**",
1630
+ "https://pypi.org/**",
1631
+ "https://files.pythonhosted.org/**",
1632
+ "https://crates.io/**",
1633
+ "https://rubygems.org/**"
1634
+ ],
1635
+ enabled: true,
1636
+ priority: 50
1637
+ },
1638
+ // Allow GitHub
1639
+ {
1640
+ id: "builtin-allow-github",
1641
+ name: "Allow GitHub",
1642
+ action: "allow",
1643
+ target: "url",
1644
+ operations: ["http_request"],
1645
+ patterns: [
1646
+ "https://github.com/**",
1647
+ "https://api.github.com/**",
1648
+ "https://raw.githubusercontent.com/**",
1649
+ "https://gist.github.com/**"
1650
+ ],
1651
+ enabled: true,
1652
+ priority: 50
1653
+ }
1654
+ ];
1655
+ function getDefaultPolicies() {
1656
+ return {
1657
+ version: "1.0.0",
1658
+ defaultAction: "deny",
1659
+ rules: [...BuiltinPolicies],
1660
+ fsConstraints: {
1661
+ allowedPaths: [
1662
+ "/Users/clawagent/workspace",
1663
+ "/tmp/agenshield"
1664
+ ],
1665
+ deniedPatterns: [
1666
+ "**/.env*",
1667
+ "**/secrets.*",
1668
+ "**/*.key",
1669
+ "**/*.pem"
1670
+ ]
1671
+ },
1672
+ networkConstraints: {
1673
+ allowedHosts: [
1674
+ "localhost",
1675
+ "127.0.0.1",
1676
+ "api.anthropic.com",
1677
+ "api.openai.com",
1678
+ "registry.npmjs.org",
1679
+ "pypi.org",
1680
+ "github.com",
1681
+ "api.github.com"
1682
+ ],
1683
+ deniedHosts: ["*"],
1684
+ allowedPorts: [80, 443, 5200]
1685
+ }
1686
+ };
1687
+ }
1688
+
1689
+ // libs/shield-broker/src/audit/logger.ts
1690
+ import * as fs5 from "node:fs";
1691
+ import * as path5 from "node:path";
1692
+ var AuditLogger = class {
1693
+ logPath;
1694
+ logLevel;
1695
+ maxFileSize;
1696
+ maxFiles;
1697
+ writeStream = null;
1698
+ currentSize = 0;
1699
+ levelPriority = {
1700
+ debug: 0,
1701
+ info: 1,
1702
+ warn: 2,
1703
+ error: 3
1704
+ };
1705
+ constructor(options) {
1706
+ this.logPath = options.logPath;
1707
+ this.logLevel = options.logLevel;
1708
+ this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024;
1709
+ this.maxFiles = options.maxFiles || 5;
1710
+ this.initializeStream();
1711
+ }
1712
+ /**
1713
+ * Initialize the write stream
1714
+ */
1715
+ initializeStream() {
1716
+ const dir = path5.dirname(this.logPath);
1717
+ if (!fs5.existsSync(dir)) {
1718
+ fs5.mkdirSync(dir, { recursive: true });
1719
+ }
1720
+ if (fs5.existsSync(this.logPath)) {
1721
+ const stats = fs5.statSync(this.logPath);
1722
+ this.currentSize = stats.size;
1723
+ }
1724
+ this.writeStream = fs5.createWriteStream(this.logPath, {
1725
+ flags: "a",
1726
+ encoding: "utf-8"
1727
+ });
1728
+ }
1729
+ /**
1730
+ * Rotate log files if needed
1731
+ */
1732
+ async maybeRotate() {
1733
+ if (this.currentSize < this.maxFileSize) {
1734
+ return;
1735
+ }
1736
+ if (this.writeStream) {
1737
+ this.writeStream.end();
1738
+ this.writeStream = null;
1739
+ }
1740
+ for (let i = this.maxFiles - 1; i >= 1; i--) {
1741
+ const oldPath = `${this.logPath}.${i}`;
1742
+ const newPath = `${this.logPath}.${i + 1}`;
1743
+ if (fs5.existsSync(oldPath)) {
1744
+ if (i === this.maxFiles - 1) {
1745
+ fs5.unlinkSync(oldPath);
1746
+ } else {
1747
+ fs5.renameSync(oldPath, newPath);
1748
+ }
1749
+ }
1750
+ }
1751
+ if (fs5.existsSync(this.logPath)) {
1752
+ fs5.renameSync(this.logPath, `${this.logPath}.1`);
1753
+ }
1754
+ this.currentSize = 0;
1755
+ this.initializeStream();
1756
+ }
1757
+ /**
1758
+ * Log an audit entry
1759
+ */
1760
+ async log(entry) {
1761
+ await this.maybeRotate();
1762
+ const logLine = JSON.stringify({
1763
+ ...entry,
1764
+ timestamp: entry.timestamp.toISOString()
1765
+ }) + "\n";
1766
+ if (this.writeStream) {
1767
+ this.writeStream.write(logLine);
1768
+ this.currentSize += Buffer.byteLength(logLine);
1769
+ }
1770
+ const level = entry.allowed ? "info" : "warn";
1771
+ if (this.shouldLog(level)) {
1772
+ const prefix = entry.allowed ? "\u2713" : "\u2717";
1773
+ const message = `[${entry.operation}] ${prefix} ${entry.target}`;
1774
+ if (level === "info") {
1775
+ console.info(message);
1776
+ } else {
1777
+ console.warn(message);
1778
+ }
1779
+ }
1780
+ }
1781
+ /**
1782
+ * Check if we should log at the given level
1783
+ */
1784
+ shouldLog(level) {
1785
+ return this.levelPriority[level] >= this.levelPriority[this.logLevel];
1786
+ }
1787
+ /**
1788
+ * Log a debug message
1789
+ */
1790
+ debug(message, data) {
1791
+ if (this.shouldLog("debug")) {
1792
+ console.debug(`[DEBUG] ${message}`, data || "");
1793
+ }
1794
+ }
1795
+ /**
1796
+ * Log an info message
1797
+ */
1798
+ info(message, data) {
1799
+ if (this.shouldLog("info")) {
1800
+ console.info(`[INFO] ${message}`, data || "");
1801
+ }
1802
+ }
1803
+ /**
1804
+ * Log a warning message
1805
+ */
1806
+ warn(message, data) {
1807
+ if (this.shouldLog("warn")) {
1808
+ console.warn(`[WARN] ${message}`, data || "");
1809
+ }
1810
+ }
1811
+ /**
1812
+ * Log an error message
1813
+ */
1814
+ error(message, data) {
1815
+ if (this.shouldLog("error")) {
1816
+ console.error(`[ERROR] ${message}`, data || "");
1817
+ }
1818
+ }
1819
+ /**
1820
+ * Query audit logs
1821
+ */
1822
+ async query(options) {
1823
+ const results = [];
1824
+ const limit = options.limit || 1e3;
1825
+ if (!fs5.existsSync(this.logPath)) {
1826
+ return results;
1827
+ }
1828
+ const content = fs5.readFileSync(this.logPath, "utf-8");
1829
+ const lines = content.trim().split("\n");
1830
+ for (const line of lines.reverse()) {
1831
+ if (results.length >= limit) break;
1832
+ try {
1833
+ const parsed = JSON.parse(line);
1834
+ const entry = {
1835
+ ...parsed,
1836
+ timestamp: new Date(parsed.timestamp)
1837
+ };
1838
+ if (options.startTime && entry.timestamp < options.startTime) continue;
1839
+ if (options.endTime && entry.timestamp > options.endTime) continue;
1840
+ if (options.operation && entry.operation !== options.operation) continue;
1841
+ if (options.allowed !== void 0 && entry.allowed !== options.allowed) continue;
1842
+ results.push(entry);
1843
+ } catch {
1844
+ }
1845
+ }
1846
+ return results;
1847
+ }
1848
+ /**
1849
+ * Close the logger
1850
+ */
1851
+ async close() {
1852
+ return new Promise((resolve3) => {
1853
+ if (this.writeStream) {
1854
+ this.writeStream.end(() => {
1855
+ this.writeStream = null;
1856
+ resolve3();
1857
+ });
1858
+ } else {
1859
+ resolve3();
1860
+ }
1861
+ });
1862
+ }
1863
+ };
1864
+
1865
+ // libs/shield-broker/src/secrets/vault.ts
1866
+ import * as fs6 from "node:fs/promises";
1867
+ import * as crypto from "node:crypto";
1868
+ var SecretVault = class {
1869
+ vaultPath;
1870
+ key = null;
1871
+ data = null;
1872
+ constructor(options) {
1873
+ this.vaultPath = options.vaultPath;
1874
+ }
1875
+ /**
1876
+ * Initialize the vault
1877
+ */
1878
+ async initialize() {
1879
+ this.key = await this.loadOrCreateKey();
1880
+ await this.load();
1881
+ }
1882
+ /**
1883
+ * Load or create the encryption key
1884
+ */
1885
+ async loadOrCreateKey() {
1886
+ const keyPath = this.vaultPath.replace(".enc", ".key");
1887
+ try {
1888
+ const keyData = await fs6.readFile(keyPath);
1889
+ return keyData;
1890
+ } catch {
1891
+ const key = crypto.randomBytes(32);
1892
+ await fs6.writeFile(keyPath, key, { mode: 384 });
1893
+ return key;
1894
+ }
1895
+ }
1896
+ /**
1897
+ * Load vault data from disk
1898
+ */
1899
+ async load() {
1900
+ try {
1901
+ const content = await fs6.readFile(this.vaultPath, "utf-8");
1902
+ this.data = JSON.parse(content);
1903
+ } catch {
1904
+ this.data = {
1905
+ version: "1.0.0",
1906
+ secrets: {}
1907
+ };
1908
+ }
1909
+ }
1910
+ /**
1911
+ * Save vault data to disk
1912
+ */
1913
+ async save() {
1914
+ if (!this.data) return;
1915
+ await fs6.writeFile(
1916
+ this.vaultPath,
1917
+ JSON.stringify(this.data, null, 2),
1918
+ { mode: 384 }
1919
+ );
1920
+ }
1921
+ /**
1922
+ * Encrypt a value
1923
+ */
1924
+ encrypt(value) {
1925
+ if (!this.key) {
1926
+ throw new Error("Vault not initialized");
1927
+ }
1928
+ const iv = crypto.randomBytes(12);
1929
+ const cipher = crypto.createCipheriv("aes-256-gcm", this.key, iv);
1930
+ let encrypted = cipher.update(value, "utf-8", "base64");
1931
+ encrypted += cipher.final("base64");
1932
+ const tag = cipher.getAuthTag();
1933
+ return {
1934
+ encrypted,
1935
+ iv: iv.toString("base64"),
1936
+ tag: tag.toString("base64")
1937
+ };
1938
+ }
1939
+ /**
1940
+ * Decrypt a value
1941
+ */
1942
+ decrypt(encrypted, iv, tag) {
1943
+ if (!this.key) {
1944
+ throw new Error("Vault not initialized");
1945
+ }
1946
+ const decipher = crypto.createDecipheriv(
1947
+ "aes-256-gcm",
1948
+ this.key,
1949
+ Buffer.from(iv, "base64")
1950
+ );
1951
+ decipher.setAuthTag(Buffer.from(tag, "base64"));
1952
+ let decrypted = decipher.update(encrypted, "base64", "utf-8");
1953
+ decrypted += decipher.final("utf-8");
1954
+ return decrypted;
1955
+ }
1956
+ /**
1957
+ * Get a secret by name
1958
+ */
1959
+ async get(name) {
1960
+ if (!this.data) {
1961
+ await this.initialize();
1962
+ }
1963
+ const entry = this.data.secrets[name];
1964
+ if (!entry) {
1965
+ return null;
1966
+ }
1967
+ try {
1968
+ const value = this.decrypt(entry.encrypted, entry.iv, entry.tag);
1969
+ entry.accessCount++;
1970
+ await this.save();
1971
+ return {
1972
+ name,
1973
+ value,
1974
+ createdAt: new Date(entry.createdAt),
1975
+ lastAccessedAt: /* @__PURE__ */ new Date(),
1976
+ accessCount: entry.accessCount
1977
+ };
1978
+ } catch (error) {
1979
+ console.error(`Failed to decrypt secret ${name}:`, error);
1980
+ return null;
1981
+ }
1982
+ }
1983
+ /**
1984
+ * Set a secret
1985
+ */
1986
+ async set(name, value) {
1987
+ if (!this.data) {
1988
+ await this.initialize();
1989
+ }
1990
+ const encrypted = this.encrypt(value);
1991
+ this.data.secrets[name] = {
1992
+ ...encrypted,
1993
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1994
+ accessCount: 0
1995
+ };
1996
+ await this.save();
1997
+ }
1998
+ /**
1999
+ * Delete a secret
2000
+ */
2001
+ async delete(name) {
2002
+ if (!this.data) {
2003
+ await this.initialize();
2004
+ }
2005
+ if (this.data.secrets[name]) {
2006
+ delete this.data.secrets[name];
2007
+ await this.save();
2008
+ return true;
2009
+ }
2010
+ return false;
2011
+ }
2012
+ /**
2013
+ * List all secret names
2014
+ */
2015
+ async list() {
2016
+ if (!this.data) {
2017
+ await this.initialize();
2018
+ }
2019
+ return Object.keys(this.data.secrets);
2020
+ }
2021
+ /**
2022
+ * Check if a secret exists
2023
+ */
2024
+ async has(name) {
2025
+ if (!this.data) {
2026
+ await this.initialize();
2027
+ }
2028
+ return name in this.data.secrets;
2029
+ }
2030
+ };
2031
+
2032
+ // libs/shield-broker/src/main.ts
2033
+ import * as fs7 from "node:fs";
2034
+ import * as path6 from "node:path";
2035
+ function loadConfig() {
2036
+ const configPath = process.env["AGENSHIELD_CONFIG"] || "/opt/agenshield/config/shield.json";
2037
+ let fileConfig = {};
2038
+ if (fs7.existsSync(configPath)) {
2039
+ try {
2040
+ const content = fs7.readFileSync(configPath, "utf-8");
2041
+ fileConfig = JSON.parse(content);
2042
+ } catch (error) {
2043
+ console.warn(`Warning: Failed to load config from ${configPath}:`, error);
2044
+ }
2045
+ }
2046
+ return {
2047
+ socketPath: process.env["AGENSHIELD_SOCKET"] || fileConfig.socketPath || "/var/run/agenshield/agenshield.sock",
2048
+ httpEnabled: process.env["AGENSHIELD_HTTP_ENABLED"] !== "false" && (fileConfig.httpEnabled ?? true),
2049
+ httpPort: parseInt(
2050
+ process.env["AGENSHIELD_HTTP_PORT"] || String(fileConfig.httpPort || 5201),
2051
+ 10
2052
+ ),
2053
+ httpHost: process.env["AGENSHIELD_HTTP_HOST"] || fileConfig.httpHost || "localhost",
2054
+ configPath,
2055
+ policiesPath: process.env["AGENSHIELD_POLICIES"] || fileConfig.policiesPath || "/opt/agenshield/policies",
2056
+ auditLogPath: process.env["AGENSHIELD_AUDIT_LOG"] || fileConfig.auditLogPath || "/var/log/agenshield/audit.log",
2057
+ logLevel: process.env["AGENSHIELD_LOG_LEVEL"] || fileConfig.logLevel || "info",
2058
+ failOpen: process.env["AGENSHIELD_FAIL_OPEN"] === "true" || (fileConfig.failOpen ?? false),
2059
+ socketMode: fileConfig.socketMode || 438,
2060
+ socketOwner: fileConfig.socketOwner || "clawbroker",
2061
+ socketGroup: fileConfig.socketGroup || "clawshield"
2062
+ };
2063
+ }
2064
+ function ensureDirectories(config) {
2065
+ const socketDir = path6.dirname(config.socketPath);
2066
+ const auditDir = path6.dirname(config.auditLogPath);
2067
+ for (const dir of [socketDir, auditDir, config.policiesPath]) {
2068
+ if (!fs7.existsSync(dir)) {
2069
+ try {
2070
+ fs7.mkdirSync(dir, { recursive: true, mode: 493 });
2071
+ } catch (error) {
2072
+ if (error.code !== "EEXIST") {
2073
+ console.warn(`Warning: Could not create directory ${dir}:`, error);
2074
+ }
2075
+ }
2076
+ }
2077
+ }
2078
+ }
2079
+ async function main() {
2080
+ console.log("AgenShield Broker v0.1.0");
2081
+ console.log("========================");
2082
+ const config = loadConfig();
2083
+ console.log(`Socket: ${config.socketPath}`);
2084
+ console.log(`HTTP Fallback: ${config.httpEnabled ? `${config.httpHost}:${config.httpPort}` : "disabled"}`);
2085
+ console.log(`Policies: ${config.policiesPath}`);
2086
+ console.log(`Log Level: ${config.logLevel}`);
2087
+ ensureDirectories(config);
2088
+ const auditLogger = new AuditLogger({
2089
+ logPath: config.auditLogPath,
2090
+ logLevel: config.logLevel
2091
+ });
2092
+ const policyEnforcer = new PolicyEnforcer({
2093
+ policiesPath: config.policiesPath,
2094
+ defaultPolicies: getDefaultPolicies(),
2095
+ failOpen: config.failOpen
2096
+ });
2097
+ const secretVault = new SecretVault({
2098
+ vaultPath: "/etc/agenshield/vault.enc"
2099
+ });
2100
+ const socketServer = new UnixSocketServer({
2101
+ config,
2102
+ policyEnforcer,
2103
+ auditLogger,
2104
+ secretVault
2105
+ });
2106
+ await socketServer.start();
2107
+ console.log(`Unix socket server listening on ${config.socketPath}`);
2108
+ let httpServer = null;
2109
+ if (config.httpEnabled) {
2110
+ httpServer = new HttpFallbackServer({
2111
+ config,
2112
+ policyEnforcer,
2113
+ auditLogger
2114
+ });
2115
+ await httpServer.start();
2116
+ console.log(`HTTP fallback server listening on ${config.httpHost}:${config.httpPort}`);
2117
+ }
2118
+ const shutdown = async (signal) => {
2119
+ console.log(`
2120
+ Received ${signal}, shutting down...`);
2121
+ await socketServer.stop();
2122
+ if (httpServer) {
2123
+ await httpServer.stop();
2124
+ }
2125
+ await auditLogger.close();
2126
+ console.log("Broker stopped.");
2127
+ process.exit(0);
2128
+ };
2129
+ process.on("SIGINT", () => shutdown("SIGINT"));
2130
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
2131
+ console.log("\nBroker is running. Press Ctrl+C to stop.");
2132
+ }
2133
+ main().catch((error) => {
2134
+ console.error("Fatal error:", error);
2135
+ process.exit(1);
2136
+ });