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