@agenshield/interceptor 0.6.1 → 0.7.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.
package/register.js CHANGED
@@ -34,9 +34,14 @@ function createConfig(overrides) {
34
34
  interceptFetch: env["AGENSHIELD_INTERCEPT_FETCH"] !== "false",
35
35
  interceptHttp: env["AGENSHIELD_INTERCEPT_HTTP"] !== "false",
36
36
  interceptWs: env["AGENSHIELD_INTERCEPT_WS"] !== "false",
37
- interceptFs: env["AGENSHIELD_INTERCEPT_FS"] !== "false",
37
+ interceptFs: false,
38
38
  interceptExec: env["AGENSHIELD_INTERCEPT_EXEC"] !== "false",
39
- timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "30000", 10),
39
+ timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "5000", 10),
40
+ contextType: env["AGENSHIELD_CONTEXT_TYPE"] || "agent",
41
+ contextSkillSlug: env["AGENSHIELD_SKILL_SLUG"],
42
+ contextAgentId: env["AGENSHIELD_AGENT_ID"],
43
+ enableSeatbelt: env["AGENSHIELD_SEATBELT"] !== "false" && process.platform === "darwin",
44
+ seatbeltProfileDir: env["AGENSHIELD_SEATBELT_DIR"] || "/tmp/agenshield-profiles",
40
45
  ...overrides
41
46
  };
42
47
  }
@@ -80,13 +85,34 @@ var TimeoutError = class extends AgenShieldError {
80
85
  // libs/shield-interceptor/src/debug-log.ts
81
86
  var fs = __toESM(require("node:fs"), 1);
82
87
  var _appendFileSync = fs.appendFileSync.bind(fs);
88
+ var _writeSync = fs.writeSync.bind(fs);
83
89
  var LOG_PATH = "/var/log/agenshield/interceptor.log";
90
+ var FALLBACK_LOG_PATH = "/tmp/agenshield-interceptor.log";
91
+ var resolvedLogPath = null;
92
+ function getLogPath() {
93
+ if (resolvedLogPath !== null) return resolvedLogPath;
94
+ try {
95
+ _appendFileSync(LOG_PATH, "");
96
+ resolvedLogPath = LOG_PATH;
97
+ } catch {
98
+ resolvedLogPath = FALLBACK_LOG_PATH;
99
+ }
100
+ return resolvedLogPath;
101
+ }
84
102
  function debugLog(msg) {
103
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
104
+ `;
85
105
  try {
86
- _appendFileSync(LOG_PATH, `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
87
- `);
106
+ _appendFileSync(getLogPath(), line);
88
107
  } catch {
89
108
  }
109
+ if (process.env["AGENSHIELD_LOG_LEVEL"] === "debug") {
110
+ try {
111
+ _writeSync(2, `[AgenShield:debug] ${msg}
112
+ `);
113
+ } catch {
114
+ }
115
+ }
90
116
  }
91
117
 
92
118
  // libs/shield-interceptor/src/interceptors/base.ts
@@ -96,6 +122,7 @@ var BaseInterceptor = class {
96
122
  eventReporter;
97
123
  failOpen;
98
124
  installed = false;
125
+ interceptorConfig;
99
126
  brokerHttpPort;
100
127
  constructor(options) {
101
128
  this.client = options.client;
@@ -103,6 +130,7 @@ var BaseInterceptor = class {
103
130
  this.eventReporter = options.eventReporter;
104
131
  this.failOpen = options.failOpen;
105
132
  this.brokerHttpPort = options.brokerHttpPort ?? 5201;
133
+ this.interceptorConfig = options.config;
106
134
  }
107
135
  /**
108
136
  * Check if a URL targets the broker or daemon (should not be intercepted)
@@ -127,15 +155,28 @@ var BaseInterceptor = class {
127
155
  isInstalled() {
128
156
  return this.installed;
129
157
  }
158
+ /**
159
+ * Build execution context from config
160
+ */
161
+ getBasePolicyExecutionContext() {
162
+ const config = this.interceptorConfig;
163
+ if (!config) return void 0;
164
+ return {
165
+ callerType: config.contextType || "agent",
166
+ skillSlug: config.contextSkillSlug,
167
+ agentId: config.contextAgentId,
168
+ depth: 0
169
+ };
170
+ }
130
171
  /**
131
172
  * Check policy and handle the result
132
173
  */
133
- async checkPolicy(operation, target) {
174
+ async checkPolicy(operation, target, context) {
134
175
  const startTime = Date.now();
135
176
  debugLog(`base.checkPolicy START op=${operation} target=${target}`);
136
177
  try {
137
178
  this.eventReporter.intercept(operation, target);
138
- const result = await this.policyEvaluator.check(operation, target);
179
+ const result = await this.policyEvaluator.check(operation, target, context);
139
180
  debugLog(`base.checkPolicy evaluator result op=${operation} target=${target} allowed=${result.allowed} policyId=${result.policyId}`);
140
181
  if (!result.allowed) {
141
182
  this.eventReporter.deny(operation, target, result.policyId, result.reason);
@@ -182,6 +223,19 @@ var FetchInterceptor = class extends BaseInterceptor {
182
223
  constructor(options) {
183
224
  super(options);
184
225
  }
226
+ /**
227
+ * Build execution context from config
228
+ */
229
+ getPolicyExecutionContext() {
230
+ const config = this.interceptorConfig;
231
+ if (!config) return void 0;
232
+ return {
233
+ callerType: config.contextType || "agent",
234
+ skillSlug: config.contextSkillSlug,
235
+ agentId: config.contextAgentId,
236
+ depth: 0
237
+ };
238
+ }
185
239
  install() {
186
240
  if (this.installed) return;
187
241
  this.originalFetch = globalThis.fetch;
@@ -212,48 +266,10 @@ var FetchInterceptor = class extends BaseInterceptor {
212
266
  return this.originalFetch(input, init);
213
267
  }
214
268
  debugLog(`fetch checkPolicy START url=${url}`);
215
- await this.checkPolicy("http_request", url);
216
- debugLog(`fetch checkPolicy DONE url=${url}`);
217
269
  try {
218
- const method = init?.method || "GET";
219
- const headers = {};
220
- if (init?.headers) {
221
- if (init.headers instanceof Headers) {
222
- init.headers.forEach((value, key) => {
223
- headers[key] = value;
224
- });
225
- } else if (Array.isArray(init.headers)) {
226
- for (const [key, value] of init.headers) {
227
- headers[key] = value;
228
- }
229
- } else {
230
- Object.assign(headers, init.headers);
231
- }
232
- }
233
- let body;
234
- if (init?.body) {
235
- if (typeof init.body === "string") {
236
- body = init.body;
237
- } else if (init.body instanceof ArrayBuffer) {
238
- body = Buffer.from(init.body).toString("base64");
239
- } else {
240
- body = String(init.body);
241
- }
242
- }
243
- const result = await this.client.request("http_request", {
244
- url,
245
- method,
246
- headers,
247
- body
248
- });
249
- const responseHeaders = new Headers(result.headers);
250
- return new Response(result.body, {
251
- status: result.status,
252
- statusText: result.statusText,
253
- headers: responseHeaders
254
- });
270
+ await this.checkPolicy("http_request", url, this.getPolicyExecutionContext());
271
+ debugLog(`fetch checkPolicy DONE url=${url}`);
255
272
  } catch (error) {
256
- debugLog(`fetch ERROR url=${url} error=${error.message}`);
257
273
  if (error.name === "PolicyDeniedError") {
258
274
  throw error;
259
275
  }
@@ -263,6 +279,7 @@ var FetchInterceptor = class extends BaseInterceptor {
263
279
  }
264
280
  throw error;
265
281
  }
282
+ return this.originalFetch(input, init);
266
283
  }
267
284
  };
268
285
 
@@ -407,6 +424,8 @@ var import_node_crypto = require("node:crypto");
407
424
  var _existsSync = fs2.existsSync.bind(fs2);
408
425
  var _readFileSync = fs2.readFileSync.bind(fs2);
409
426
  var _unlinkSync = fs2.unlinkSync.bind(fs2);
427
+ var _readdirSync = fs2.readdirSync.bind(fs2);
428
+ var _statSync = fs2.statSync.bind(fs2);
410
429
  var _spawnSync = import_node_child_process.spawnSync;
411
430
  var _execSync = import_node_child_process.execSync;
412
431
  var SyncClient = class {
@@ -414,22 +433,59 @@ var SyncClient = class {
414
433
  httpHost;
415
434
  httpPort;
416
435
  timeout;
436
+ socketFailCount = 0;
437
+ socketSkipUntil = 0;
417
438
  constructor(options) {
418
439
  this.socketPath = options.socketPath;
419
440
  this.httpHost = options.httpHost;
420
441
  this.httpPort = options.httpPort;
421
442
  this.timeout = options.timeout;
443
+ this.cleanupStaleTmpFiles();
444
+ }
445
+ /**
446
+ * Remove stale /tmp/agenshield-sync-*.json files from previous runs
447
+ */
448
+ cleanupStaleTmpFiles() {
449
+ try {
450
+ const tmpDir = "/tmp";
451
+ const files = _readdirSync(tmpDir);
452
+ const cutoff = Date.now() - 5 * 60 * 1e3;
453
+ for (const f of files) {
454
+ if (f.startsWith("agenshield-sync-") && f.endsWith(".json")) {
455
+ const fp = `${tmpDir}/${f}`;
456
+ try {
457
+ const stat = _statSync(fp);
458
+ if (stat.mtimeMs < cutoff) _unlinkSync(fp);
459
+ } catch {
460
+ }
461
+ }
462
+ }
463
+ } catch {
464
+ }
422
465
  }
423
466
  /**
424
467
  * Send a synchronous request to the broker
425
468
  */
426
469
  request(method, params) {
427
470
  debugLog(`syncClient.request START method=${method}`);
471
+ const now = Date.now();
472
+ if (now < this.socketSkipUntil) {
473
+ debugLog(`syncClient.request SKIP socket (circuit open for ${this.socketSkipUntil - now}ms), using HTTP`);
474
+ const result = this.httpRequestSync(method, params);
475
+ debugLog(`syncClient.request http OK method=${method}`);
476
+ return result;
477
+ }
428
478
  try {
429
479
  const result = this.socketRequestSync(method, params);
480
+ this.socketFailCount = 0;
430
481
  debugLog(`syncClient.request socket OK method=${method}`);
431
482
  return result;
432
483
  } catch (socketErr) {
484
+ this.socketFailCount++;
485
+ if (this.socketFailCount >= 2) {
486
+ this.socketSkipUntil = Date.now() + 6e4;
487
+ debugLog(`syncClient.request socket circuit OPEN (${this.socketFailCount} failures)`);
488
+ }
433
489
  debugLog(`syncClient.request socket FAILED: ${socketErr.message}, trying HTTP`);
434
490
  const result = this.httpRequestSync(method, params);
435
491
  debugLog(`syncClient.request http OK method=${method}`);
@@ -454,29 +510,40 @@ var SyncClient = class {
454
510
  const net = require('net');
455
511
  const fs = require('fs');
456
512
 
513
+ let done = false;
457
514
  const socket = net.createConnection('${this.socketPath}');
458
515
  let data = '';
459
516
 
517
+ const timer = setTimeout(() => {
518
+ if (done) return;
519
+ done = true;
520
+ socket.destroy();
521
+ fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
522
+ process.exit(1);
523
+ }, ${this.timeout});
524
+
460
525
  socket.on('connect', () => {
461
526
  socket.write(${JSON.stringify(request)});
462
527
  });
463
528
 
464
529
  socket.on('data', (chunk) => {
465
530
  data += chunk.toString();
466
- if (data.includes('\\n')) {
531
+ if (data.includes('\\n') && !done) {
532
+ done = true;
533
+ clearTimeout(timer);
467
534
  socket.end();
468
535
  fs.writeFileSync('${tmpFile}', data.split('\\n')[0]);
536
+ process.exit(0);
469
537
  }
470
538
  });
471
539
 
472
540
  socket.on('error', (err) => {
541
+ if (done) return;
542
+ done = true;
543
+ clearTimeout(timer);
473
544
  fs.writeFileSync('${tmpFile}', JSON.stringify({ error: err.message }));
545
+ process.exit(1);
474
546
  });
475
-
476
- setTimeout(() => {
477
- socket.destroy();
478
- fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
479
- }, ${this.timeout});
480
547
  `;
481
548
  try {
482
549
  debugLog(`syncClient.socketRequestSync _spawnSync START node-bin method=${method}`);
@@ -553,11 +620,227 @@ var SyncClient = class {
553
620
  }
554
621
  };
555
622
 
623
+ // libs/shield-interceptor/src/seatbelt/profile-manager.ts
624
+ var fs3 = __toESM(require("node:fs"), 1);
625
+ var crypto = __toESM(require("node:crypto"), 1);
626
+ var path = __toESM(require("node:path"), 1);
627
+ var _mkdirSync = fs3.mkdirSync.bind(fs3);
628
+ var _writeFileSync = fs3.writeFileSync.bind(fs3);
629
+ var _existsSync2 = fs3.existsSync.bind(fs3);
630
+ var _readFileSync2 = fs3.readFileSync.bind(fs3);
631
+ var _readdirSync2 = fs3.readdirSync.bind(fs3);
632
+ var _statSync2 = fs3.statSync.bind(fs3);
633
+ var _unlinkSync2 = fs3.unlinkSync.bind(fs3);
634
+ var _chmodSync = fs3.chmodSync.bind(fs3);
635
+ var ProfileManager = class {
636
+ profileDir;
637
+ ensuredDir = false;
638
+ constructor(profileDir) {
639
+ this.profileDir = profileDir;
640
+ }
641
+ /**
642
+ * Get or create a profile file on disk. Returns the absolute path.
643
+ * Uses content-hash naming so identical configs reuse the same file.
644
+ */
645
+ getOrCreateProfile(content) {
646
+ this.ensureDir();
647
+ const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
648
+ const profilePath = path.join(this.profileDir, `sb-${hash}.sb`);
649
+ if (!_existsSync2(profilePath)) {
650
+ debugLog(`profile-manager: writing new profile ${profilePath} (${content.length} bytes)`);
651
+ _writeFileSync(profilePath, content, { mode: 420 });
652
+ }
653
+ return profilePath;
654
+ }
655
+ /**
656
+ * Generate an SBPL profile from a SandboxConfig.
657
+ */
658
+ generateProfile(sandbox) {
659
+ if (sandbox.profileContent) {
660
+ return sandbox.profileContent;
661
+ }
662
+ const lines = [
663
+ ";; AgenShield dynamic seatbelt profile",
664
+ `;; Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
665
+ "(version 1)",
666
+ "(deny default)",
667
+ ""
668
+ ];
669
+ lines.push(
670
+ ";; Filesystem: reads allowed, writes restricted",
671
+ "(allow file-read*)",
672
+ ""
673
+ );
674
+ const writePaths = ["/tmp", "/private/tmp", "/var/folders"];
675
+ if (sandbox.allowedWritePaths.length > 0) {
676
+ writePaths.push(...sandbox.allowedWritePaths);
677
+ }
678
+ lines.push("(allow file-write*");
679
+ for (const p of writePaths) {
680
+ lines.push(` (subpath "${this.escapeSbpl(p)}")`);
681
+ }
682
+ lines.push(")");
683
+ lines.push("");
684
+ lines.push("(allow file-write*");
685
+ lines.push(' (literal "/dev/null")');
686
+ lines.push(' (literal "/dev/zero")');
687
+ lines.push(' (literal "/dev/random")');
688
+ lines.push(' (literal "/dev/urandom")');
689
+ lines.push(")");
690
+ lines.push("");
691
+ if (sandbox.deniedPaths.length > 0) {
692
+ lines.push(";; Denied paths");
693
+ for (const p of sandbox.deniedPaths) {
694
+ lines.push(`(deny file-read* file-write* (subpath "${this.escapeSbpl(p)}"))`);
695
+ }
696
+ lines.push("");
697
+ }
698
+ lines.push(";; Binary execution (system directories allowed as subpaths)");
699
+ lines.push("(allow process-exec");
700
+ lines.push(' (subpath "/bin")');
701
+ lines.push(' (subpath "/sbin")');
702
+ lines.push(' (subpath "/usr/bin")');
703
+ lines.push(' (subpath "/usr/sbin")');
704
+ lines.push(' (subpath "/usr/local/bin")');
705
+ lines.push(' (subpath "/opt/agenshield/bin")');
706
+ const coveredSubpaths = ["/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/", "/opt/agenshield/bin/"];
707
+ const home = process.env["HOME"];
708
+ if (home) {
709
+ lines.push(` (subpath "${this.escapeSbpl(home)}/bin")`);
710
+ lines.push(` (subpath "${this.escapeSbpl(home)}/homebrew")`);
711
+ coveredSubpaths.push(`${home}/bin/`, `${home}/homebrew/`);
712
+ }
713
+ const nvmDir = process.env["NVM_DIR"] || (home ? `${home}/.nvm` : null);
714
+ if (nvmDir) {
715
+ lines.push(` (subpath "${this.escapeSbpl(nvmDir)}")`);
716
+ coveredSubpaths.push(`${nvmDir}/`);
717
+ }
718
+ const brewPrefix = process.env["HOMEBREW_PREFIX"];
719
+ if (brewPrefix && (!home || !brewPrefix.startsWith(home))) {
720
+ lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/bin")`);
721
+ lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/lib")`);
722
+ coveredSubpaths.push(`${brewPrefix}/bin/`, `${brewPrefix}/lib/`);
723
+ }
724
+ const uniqueBinaries = [...new Set(sandbox.allowedBinaries)];
725
+ for (const bin of uniqueBinaries) {
726
+ if (coveredSubpaths.some((dir) => bin === dir || bin.startsWith(dir))) continue;
727
+ if (bin.endsWith("/")) {
728
+ lines.push(` (subpath "${this.escapeSbpl(bin)}")`);
729
+ } else {
730
+ lines.push(` (literal "${this.escapeSbpl(bin)}")`);
731
+ }
732
+ }
733
+ lines.push(")");
734
+ lines.push("");
735
+ const uniqueDenied = [...new Set(sandbox.deniedBinaries)];
736
+ if (uniqueDenied.length > 0) {
737
+ lines.push(";; Denied binaries");
738
+ for (const bin of uniqueDenied) {
739
+ lines.push(`(deny process-exec (literal "${this.escapeSbpl(bin)}"))`);
740
+ }
741
+ lines.push("");
742
+ }
743
+ lines.push(";; Network");
744
+ if (sandbox.networkAllowed) {
745
+ if (sandbox.allowedHosts.length > 0 || sandbox.allowedPorts.length > 0) {
746
+ lines.push(";; Allow specific network targets");
747
+ for (const host of sandbox.allowedHosts) {
748
+ lines.push(`(allow network-outbound (remote tcp "${this.escapeSbpl(host)}:*"))`);
749
+ }
750
+ for (const port of sandbox.allowedPorts) {
751
+ lines.push(`(allow network-outbound (remote tcp "*:${port}"))`);
752
+ }
753
+ const isLocalhostOnly = sandbox.allowedHosts.length > 0 && sandbox.allowedHosts.every((h) => h === "localhost" || h === "127.0.0.1");
754
+ if (!isLocalhostOnly) {
755
+ lines.push('(allow network-outbound (remote udp "*:53") (remote tcp "*:53"))');
756
+ }
757
+ } else {
758
+ lines.push("(allow network*)");
759
+ }
760
+ } else {
761
+ lines.push("(deny network*)");
762
+ }
763
+ lines.push("");
764
+ lines.push(
765
+ ";; Broker / local unix sockets",
766
+ "(allow network-outbound (remote unix))",
767
+ "(allow network-inbound (local unix))",
768
+ "(allow file-read* file-write*",
769
+ ' (subpath "/var/run/agenshield")',
770
+ ' (subpath "/private/var/run/agenshield"))',
771
+ ""
772
+ );
773
+ lines.push(
774
+ ";; Process management",
775
+ "(allow process-fork)",
776
+ "(allow signal (target self))",
777
+ "(allow sysctl-read)",
778
+ ""
779
+ );
780
+ lines.push(
781
+ ";; Mach IPC",
782
+ "(allow mach-lookup)",
783
+ ""
784
+ );
785
+ return lines.join("\n");
786
+ }
787
+ /**
788
+ * Remove stale profile files older than maxAgeMs.
789
+ */
790
+ cleanup(maxAgeMs) {
791
+ if (!_existsSync2(this.profileDir)) return;
792
+ try {
793
+ const now = Date.now();
794
+ const entries = _readdirSync2(this.profileDir);
795
+ for (const entry of entries) {
796
+ if (!entry.endsWith(".sb")) continue;
797
+ const filePath = path.join(this.profileDir, entry);
798
+ try {
799
+ const stat = _statSync2(filePath);
800
+ if (now - stat.mtimeMs > maxAgeMs) {
801
+ _unlinkSync2(filePath);
802
+ debugLog(`profile-manager: cleaned up stale profile ${filePath}`);
803
+ }
804
+ } catch {
805
+ }
806
+ }
807
+ } catch {
808
+ }
809
+ }
810
+ /**
811
+ * Escape a string for safe inclusion in SBPL
812
+ */
813
+ escapeSbpl(s) {
814
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
815
+ }
816
+ /**
817
+ * Ensure the profile directory exists
818
+ */
819
+ ensureDir() {
820
+ if (this.ensuredDir) return;
821
+ if (!_existsSync2(this.profileDir)) {
822
+ _mkdirSync(this.profileDir, { recursive: true, mode: 1023 });
823
+ } else {
824
+ try {
825
+ const stat = _statSync2(this.profileDir);
826
+ if ((stat.mode & 511) !== 511) {
827
+ _chmodSync(this.profileDir, 1023);
828
+ }
829
+ } catch {
830
+ }
831
+ }
832
+ this.ensuredDir = true;
833
+ }
834
+ };
835
+
556
836
  // libs/shield-interceptor/src/interceptors/child-process.ts
557
837
  var childProcessModule = require("node:child_process");
558
838
  var ChildProcessInterceptor = class extends BaseInterceptor {
559
839
  syncClient;
560
840
  _checking = false;
841
+ _executing = false;
842
+ // Guards exec→execFile re-entrancy
843
+ profileManager = null;
561
844
  originalExec = null;
562
845
  originalExecSync = null;
563
846
  originalSpawn = null;
@@ -566,13 +849,18 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
566
849
  originalFork = null;
567
850
  constructor(options) {
568
851
  super(options);
852
+ const config = this.interceptorConfig;
569
853
  this.syncClient = new SyncClient({
570
- socketPath: "/var/run/agenshield/agenshield.sock",
571
- httpHost: "localhost",
572
- httpPort: 5201,
573
- // Broker uses 5201
574
- timeout: 3e4
854
+ socketPath: config?.socketPath || "/var/run/agenshield/agenshield.sock",
855
+ httpHost: config?.httpHost || "localhost",
856
+ httpPort: config?.httpPort || 5201,
857
+ timeout: config?.timeout || 3e4
575
858
  });
859
+ if (config?.enableSeatbelt && process.platform === "darwin") {
860
+ this.profileManager = new ProfileManager(
861
+ config.seatbeltProfileDir || "/tmp/agenshield-profiles"
862
+ );
863
+ }
576
864
  }
577
865
  install() {
578
866
  if (this.installed) return;
@@ -606,64 +894,207 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
606
894
  this.originalFork = null;
607
895
  this.installed = false;
608
896
  }
897
+ /**
898
+ * Build execution context from config for RPC calls
899
+ */
900
+ getPolicyExecutionContext() {
901
+ const config = this.interceptorConfig;
902
+ return {
903
+ callerType: config?.contextType || "agent",
904
+ skillSlug: config?.contextSkillSlug,
905
+ agentId: config?.contextAgentId,
906
+ depth: 0
907
+ };
908
+ }
909
+ /**
910
+ * Synchronous policy check via SyncClient.
911
+ * Returns the full policy result (with sandbox config) or null if broker
912
+ * is unavailable and failOpen is true.
913
+ */
914
+ syncPolicyCheck(fullCommand) {
915
+ this._checking = true;
916
+ try {
917
+ debugLog(`cp.syncPolicyCheck START command=${fullCommand}`);
918
+ const context = this.getPolicyExecutionContext();
919
+ const result = this.syncClient.request(
920
+ "policy_check",
921
+ { operation: "exec", target: fullCommand, context }
922
+ );
923
+ debugLog(`cp.syncPolicyCheck DONE allowed=${result.allowed} command=${fullCommand}`);
924
+ if (!result.allowed) {
925
+ throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
926
+ operation: "exec",
927
+ target: fullCommand,
928
+ policyId: result.policyId
929
+ });
930
+ }
931
+ return result;
932
+ } catch (error) {
933
+ if (error instanceof PolicyDeniedError) {
934
+ throw error;
935
+ }
936
+ debugLog(`cp.syncPolicyCheck ERROR: ${error.message} command=${fullCommand}`);
937
+ if (!this.failOpen) {
938
+ throw error;
939
+ }
940
+ return null;
941
+ } finally {
942
+ this._checking = false;
943
+ }
944
+ }
945
+ /**
946
+ * Create a restrictive default sandbox config for fail-open scenarios.
947
+ * No network, minimal fs — better than running completely unsandboxed.
948
+ */
949
+ getFailOpenSandbox() {
950
+ return {
951
+ enabled: true,
952
+ allowedReadPaths: [],
953
+ allowedWritePaths: [],
954
+ deniedPaths: [],
955
+ networkAllowed: false,
956
+ allowedHosts: [],
957
+ allowedPorts: [],
958
+ allowedBinaries: [],
959
+ deniedBinaries: [],
960
+ envInjection: {},
961
+ envDeny: []
962
+ };
963
+ }
964
+ /**
965
+ * Resolve the sandbox config to use: from policy result, fail-open default, or null.
966
+ */
967
+ resolveSandbox(policyResult) {
968
+ if (policyResult?.sandbox?.enabled) {
969
+ return policyResult.sandbox;
970
+ }
971
+ if (policyResult === null && this.profileManager) {
972
+ return this.getFailOpenSandbox();
973
+ }
974
+ return null;
975
+ }
976
+ /**
977
+ * Wrap a command with sandbox-exec if seatbelt is enabled and sandbox config is present.
978
+ * Returns modified { command, args, options } for spawn-style calls.
979
+ */
980
+ wrapWithSeatbelt(command, args, options, policyResult) {
981
+ const sandbox = this.resolveSandbox(policyResult);
982
+ if (!this.profileManager || !sandbox || process.platform !== "darwin") {
983
+ return { command, args, options };
984
+ }
985
+ if (command === "/opt/agenshield/bin/node-bin" || command.endsWith("/node-bin")) {
986
+ debugLog(`cp.wrapWithSeatbelt: SKIP node-bin (already intercepted) command=${command}`);
987
+ return { command, args, options };
988
+ }
989
+ if (command === "/usr/bin/sandbox-exec" || command.endsWith("/sandbox-exec")) {
990
+ debugLog(`cp.wrapWithSeatbelt: SKIP already sandbox-exec command=${command}`);
991
+ return { command, args, options };
992
+ }
993
+ debugLog(`cp.wrapWithSeatbelt: wrapping command=${command}`);
994
+ const profileContent = this.profileManager.generateProfile(sandbox);
995
+ const profilePath = this.profileManager.getOrCreateProfile(profileContent);
996
+ const env = { ...options?.env || process.env };
997
+ if (sandbox.envInjection) {
998
+ Object.assign(env, sandbox.envInjection);
999
+ }
1000
+ if (sandbox.envDeny) {
1001
+ for (const key of sandbox.envDeny) {
1002
+ delete env[key];
1003
+ }
1004
+ }
1005
+ return {
1006
+ command: "/usr/bin/sandbox-exec",
1007
+ args: ["-f", profilePath, command, ...args],
1008
+ options: { ...options, env }
1009
+ };
1010
+ }
1011
+ /**
1012
+ * Wrap a shell command string with sandbox-exec.
1013
+ * For exec/execSync which take a full command string.
1014
+ */
1015
+ wrapCommandStringWithSeatbelt(command, options, policyResult) {
1016
+ const sandbox = this.resolveSandbox(policyResult);
1017
+ if (!this.profileManager || !sandbox || process.platform !== "darwin") {
1018
+ return { command, options };
1019
+ }
1020
+ if (command.startsWith("/usr/bin/sandbox-exec ") || command.startsWith("sandbox-exec ")) {
1021
+ debugLog(`cp.wrapCommandStringWithSeatbelt: SKIP already sandbox-exec command=${command}`);
1022
+ return { command, options };
1023
+ }
1024
+ debugLog(`cp.wrapCommandStringWithSeatbelt: wrapping command=${command}`);
1025
+ const profileContent = this.profileManager.generateProfile(sandbox);
1026
+ const profilePath = this.profileManager.getOrCreateProfile(profileContent);
1027
+ const env = { ...options?.env || process.env };
1028
+ if (sandbox.envInjection) {
1029
+ Object.assign(env, sandbox.envInjection);
1030
+ }
1031
+ if (sandbox.envDeny) {
1032
+ for (const key of sandbox.envDeny) {
1033
+ delete env[key];
1034
+ }
1035
+ }
1036
+ return {
1037
+ command: `/usr/bin/sandbox-exec -f ${profilePath} ${command}`,
1038
+ options: { ...options, env }
1039
+ };
1040
+ }
609
1041
  createInterceptedExec() {
610
1042
  const self = this;
611
1043
  const original = this.originalExec;
612
1044
  return function interceptedExec(command, ...args) {
613
1045
  const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
614
- debugLog(`cp.exec ENTER command=${command} _checking=${self._checking}`);
615
- if (self._checking) {
1046
+ const options = args[0];
1047
+ debugLog(`cp.exec ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
1048
+ if (self._checking || self._executing) {
616
1049
  debugLog(`cp.exec SKIP (re-entrancy) command=${command}`);
617
1050
  return original(command, ...args, callback);
618
1051
  }
619
1052
  self.eventReporter.intercept("exec", command);
620
- self.checkPolicy("exec", command).then(() => {
621
- original(command, ...args, callback);
622
- }).catch((error) => {
1053
+ let policyResult = null;
1054
+ try {
1055
+ policyResult = self.syncPolicyCheck(command);
1056
+ } catch (error) {
623
1057
  if (callback) {
624
- callback(error, "", "");
1058
+ process.nextTick(() => callback(error, "", ""));
625
1059
  }
626
- });
627
- return original('echo ""');
1060
+ return original('echo ""');
1061
+ }
1062
+ const wrapped = self.wrapCommandStringWithSeatbelt(command, options, policyResult);
1063
+ debugLog(`cp.exec calling original command=${wrapped.command}`);
1064
+ self._executing = true;
1065
+ try {
1066
+ if (wrapped.options) {
1067
+ return original(wrapped.command, wrapped.options, callback);
1068
+ }
1069
+ return original(wrapped.command, callback);
1070
+ } finally {
1071
+ self._executing = false;
1072
+ }
628
1073
  };
629
1074
  }
630
1075
  createInterceptedExecSync() {
631
1076
  const self = this;
632
1077
  const original = this.originalExecSync;
633
1078
  const interceptedExecSync = function(command, options) {
634
- debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking}`);
635
- if (self._checking) {
1079
+ debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
1080
+ if (self._checking || self._executing) {
636
1081
  debugLog(`cp.execSync SKIP (re-entrancy) command=${command}`);
637
1082
  return original(command, options);
638
1083
  }
639
- self._checking = true;
1084
+ self.eventReporter.intercept("exec", command);
1085
+ const policyResult = self.syncPolicyCheck(command);
1086
+ const wrapped = self.wrapCommandStringWithSeatbelt(
1087
+ command,
1088
+ options,
1089
+ policyResult
1090
+ );
1091
+ debugLog(`cp.execSync calling original command=${wrapped.command}`);
1092
+ self._executing = true;
640
1093
  try {
641
- self.eventReporter.intercept("exec", command);
642
- debugLog(`cp.execSync policy_check START command=${command}`);
643
- const result = self.syncClient.request(
644
- "policy_check",
645
- { operation: "exec", target: command }
646
- );
647
- debugLog(`cp.execSync policy_check DONE allowed=${result.allowed} command=${command}`);
648
- if (!result.allowed) {
649
- throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
650
- operation: "exec",
651
- target: command
652
- });
653
- }
654
- } catch (error) {
655
- debugLog(`cp.execSync policy_check ERROR: ${error.message} command=${command}`);
656
- if (error instanceof PolicyDeniedError) {
657
- throw error;
658
- }
659
- if (!self.failOpen) {
660
- throw error;
661
- }
1094
+ return original(wrapped.command, wrapped.options);
662
1095
  } finally {
663
- self._checking = false;
1096
+ self._executing = false;
664
1097
  }
665
- debugLog(`cp.execSync calling original command=${command}`);
666
- return original(command, options);
667
1098
  };
668
1099
  return interceptedExecSync;
669
1100
  }
@@ -672,17 +1103,31 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
672
1103
  const original = this.originalSpawn;
673
1104
  const interceptedSpawn = function(command, args, options) {
674
1105
  const fullCmd = args ? `${command} ${args.join(" ")}` : command;
675
- debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking}`);
676
- if (self._checking) {
1106
+ debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking} _executing=${self._executing}`);
1107
+ if (self._checking || self._executing) {
677
1108
  debugLog(`cp.spawn SKIP (re-entrancy) command=${fullCmd}`);
678
1109
  return original(command, args, options || {});
679
1110
  }
680
- const fullCommand = args ? `${command} ${args.join(" ")}` : command;
681
- self.eventReporter.intercept("exec", fullCommand);
682
- self.checkPolicy("exec", fullCommand).catch((error) => {
683
- self.eventReporter.error("exec", fullCommand, error.message);
684
- });
685
- return original(command, args, options || {});
1111
+ self.eventReporter.intercept("exec", fullCmd);
1112
+ let policyResult = null;
1113
+ try {
1114
+ policyResult = self.syncPolicyCheck(fullCmd);
1115
+ } catch (error) {
1116
+ debugLog(`cp.spawn DENIED command=${fullCmd}`);
1117
+ const denied = original("false", [], { stdio: "pipe" });
1118
+ process.nextTick(() => {
1119
+ denied.emit("error", error);
1120
+ });
1121
+ return denied;
1122
+ }
1123
+ const wrapped = self.wrapWithSeatbelt(
1124
+ command,
1125
+ Array.from(args || []),
1126
+ options,
1127
+ policyResult
1128
+ );
1129
+ debugLog(`cp.spawn calling original command=${wrapped.command} args=${wrapped.args.join(" ")}`);
1130
+ return original(wrapped.command, wrapped.args, wrapped.options || {});
686
1131
  };
687
1132
  return interceptedSpawn;
688
1133
  }
@@ -691,77 +1136,130 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
691
1136
  const original = this.originalSpawnSync;
692
1137
  return function interceptedSpawnSync(command, args, options) {
693
1138
  const fullCommand = args ? `${command} ${args.join(" ")}` : command;
694
- debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking}`);
695
- if (self._checking) {
1139
+ debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking} _executing=${self._executing}`);
1140
+ if (self._checking || self._executing) {
696
1141
  debugLog(`cp.spawnSync SKIP (re-entrancy) command=${fullCommand}`);
697
1142
  return original(command, args, options);
698
1143
  }
699
- self._checking = true;
1144
+ self.eventReporter.intercept("exec", fullCommand);
1145
+ let policyResult = null;
700
1146
  try {
701
- self.eventReporter.intercept("exec", fullCommand);
702
- debugLog(`cp.spawnSync policy_check START command=${fullCommand}`);
703
- const result = self.syncClient.request(
704
- "policy_check",
705
- { operation: "exec", target: fullCommand }
706
- );
707
- debugLog(`cp.spawnSync policy_check DONE allowed=${result.allowed} command=${fullCommand}`);
708
- if (!result.allowed) {
709
- return {
710
- pid: -1,
711
- output: [],
712
- stdout: Buffer.alloc(0),
713
- stderr: Buffer.from(result.reason || "Policy denied"),
714
- status: 1,
715
- signal: null,
716
- error: new PolicyDeniedError(result.reason || "Policy denied")
717
- };
718
- }
1147
+ policyResult = self.syncPolicyCheck(fullCommand);
719
1148
  } catch (error) {
720
- debugLog(`cp.spawnSync policy_check ERROR: ${error.message} command=${fullCommand}`);
721
- if (!self.failOpen) {
722
- return {
723
- pid: -1,
724
- output: [],
725
- stdout: Buffer.alloc(0),
726
- stderr: Buffer.from(error.message),
727
- status: 1,
728
- signal: null,
729
- error
730
- };
731
- }
732
- } finally {
733
- self._checking = false;
1149
+ debugLog(`cp.spawnSync DENIED command=${fullCommand}`);
1150
+ return {
1151
+ pid: -1,
1152
+ output: [],
1153
+ stdout: Buffer.alloc(0),
1154
+ stderr: Buffer.from(
1155
+ error instanceof PolicyDeniedError ? error.message || "Policy denied" : error.message
1156
+ ),
1157
+ status: 1,
1158
+ signal: null,
1159
+ error
1160
+ };
734
1161
  }
735
- debugLog(`cp.spawnSync calling original command=${fullCommand}`);
736
- return original(command, args, options);
1162
+ const wrapped = self.wrapWithSeatbelt(
1163
+ command,
1164
+ Array.from(args || []),
1165
+ options,
1166
+ policyResult
1167
+ );
1168
+ debugLog(`cp.spawnSync calling original command=${wrapped.command}`);
1169
+ return original(
1170
+ wrapped.command,
1171
+ wrapped.args,
1172
+ wrapped.options
1173
+ );
737
1174
  };
738
1175
  }
739
1176
  createInterceptedExecFile() {
740
1177
  const self = this;
741
1178
  const original = this.originalExecFile;
742
- return function interceptedExecFile(file, ...args) {
743
- if (self._checking) {
744
- return original(file, ...args);
1179
+ return function interceptedExecFile(file, ...rest) {
1180
+ if (self._checking || self._executing) {
1181
+ return original(file, ...rest);
745
1182
  }
746
- self.eventReporter.intercept("exec", file);
747
- self.checkPolicy("exec", file).catch((error) => {
748
- self.eventReporter.error("exec", file, error.message);
749
- });
750
- return original(file, ...args);
1183
+ let args = [];
1184
+ let options;
1185
+ let callback;
1186
+ for (const arg of rest) {
1187
+ if (typeof arg === "function") {
1188
+ callback = arg;
1189
+ } else if (Array.isArray(arg)) {
1190
+ args = arg;
1191
+ } else if (typeof arg === "object" && arg !== null) {
1192
+ options = arg;
1193
+ }
1194
+ }
1195
+ const fullCommand = args.length > 0 ? `${file} ${args.join(" ")}` : file;
1196
+ debugLog(`cp.execFile ENTER command=${fullCommand}`);
1197
+ self.eventReporter.intercept("exec", fullCommand);
1198
+ let policyResult = null;
1199
+ try {
1200
+ policyResult = self.syncPolicyCheck(fullCommand);
1201
+ } catch (error) {
1202
+ if (callback) {
1203
+ process.nextTick(() => callback(error, "", ""));
1204
+ }
1205
+ return original("false");
1206
+ }
1207
+ const wrapped = self.wrapWithSeatbelt(file, args, options, policyResult);
1208
+ debugLog(`cp.execFile calling original command=${wrapped.command}`);
1209
+ if (callback) {
1210
+ return original(
1211
+ wrapped.command,
1212
+ wrapped.args,
1213
+ wrapped.options,
1214
+ callback
1215
+ );
1216
+ }
1217
+ return original(
1218
+ wrapped.command,
1219
+ wrapped.args,
1220
+ wrapped.options || {},
1221
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1222
+ () => {
1223
+ }
1224
+ );
751
1225
  };
752
1226
  }
753
1227
  createInterceptedFork() {
754
1228
  const self = this;
755
1229
  const original = this.originalFork;
756
1230
  const interceptedFork = function(modulePath, args, options) {
757
- if (self._checking) {
1231
+ if (self._checking || self._executing) {
758
1232
  return original(modulePath, args, options);
759
1233
  }
760
1234
  const pathStr = modulePath.toString();
761
- self.eventReporter.intercept("exec", `fork:${pathStr}`);
762
- self.checkPolicy("exec", `fork:${pathStr}`).catch((error) => {
763
- self.eventReporter.error("exec", pathStr, error.message);
764
- });
1235
+ const fullCommand = `fork:${pathStr}`;
1236
+ debugLog(`cp.fork ENTER command=${fullCommand}`);
1237
+ self.eventReporter.intercept("exec", fullCommand);
1238
+ let policyResult = null;
1239
+ try {
1240
+ policyResult = self.syncPolicyCheck(fullCommand);
1241
+ } catch (error) {
1242
+ debugLog(`cp.fork DENIED command=${fullCommand}`);
1243
+ const denied = self.originalSpawn("false", [], { stdio: "pipe" });
1244
+ process.nextTick(() => {
1245
+ denied.emit("error", error);
1246
+ });
1247
+ return denied;
1248
+ }
1249
+ if (policyResult?.sandbox) {
1250
+ const sandbox = policyResult.sandbox;
1251
+ const env = { ...options?.env || process.env };
1252
+ if (sandbox.envInjection) {
1253
+ Object.assign(env, sandbox.envInjection);
1254
+ }
1255
+ if (sandbox.envDeny) {
1256
+ for (const key of sandbox.envDeny) {
1257
+ delete env[key];
1258
+ }
1259
+ }
1260
+ options = { ...options, env };
1261
+ }
1262
+ debugLog(`cp.fork calling original module=${pathStr}`);
765
1263
  return original(modulePath, args, options);
766
1264
  };
767
1265
  return interceptedFork;
@@ -855,19 +1353,19 @@ var FsInterceptor = class extends BaseInterceptor {
855
1353
  const key = `fs:${methodName}`;
856
1354
  this.originals.set(key, original);
857
1355
  const self = this;
858
- safeOverride(module2, methodName, function intercepted(path, ...args) {
859
- const pathString = normalizePathArg(path);
1356
+ safeOverride(module2, methodName, function intercepted(path2, ...args) {
1357
+ const pathString = normalizePathArg(path2);
860
1358
  const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
861
1359
  debugLog(`fs.${methodName} ENTER (async) path=${pathString} _checking=${self._checking}`);
862
1360
  if (self._checking) {
863
1361
  debugLog(`fs.${methodName} SKIP (re-entrancy, async) path=${pathString}`);
864
- original.call(module2, path, ...args, callback);
1362
+ original.call(module2, path2, ...args, callback);
865
1363
  return;
866
1364
  }
867
1365
  self.eventReporter.intercept(operation, pathString);
868
1366
  self.checkPolicy(operation, pathString).then(() => {
869
1367
  debugLog(`fs.${methodName} policy OK (async) path=${pathString}`);
870
- original.call(module2, path, ...args, callback);
1368
+ original.call(module2, path2, ...args, callback);
871
1369
  }).catch((error) => {
872
1370
  debugLog(`fs.${methodName} policy ERROR (async): ${error.message} path=${pathString}`);
873
1371
  if (callback) {
@@ -882,12 +1380,12 @@ var FsInterceptor = class extends BaseInterceptor {
882
1380
  const key = `fs:${methodName}`;
883
1381
  this.originals.set(key, original);
884
1382
  const self = this;
885
- safeOverride(module2, methodName, function interceptedSync(path, ...args) {
886
- const pathString = normalizePathArg(path);
1383
+ safeOverride(module2, methodName, function interceptedSync(path2, ...args) {
1384
+ const pathString = normalizePathArg(path2);
887
1385
  debugLog(`fs.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
888
1386
  if (self._checking) {
889
1387
  debugLog(`fs.${methodName} SKIP (re-entrancy) path=${pathString}`);
890
- return original.call(module2, path, ...args);
1388
+ return original.call(module2, path2, ...args);
891
1389
  }
892
1390
  self._checking = true;
893
1391
  try {
@@ -916,7 +1414,7 @@ var FsInterceptor = class extends BaseInterceptor {
916
1414
  self._checking = false;
917
1415
  }
918
1416
  debugLog(`fs.${methodName} calling original path=${pathString}`);
919
- return original.call(module2, path, ...args);
1417
+ return original.call(module2, path2, ...args);
920
1418
  });
921
1419
  }
922
1420
  interceptPromiseMethod(module2, methodName, operation) {
@@ -925,17 +1423,17 @@ var FsInterceptor = class extends BaseInterceptor {
925
1423
  const key = `fsPromises:${methodName}`;
926
1424
  this.originals.set(key, original);
927
1425
  const self = this;
928
- safeOverride(module2, methodName, async function interceptedPromise(path, ...args) {
929
- const pathString = normalizePathArg(path);
1426
+ safeOverride(module2, methodName, async function interceptedPromise(path2, ...args) {
1427
+ const pathString = normalizePathArg(path2);
930
1428
  debugLog(`fsPromises.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
931
1429
  if (self._checking) {
932
1430
  debugLog(`fsPromises.${methodName} SKIP (re-entrancy) path=${pathString}`);
933
- return original.call(module2, path, ...args);
1431
+ return original.call(module2, path2, ...args);
934
1432
  }
935
1433
  self.eventReporter.intercept(operation, pathString);
936
1434
  await self.checkPolicy(operation, pathString);
937
1435
  debugLog(`fsPromises.${methodName} policy OK path=${pathString}`);
938
- return original.call(module2, path, ...args);
1436
+ return original.call(module2, path2, ...args);
939
1437
  });
940
1438
  }
941
1439
  };
@@ -1079,11 +1577,11 @@ var PolicyEvaluator = class {
1079
1577
  * Check if an operation is allowed
1080
1578
  * Always queries the daemon for fresh policy decisions
1081
1579
  */
1082
- async check(operation, target) {
1580
+ async check(operation, target, context) {
1083
1581
  try {
1084
1582
  const result = await this.client.request(
1085
1583
  "policy_check",
1086
- { operation, target }
1584
+ { operation, target, context }
1087
1585
  );
1088
1586
  return result;
1089
1587
  } catch (error) {
@@ -1127,7 +1625,11 @@ var EventReporter = class _EventReporter {
1127
1625
  const level = this.getLogLevel(event);
1128
1626
  if (this.shouldLog(level)) {
1129
1627
  const prefix = event.type === "allow" ? "\u2713" : event.type === "deny" ? "\u2717" : "\u2022";
1130
- console[level](`[AgenShield] ${prefix} ${event.operation}: ${event.target}`);
1628
+ let detail = `${prefix} ${event.operation}: ${event.target}`;
1629
+ if (event.policyId) detail += ` [policy:${event.policyId}]`;
1630
+ if (event.error) detail += ` [reason:${event.error}]`;
1631
+ if (event.duration) detail += ` [${event.duration}ms]`;
1632
+ console[level](`[AgenShield] ${detail}`);
1131
1633
  }
1132
1634
  if (this.queue.length >= 100) {
1133
1635
  this.flush();
@@ -1247,6 +1749,13 @@ function installInterceptors(configOverrides) {
1247
1749
  return;
1248
1750
  }
1249
1751
  const config = createConfig(configOverrides);
1752
+ if (config.logLevel === "debug") {
1753
+ try {
1754
+ const safeConfig = { ...config };
1755
+ console.error("[AgenShield:config]", JSON.stringify(safeConfig, null, 2));
1756
+ } catch {
1757
+ }
1758
+ }
1250
1759
  client = new AsyncClient({
1251
1760
  socketPath: config.socketPath,
1252
1761
  httpHost: config.httpHost,
@@ -1267,7 +1776,8 @@ function installInterceptors(configOverrides) {
1267
1776
  policyEvaluator,
1268
1777
  eventReporter,
1269
1778
  failOpen: config.failOpen,
1270
- brokerHttpPort: config.httpPort
1779
+ brokerHttpPort: config.httpPort,
1780
+ config
1271
1781
  });
1272
1782
  installed.fetch.install();
1273
1783
  log(config, "debug", "Installed fetch interceptor");
@@ -1300,7 +1810,8 @@ function installInterceptors(configOverrides) {
1300
1810
  policyEvaluator,
1301
1811
  eventReporter,
1302
1812
  failOpen: config.failOpen,
1303
- brokerHttpPort: config.httpPort
1813
+ brokerHttpPort: config.httpPort,
1814
+ config
1304
1815
  });
1305
1816
  installed.childProcess.install();
1306
1817
  log(config, "debug", "Installed child_process interceptor");