@agenshield/interceptor 0.6.2 → 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/index.js CHANGED
@@ -61,7 +61,12 @@ function createConfig(overrides) {
61
61
  interceptWs: env["AGENSHIELD_INTERCEPT_WS"] !== "false",
62
62
  interceptFs: false,
63
63
  interceptExec: env["AGENSHIELD_INTERCEPT_EXEC"] !== "false",
64
- timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "30000", 10),
64
+ timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "5000", 10),
65
+ contextType: env["AGENSHIELD_CONTEXT_TYPE"] || "agent",
66
+ contextSkillSlug: env["AGENSHIELD_SKILL_SLUG"],
67
+ contextAgentId: env["AGENSHIELD_AGENT_ID"],
68
+ enableSeatbelt: env["AGENSHIELD_SEATBELT"] !== "false" && process.platform === "darwin",
69
+ seatbeltProfileDir: env["AGENSHIELD_SEATBELT_DIR"] || "/tmp/agenshield-profiles",
65
70
  ...overrides
66
71
  };
67
72
  }
@@ -105,13 +110,34 @@ var TimeoutError = class extends AgenShieldError {
105
110
  // libs/shield-interceptor/src/debug-log.ts
106
111
  var fs = __toESM(require("node:fs"), 1);
107
112
  var _appendFileSync = fs.appendFileSync.bind(fs);
113
+ var _writeSync = fs.writeSync.bind(fs);
108
114
  var LOG_PATH = "/var/log/agenshield/interceptor.log";
115
+ var FALLBACK_LOG_PATH = "/tmp/agenshield-interceptor.log";
116
+ var resolvedLogPath = null;
117
+ function getLogPath() {
118
+ if (resolvedLogPath !== null) return resolvedLogPath;
119
+ try {
120
+ _appendFileSync(LOG_PATH, "");
121
+ resolvedLogPath = LOG_PATH;
122
+ } catch {
123
+ resolvedLogPath = FALLBACK_LOG_PATH;
124
+ }
125
+ return resolvedLogPath;
126
+ }
109
127
  function debugLog(msg) {
128
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
129
+ `;
110
130
  try {
111
- _appendFileSync(LOG_PATH, `[${(/* @__PURE__ */ new Date()).toISOString()}] [pid:${process.pid}] ${msg}
112
- `);
131
+ _appendFileSync(getLogPath(), line);
113
132
  } catch {
114
133
  }
134
+ if (process.env["AGENSHIELD_LOG_LEVEL"] === "debug") {
135
+ try {
136
+ _writeSync(2, `[AgenShield:debug] ${msg}
137
+ `);
138
+ } catch {
139
+ }
140
+ }
115
141
  }
116
142
 
117
143
  // libs/shield-interceptor/src/interceptors/base.ts
@@ -121,6 +147,7 @@ var BaseInterceptor = class {
121
147
  eventReporter;
122
148
  failOpen;
123
149
  installed = false;
150
+ interceptorConfig;
124
151
  brokerHttpPort;
125
152
  constructor(options) {
126
153
  this.client = options.client;
@@ -128,6 +155,7 @@ var BaseInterceptor = class {
128
155
  this.eventReporter = options.eventReporter;
129
156
  this.failOpen = options.failOpen;
130
157
  this.brokerHttpPort = options.brokerHttpPort ?? 5201;
158
+ this.interceptorConfig = options.config;
131
159
  }
132
160
  /**
133
161
  * Check if a URL targets the broker or daemon (should not be intercepted)
@@ -152,15 +180,28 @@ var BaseInterceptor = class {
152
180
  isInstalled() {
153
181
  return this.installed;
154
182
  }
183
+ /**
184
+ * Build execution context from config
185
+ */
186
+ getBasePolicyExecutionContext() {
187
+ const config = this.interceptorConfig;
188
+ if (!config) return void 0;
189
+ return {
190
+ callerType: config.contextType || "agent",
191
+ skillSlug: config.contextSkillSlug,
192
+ agentId: config.contextAgentId,
193
+ depth: 0
194
+ };
195
+ }
155
196
  /**
156
197
  * Check policy and handle the result
157
198
  */
158
- async checkPolicy(operation, target) {
199
+ async checkPolicy(operation, target, context) {
159
200
  const startTime = Date.now();
160
201
  debugLog(`base.checkPolicy START op=${operation} target=${target}`);
161
202
  try {
162
203
  this.eventReporter.intercept(operation, target);
163
- const result = await this.policyEvaluator.check(operation, target);
204
+ const result = await this.policyEvaluator.check(operation, target, context);
164
205
  debugLog(`base.checkPolicy evaluator result op=${operation} target=${target} allowed=${result.allowed} policyId=${result.policyId}`);
165
206
  if (!result.allowed) {
166
207
  this.eventReporter.deny(operation, target, result.policyId, result.reason);
@@ -207,6 +248,19 @@ var FetchInterceptor = class extends BaseInterceptor {
207
248
  constructor(options) {
208
249
  super(options);
209
250
  }
251
+ /**
252
+ * Build execution context from config
253
+ */
254
+ getPolicyExecutionContext() {
255
+ const config = this.interceptorConfig;
256
+ if (!config) return void 0;
257
+ return {
258
+ callerType: config.contextType || "agent",
259
+ skillSlug: config.contextSkillSlug,
260
+ agentId: config.contextAgentId,
261
+ depth: 0
262
+ };
263
+ }
210
264
  install() {
211
265
  if (this.installed) return;
212
266
  this.originalFetch = globalThis.fetch;
@@ -237,48 +291,10 @@ var FetchInterceptor = class extends BaseInterceptor {
237
291
  return this.originalFetch(input, init);
238
292
  }
239
293
  debugLog(`fetch checkPolicy START url=${url}`);
240
- await this.checkPolicy("http_request", url);
241
- debugLog(`fetch checkPolicy DONE url=${url}`);
242
294
  try {
243
- const method = init?.method || "GET";
244
- const headers = {};
245
- if (init?.headers) {
246
- if (init.headers instanceof Headers) {
247
- init.headers.forEach((value, key) => {
248
- headers[key] = value;
249
- });
250
- } else if (Array.isArray(init.headers)) {
251
- for (const [key, value] of init.headers) {
252
- headers[key] = value;
253
- }
254
- } else {
255
- Object.assign(headers, init.headers);
256
- }
257
- }
258
- let body;
259
- if (init?.body) {
260
- if (typeof init.body === "string") {
261
- body = init.body;
262
- } else if (init.body instanceof ArrayBuffer) {
263
- body = Buffer.from(init.body).toString("base64");
264
- } else {
265
- body = String(init.body);
266
- }
267
- }
268
- const result = await this.client.request("http_request", {
269
- url,
270
- method,
271
- headers,
272
- body
273
- });
274
- const responseHeaders = new Headers(result.headers);
275
- return new Response(result.body, {
276
- status: result.status,
277
- statusText: result.statusText,
278
- headers: responseHeaders
279
- });
295
+ await this.checkPolicy("http_request", url, this.getPolicyExecutionContext());
296
+ debugLog(`fetch checkPolicy DONE url=${url}`);
280
297
  } catch (error) {
281
- debugLog(`fetch ERROR url=${url} error=${error.message}`);
282
298
  if (error.name === "PolicyDeniedError") {
283
299
  throw error;
284
300
  }
@@ -288,6 +304,7 @@ var FetchInterceptor = class extends BaseInterceptor {
288
304
  }
289
305
  throw error;
290
306
  }
307
+ return this.originalFetch(input, init);
291
308
  }
292
309
  };
293
310
 
@@ -432,6 +449,8 @@ var import_node_crypto = require("node:crypto");
432
449
  var _existsSync = fs2.existsSync.bind(fs2);
433
450
  var _readFileSync = fs2.readFileSync.bind(fs2);
434
451
  var _unlinkSync = fs2.unlinkSync.bind(fs2);
452
+ var _readdirSync = fs2.readdirSync.bind(fs2);
453
+ var _statSync = fs2.statSync.bind(fs2);
435
454
  var _spawnSync = import_node_child_process.spawnSync;
436
455
  var _execSync = import_node_child_process.execSync;
437
456
  var SyncClient = class {
@@ -439,22 +458,59 @@ var SyncClient = class {
439
458
  httpHost;
440
459
  httpPort;
441
460
  timeout;
461
+ socketFailCount = 0;
462
+ socketSkipUntil = 0;
442
463
  constructor(options) {
443
464
  this.socketPath = options.socketPath;
444
465
  this.httpHost = options.httpHost;
445
466
  this.httpPort = options.httpPort;
446
467
  this.timeout = options.timeout;
468
+ this.cleanupStaleTmpFiles();
469
+ }
470
+ /**
471
+ * Remove stale /tmp/agenshield-sync-*.json files from previous runs
472
+ */
473
+ cleanupStaleTmpFiles() {
474
+ try {
475
+ const tmpDir = "/tmp";
476
+ const files = _readdirSync(tmpDir);
477
+ const cutoff = Date.now() - 5 * 60 * 1e3;
478
+ for (const f of files) {
479
+ if (f.startsWith("agenshield-sync-") && f.endsWith(".json")) {
480
+ const fp = `${tmpDir}/${f}`;
481
+ try {
482
+ const stat = _statSync(fp);
483
+ if (stat.mtimeMs < cutoff) _unlinkSync(fp);
484
+ } catch {
485
+ }
486
+ }
487
+ }
488
+ } catch {
489
+ }
447
490
  }
448
491
  /**
449
492
  * Send a synchronous request to the broker
450
493
  */
451
494
  request(method, params) {
452
495
  debugLog(`syncClient.request START method=${method}`);
496
+ const now = Date.now();
497
+ if (now < this.socketSkipUntil) {
498
+ debugLog(`syncClient.request SKIP socket (circuit open for ${this.socketSkipUntil - now}ms), using HTTP`);
499
+ const result = this.httpRequestSync(method, params);
500
+ debugLog(`syncClient.request http OK method=${method}`);
501
+ return result;
502
+ }
453
503
  try {
454
504
  const result = this.socketRequestSync(method, params);
505
+ this.socketFailCount = 0;
455
506
  debugLog(`syncClient.request socket OK method=${method}`);
456
507
  return result;
457
508
  } catch (socketErr) {
509
+ this.socketFailCount++;
510
+ if (this.socketFailCount >= 2) {
511
+ this.socketSkipUntil = Date.now() + 6e4;
512
+ debugLog(`syncClient.request socket circuit OPEN (${this.socketFailCount} failures)`);
513
+ }
458
514
  debugLog(`syncClient.request socket FAILED: ${socketErr.message}, trying HTTP`);
459
515
  const result = this.httpRequestSync(method, params);
460
516
  debugLog(`syncClient.request http OK method=${method}`);
@@ -479,29 +535,40 @@ var SyncClient = class {
479
535
  const net = require('net');
480
536
  const fs = require('fs');
481
537
 
538
+ let done = false;
482
539
  const socket = net.createConnection('${this.socketPath}');
483
540
  let data = '';
484
541
 
542
+ const timer = setTimeout(() => {
543
+ if (done) return;
544
+ done = true;
545
+ socket.destroy();
546
+ fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
547
+ process.exit(1);
548
+ }, ${this.timeout});
549
+
485
550
  socket.on('connect', () => {
486
551
  socket.write(${JSON.stringify(request)});
487
552
  });
488
553
 
489
554
  socket.on('data', (chunk) => {
490
555
  data += chunk.toString();
491
- if (data.includes('\\n')) {
556
+ if (data.includes('\\n') && !done) {
557
+ done = true;
558
+ clearTimeout(timer);
492
559
  socket.end();
493
560
  fs.writeFileSync('${tmpFile}', data.split('\\n')[0]);
561
+ process.exit(0);
494
562
  }
495
563
  });
496
564
 
497
565
  socket.on('error', (err) => {
566
+ if (done) return;
567
+ done = true;
568
+ clearTimeout(timer);
498
569
  fs.writeFileSync('${tmpFile}', JSON.stringify({ error: err.message }));
570
+ process.exit(1);
499
571
  });
500
-
501
- setTimeout(() => {
502
- socket.destroy();
503
- fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
504
- }, ${this.timeout});
505
572
  `;
506
573
  try {
507
574
  debugLog(`syncClient.socketRequestSync _spawnSync START node-bin method=${method}`);
@@ -578,11 +645,227 @@ var SyncClient = class {
578
645
  }
579
646
  };
580
647
 
648
+ // libs/shield-interceptor/src/seatbelt/profile-manager.ts
649
+ var fs3 = __toESM(require("node:fs"), 1);
650
+ var crypto = __toESM(require("node:crypto"), 1);
651
+ var path = __toESM(require("node:path"), 1);
652
+ var _mkdirSync = fs3.mkdirSync.bind(fs3);
653
+ var _writeFileSync = fs3.writeFileSync.bind(fs3);
654
+ var _existsSync2 = fs3.existsSync.bind(fs3);
655
+ var _readFileSync2 = fs3.readFileSync.bind(fs3);
656
+ var _readdirSync2 = fs3.readdirSync.bind(fs3);
657
+ var _statSync2 = fs3.statSync.bind(fs3);
658
+ var _unlinkSync2 = fs3.unlinkSync.bind(fs3);
659
+ var _chmodSync = fs3.chmodSync.bind(fs3);
660
+ var ProfileManager = class {
661
+ profileDir;
662
+ ensuredDir = false;
663
+ constructor(profileDir) {
664
+ this.profileDir = profileDir;
665
+ }
666
+ /**
667
+ * Get or create a profile file on disk. Returns the absolute path.
668
+ * Uses content-hash naming so identical configs reuse the same file.
669
+ */
670
+ getOrCreateProfile(content) {
671
+ this.ensureDir();
672
+ const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
673
+ const profilePath = path.join(this.profileDir, `sb-${hash}.sb`);
674
+ if (!_existsSync2(profilePath)) {
675
+ debugLog(`profile-manager: writing new profile ${profilePath} (${content.length} bytes)`);
676
+ _writeFileSync(profilePath, content, { mode: 420 });
677
+ }
678
+ return profilePath;
679
+ }
680
+ /**
681
+ * Generate an SBPL profile from a SandboxConfig.
682
+ */
683
+ generateProfile(sandbox) {
684
+ if (sandbox.profileContent) {
685
+ return sandbox.profileContent;
686
+ }
687
+ const lines = [
688
+ ";; AgenShield dynamic seatbelt profile",
689
+ `;; Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
690
+ "(version 1)",
691
+ "(deny default)",
692
+ ""
693
+ ];
694
+ lines.push(
695
+ ";; Filesystem: reads allowed, writes restricted",
696
+ "(allow file-read*)",
697
+ ""
698
+ );
699
+ const writePaths = ["/tmp", "/private/tmp", "/var/folders"];
700
+ if (sandbox.allowedWritePaths.length > 0) {
701
+ writePaths.push(...sandbox.allowedWritePaths);
702
+ }
703
+ lines.push("(allow file-write*");
704
+ for (const p of writePaths) {
705
+ lines.push(` (subpath "${this.escapeSbpl(p)}")`);
706
+ }
707
+ lines.push(")");
708
+ lines.push("");
709
+ lines.push("(allow file-write*");
710
+ lines.push(' (literal "/dev/null")');
711
+ lines.push(' (literal "/dev/zero")');
712
+ lines.push(' (literal "/dev/random")');
713
+ lines.push(' (literal "/dev/urandom")');
714
+ lines.push(")");
715
+ lines.push("");
716
+ if (sandbox.deniedPaths.length > 0) {
717
+ lines.push(";; Denied paths");
718
+ for (const p of sandbox.deniedPaths) {
719
+ lines.push(`(deny file-read* file-write* (subpath "${this.escapeSbpl(p)}"))`);
720
+ }
721
+ lines.push("");
722
+ }
723
+ lines.push(";; Binary execution (system directories allowed as subpaths)");
724
+ lines.push("(allow process-exec");
725
+ lines.push(' (subpath "/bin")');
726
+ lines.push(' (subpath "/sbin")');
727
+ lines.push(' (subpath "/usr/bin")');
728
+ lines.push(' (subpath "/usr/sbin")');
729
+ lines.push(' (subpath "/usr/local/bin")');
730
+ lines.push(' (subpath "/opt/agenshield/bin")');
731
+ const coveredSubpaths = ["/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/", "/opt/agenshield/bin/"];
732
+ const home = process.env["HOME"];
733
+ if (home) {
734
+ lines.push(` (subpath "${this.escapeSbpl(home)}/bin")`);
735
+ lines.push(` (subpath "${this.escapeSbpl(home)}/homebrew")`);
736
+ coveredSubpaths.push(`${home}/bin/`, `${home}/homebrew/`);
737
+ }
738
+ const nvmDir = process.env["NVM_DIR"] || (home ? `${home}/.nvm` : null);
739
+ if (nvmDir) {
740
+ lines.push(` (subpath "${this.escapeSbpl(nvmDir)}")`);
741
+ coveredSubpaths.push(`${nvmDir}/`);
742
+ }
743
+ const brewPrefix = process.env["HOMEBREW_PREFIX"];
744
+ if (brewPrefix && (!home || !brewPrefix.startsWith(home))) {
745
+ lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/bin")`);
746
+ lines.push(` (subpath "${this.escapeSbpl(brewPrefix)}/lib")`);
747
+ coveredSubpaths.push(`${brewPrefix}/bin/`, `${brewPrefix}/lib/`);
748
+ }
749
+ const uniqueBinaries = [...new Set(sandbox.allowedBinaries)];
750
+ for (const bin of uniqueBinaries) {
751
+ if (coveredSubpaths.some((dir) => bin === dir || bin.startsWith(dir))) continue;
752
+ if (bin.endsWith("/")) {
753
+ lines.push(` (subpath "${this.escapeSbpl(bin)}")`);
754
+ } else {
755
+ lines.push(` (literal "${this.escapeSbpl(bin)}")`);
756
+ }
757
+ }
758
+ lines.push(")");
759
+ lines.push("");
760
+ const uniqueDenied = [...new Set(sandbox.deniedBinaries)];
761
+ if (uniqueDenied.length > 0) {
762
+ lines.push(";; Denied binaries");
763
+ for (const bin of uniqueDenied) {
764
+ lines.push(`(deny process-exec (literal "${this.escapeSbpl(bin)}"))`);
765
+ }
766
+ lines.push("");
767
+ }
768
+ lines.push(";; Network");
769
+ if (sandbox.networkAllowed) {
770
+ if (sandbox.allowedHosts.length > 0 || sandbox.allowedPorts.length > 0) {
771
+ lines.push(";; Allow specific network targets");
772
+ for (const host of sandbox.allowedHosts) {
773
+ lines.push(`(allow network-outbound (remote tcp "${this.escapeSbpl(host)}:*"))`);
774
+ }
775
+ for (const port of sandbox.allowedPorts) {
776
+ lines.push(`(allow network-outbound (remote tcp "*:${port}"))`);
777
+ }
778
+ const isLocalhostOnly = sandbox.allowedHosts.length > 0 && sandbox.allowedHosts.every((h) => h === "localhost" || h === "127.0.0.1");
779
+ if (!isLocalhostOnly) {
780
+ lines.push('(allow network-outbound (remote udp "*:53") (remote tcp "*:53"))');
781
+ }
782
+ } else {
783
+ lines.push("(allow network*)");
784
+ }
785
+ } else {
786
+ lines.push("(deny network*)");
787
+ }
788
+ lines.push("");
789
+ lines.push(
790
+ ";; Broker / local unix sockets",
791
+ "(allow network-outbound (remote unix))",
792
+ "(allow network-inbound (local unix))",
793
+ "(allow file-read* file-write*",
794
+ ' (subpath "/var/run/agenshield")',
795
+ ' (subpath "/private/var/run/agenshield"))',
796
+ ""
797
+ );
798
+ lines.push(
799
+ ";; Process management",
800
+ "(allow process-fork)",
801
+ "(allow signal (target self))",
802
+ "(allow sysctl-read)",
803
+ ""
804
+ );
805
+ lines.push(
806
+ ";; Mach IPC",
807
+ "(allow mach-lookup)",
808
+ ""
809
+ );
810
+ return lines.join("\n");
811
+ }
812
+ /**
813
+ * Remove stale profile files older than maxAgeMs.
814
+ */
815
+ cleanup(maxAgeMs) {
816
+ if (!_existsSync2(this.profileDir)) return;
817
+ try {
818
+ const now = Date.now();
819
+ const entries = _readdirSync2(this.profileDir);
820
+ for (const entry of entries) {
821
+ if (!entry.endsWith(".sb")) continue;
822
+ const filePath = path.join(this.profileDir, entry);
823
+ try {
824
+ const stat = _statSync2(filePath);
825
+ if (now - stat.mtimeMs > maxAgeMs) {
826
+ _unlinkSync2(filePath);
827
+ debugLog(`profile-manager: cleaned up stale profile ${filePath}`);
828
+ }
829
+ } catch {
830
+ }
831
+ }
832
+ } catch {
833
+ }
834
+ }
835
+ /**
836
+ * Escape a string for safe inclusion in SBPL
837
+ */
838
+ escapeSbpl(s) {
839
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
840
+ }
841
+ /**
842
+ * Ensure the profile directory exists
843
+ */
844
+ ensureDir() {
845
+ if (this.ensuredDir) return;
846
+ if (!_existsSync2(this.profileDir)) {
847
+ _mkdirSync(this.profileDir, { recursive: true, mode: 1023 });
848
+ } else {
849
+ try {
850
+ const stat = _statSync2(this.profileDir);
851
+ if ((stat.mode & 511) !== 511) {
852
+ _chmodSync(this.profileDir, 1023);
853
+ }
854
+ } catch {
855
+ }
856
+ }
857
+ this.ensuredDir = true;
858
+ }
859
+ };
860
+
581
861
  // libs/shield-interceptor/src/interceptors/child-process.ts
582
862
  var childProcessModule = require("node:child_process");
583
863
  var ChildProcessInterceptor = class extends BaseInterceptor {
584
864
  syncClient;
585
865
  _checking = false;
866
+ _executing = false;
867
+ // Guards exec→execFile re-entrancy
868
+ profileManager = null;
586
869
  originalExec = null;
587
870
  originalExecSync = null;
588
871
  originalSpawn = null;
@@ -591,13 +874,18 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
591
874
  originalFork = null;
592
875
  constructor(options) {
593
876
  super(options);
877
+ const config = this.interceptorConfig;
594
878
  this.syncClient = new SyncClient({
595
- socketPath: "/var/run/agenshield/agenshield.sock",
596
- httpHost: "localhost",
597
- httpPort: 5201,
598
- // Broker uses 5201
599
- timeout: 3e4
879
+ socketPath: config?.socketPath || "/var/run/agenshield/agenshield.sock",
880
+ httpHost: config?.httpHost || "localhost",
881
+ httpPort: config?.httpPort || 5201,
882
+ timeout: config?.timeout || 3e4
600
883
  });
884
+ if (config?.enableSeatbelt && process.platform === "darwin") {
885
+ this.profileManager = new ProfileManager(
886
+ config.seatbeltProfileDir || "/tmp/agenshield-profiles"
887
+ );
888
+ }
601
889
  }
602
890
  install() {
603
891
  if (this.installed) return;
@@ -631,64 +919,207 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
631
919
  this.originalFork = null;
632
920
  this.installed = false;
633
921
  }
922
+ /**
923
+ * Build execution context from config for RPC calls
924
+ */
925
+ getPolicyExecutionContext() {
926
+ const config = this.interceptorConfig;
927
+ return {
928
+ callerType: config?.contextType || "agent",
929
+ skillSlug: config?.contextSkillSlug,
930
+ agentId: config?.contextAgentId,
931
+ depth: 0
932
+ };
933
+ }
934
+ /**
935
+ * Synchronous policy check via SyncClient.
936
+ * Returns the full policy result (with sandbox config) or null if broker
937
+ * is unavailable and failOpen is true.
938
+ */
939
+ syncPolicyCheck(fullCommand) {
940
+ this._checking = true;
941
+ try {
942
+ debugLog(`cp.syncPolicyCheck START command=${fullCommand}`);
943
+ const context = this.getPolicyExecutionContext();
944
+ const result = this.syncClient.request(
945
+ "policy_check",
946
+ { operation: "exec", target: fullCommand, context }
947
+ );
948
+ debugLog(`cp.syncPolicyCheck DONE allowed=${result.allowed} command=${fullCommand}`);
949
+ if (!result.allowed) {
950
+ throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
951
+ operation: "exec",
952
+ target: fullCommand,
953
+ policyId: result.policyId
954
+ });
955
+ }
956
+ return result;
957
+ } catch (error) {
958
+ if (error instanceof PolicyDeniedError) {
959
+ throw error;
960
+ }
961
+ debugLog(`cp.syncPolicyCheck ERROR: ${error.message} command=${fullCommand}`);
962
+ if (!this.failOpen) {
963
+ throw error;
964
+ }
965
+ return null;
966
+ } finally {
967
+ this._checking = false;
968
+ }
969
+ }
970
+ /**
971
+ * Create a restrictive default sandbox config for fail-open scenarios.
972
+ * No network, minimal fs — better than running completely unsandboxed.
973
+ */
974
+ getFailOpenSandbox() {
975
+ return {
976
+ enabled: true,
977
+ allowedReadPaths: [],
978
+ allowedWritePaths: [],
979
+ deniedPaths: [],
980
+ networkAllowed: false,
981
+ allowedHosts: [],
982
+ allowedPorts: [],
983
+ allowedBinaries: [],
984
+ deniedBinaries: [],
985
+ envInjection: {},
986
+ envDeny: []
987
+ };
988
+ }
989
+ /**
990
+ * Resolve the sandbox config to use: from policy result, fail-open default, or null.
991
+ */
992
+ resolveSandbox(policyResult) {
993
+ if (policyResult?.sandbox?.enabled) {
994
+ return policyResult.sandbox;
995
+ }
996
+ if (policyResult === null && this.profileManager) {
997
+ return this.getFailOpenSandbox();
998
+ }
999
+ return null;
1000
+ }
1001
+ /**
1002
+ * Wrap a command with sandbox-exec if seatbelt is enabled and sandbox config is present.
1003
+ * Returns modified { command, args, options } for spawn-style calls.
1004
+ */
1005
+ wrapWithSeatbelt(command, args, options, policyResult) {
1006
+ const sandbox = this.resolveSandbox(policyResult);
1007
+ if (!this.profileManager || !sandbox || process.platform !== "darwin") {
1008
+ return { command, args, options };
1009
+ }
1010
+ if (command === "/opt/agenshield/bin/node-bin" || command.endsWith("/node-bin")) {
1011
+ debugLog(`cp.wrapWithSeatbelt: SKIP node-bin (already intercepted) command=${command}`);
1012
+ return { command, args, options };
1013
+ }
1014
+ if (command === "/usr/bin/sandbox-exec" || command.endsWith("/sandbox-exec")) {
1015
+ debugLog(`cp.wrapWithSeatbelt: SKIP already sandbox-exec command=${command}`);
1016
+ return { command, args, options };
1017
+ }
1018
+ debugLog(`cp.wrapWithSeatbelt: wrapping command=${command}`);
1019
+ const profileContent = this.profileManager.generateProfile(sandbox);
1020
+ const profilePath = this.profileManager.getOrCreateProfile(profileContent);
1021
+ const env = { ...options?.env || process.env };
1022
+ if (sandbox.envInjection) {
1023
+ Object.assign(env, sandbox.envInjection);
1024
+ }
1025
+ if (sandbox.envDeny) {
1026
+ for (const key of sandbox.envDeny) {
1027
+ delete env[key];
1028
+ }
1029
+ }
1030
+ return {
1031
+ command: "/usr/bin/sandbox-exec",
1032
+ args: ["-f", profilePath, command, ...args],
1033
+ options: { ...options, env }
1034
+ };
1035
+ }
1036
+ /**
1037
+ * Wrap a shell command string with sandbox-exec.
1038
+ * For exec/execSync which take a full command string.
1039
+ */
1040
+ wrapCommandStringWithSeatbelt(command, options, policyResult) {
1041
+ const sandbox = this.resolveSandbox(policyResult);
1042
+ if (!this.profileManager || !sandbox || process.platform !== "darwin") {
1043
+ return { command, options };
1044
+ }
1045
+ if (command.startsWith("/usr/bin/sandbox-exec ") || command.startsWith("sandbox-exec ")) {
1046
+ debugLog(`cp.wrapCommandStringWithSeatbelt: SKIP already sandbox-exec command=${command}`);
1047
+ return { command, options };
1048
+ }
1049
+ debugLog(`cp.wrapCommandStringWithSeatbelt: wrapping command=${command}`);
1050
+ const profileContent = this.profileManager.generateProfile(sandbox);
1051
+ const profilePath = this.profileManager.getOrCreateProfile(profileContent);
1052
+ const env = { ...options?.env || process.env };
1053
+ if (sandbox.envInjection) {
1054
+ Object.assign(env, sandbox.envInjection);
1055
+ }
1056
+ if (sandbox.envDeny) {
1057
+ for (const key of sandbox.envDeny) {
1058
+ delete env[key];
1059
+ }
1060
+ }
1061
+ return {
1062
+ command: `/usr/bin/sandbox-exec -f ${profilePath} ${command}`,
1063
+ options: { ...options, env }
1064
+ };
1065
+ }
634
1066
  createInterceptedExec() {
635
1067
  const self = this;
636
1068
  const original = this.originalExec;
637
1069
  return function interceptedExec(command, ...args) {
638
1070
  const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
639
- debugLog(`cp.exec ENTER command=${command} _checking=${self._checking}`);
640
- if (self._checking) {
1071
+ const options = args[0];
1072
+ debugLog(`cp.exec ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
1073
+ if (self._checking || self._executing) {
641
1074
  debugLog(`cp.exec SKIP (re-entrancy) command=${command}`);
642
1075
  return original(command, ...args, callback);
643
1076
  }
644
1077
  self.eventReporter.intercept("exec", command);
645
- self.checkPolicy("exec", command).then(() => {
646
- original(command, ...args, callback);
647
- }).catch((error) => {
1078
+ let policyResult = null;
1079
+ try {
1080
+ policyResult = self.syncPolicyCheck(command);
1081
+ } catch (error) {
648
1082
  if (callback) {
649
- callback(error, "", "");
1083
+ process.nextTick(() => callback(error, "", ""));
650
1084
  }
651
- });
652
- return original('echo ""');
1085
+ return original('echo ""');
1086
+ }
1087
+ const wrapped = self.wrapCommandStringWithSeatbelt(command, options, policyResult);
1088
+ debugLog(`cp.exec calling original command=${wrapped.command}`);
1089
+ self._executing = true;
1090
+ try {
1091
+ if (wrapped.options) {
1092
+ return original(wrapped.command, wrapped.options, callback);
1093
+ }
1094
+ return original(wrapped.command, callback);
1095
+ } finally {
1096
+ self._executing = false;
1097
+ }
653
1098
  };
654
1099
  }
655
1100
  createInterceptedExecSync() {
656
1101
  const self = this;
657
1102
  const original = this.originalExecSync;
658
1103
  const interceptedExecSync = function(command, options) {
659
- debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking}`);
660
- if (self._checking) {
1104
+ debugLog(`cp.execSync ENTER command=${command} _checking=${self._checking} _executing=${self._executing}`);
1105
+ if (self._checking || self._executing) {
661
1106
  debugLog(`cp.execSync SKIP (re-entrancy) command=${command}`);
662
1107
  return original(command, options);
663
1108
  }
664
- self._checking = true;
1109
+ self.eventReporter.intercept("exec", command);
1110
+ const policyResult = self.syncPolicyCheck(command);
1111
+ const wrapped = self.wrapCommandStringWithSeatbelt(
1112
+ command,
1113
+ options,
1114
+ policyResult
1115
+ );
1116
+ debugLog(`cp.execSync calling original command=${wrapped.command}`);
1117
+ self._executing = true;
665
1118
  try {
666
- self.eventReporter.intercept("exec", command);
667
- debugLog(`cp.execSync policy_check START command=${command}`);
668
- const result = self.syncClient.request(
669
- "policy_check",
670
- { operation: "exec", target: command }
671
- );
672
- debugLog(`cp.execSync policy_check DONE allowed=${result.allowed} command=${command}`);
673
- if (!result.allowed) {
674
- throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
675
- operation: "exec",
676
- target: command
677
- });
678
- }
679
- } catch (error) {
680
- debugLog(`cp.execSync policy_check ERROR: ${error.message} command=${command}`);
681
- if (error instanceof PolicyDeniedError) {
682
- throw error;
683
- }
684
- if (!self.failOpen) {
685
- throw error;
686
- }
1119
+ return original(wrapped.command, wrapped.options);
687
1120
  } finally {
688
- self._checking = false;
1121
+ self._executing = false;
689
1122
  }
690
- debugLog(`cp.execSync calling original command=${command}`);
691
- return original(command, options);
692
1123
  };
693
1124
  return interceptedExecSync;
694
1125
  }
@@ -697,17 +1128,31 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
697
1128
  const original = this.originalSpawn;
698
1129
  const interceptedSpawn = function(command, args, options) {
699
1130
  const fullCmd = args ? `${command} ${args.join(" ")}` : command;
700
- debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking}`);
701
- if (self._checking) {
1131
+ debugLog(`cp.spawn ENTER command=${fullCmd} _checking=${self._checking} _executing=${self._executing}`);
1132
+ if (self._checking || self._executing) {
702
1133
  debugLog(`cp.spawn SKIP (re-entrancy) command=${fullCmd}`);
703
1134
  return original(command, args, options || {});
704
1135
  }
705
- const fullCommand = args ? `${command} ${args.join(" ")}` : command;
706
- self.eventReporter.intercept("exec", fullCommand);
707
- self.checkPolicy("exec", fullCommand).catch((error) => {
708
- self.eventReporter.error("exec", fullCommand, error.message);
709
- });
710
- return original(command, args, options || {});
1136
+ self.eventReporter.intercept("exec", fullCmd);
1137
+ let policyResult = null;
1138
+ try {
1139
+ policyResult = self.syncPolicyCheck(fullCmd);
1140
+ } catch (error) {
1141
+ debugLog(`cp.spawn DENIED command=${fullCmd}`);
1142
+ const denied = original("false", [], { stdio: "pipe" });
1143
+ process.nextTick(() => {
1144
+ denied.emit("error", error);
1145
+ });
1146
+ return denied;
1147
+ }
1148
+ const wrapped = self.wrapWithSeatbelt(
1149
+ command,
1150
+ Array.from(args || []),
1151
+ options,
1152
+ policyResult
1153
+ );
1154
+ debugLog(`cp.spawn calling original command=${wrapped.command} args=${wrapped.args.join(" ")}`);
1155
+ return original(wrapped.command, wrapped.args, wrapped.options || {});
711
1156
  };
712
1157
  return interceptedSpawn;
713
1158
  }
@@ -716,77 +1161,130 @@ var ChildProcessInterceptor = class extends BaseInterceptor {
716
1161
  const original = this.originalSpawnSync;
717
1162
  return function interceptedSpawnSync(command, args, options) {
718
1163
  const fullCommand = args ? `${command} ${args.join(" ")}` : command;
719
- debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking}`);
720
- if (self._checking) {
1164
+ debugLog(`cp.spawnSync ENTER command=${fullCommand} _checking=${self._checking} _executing=${self._executing}`);
1165
+ if (self._checking || self._executing) {
721
1166
  debugLog(`cp.spawnSync SKIP (re-entrancy) command=${fullCommand}`);
722
1167
  return original(command, args, options);
723
1168
  }
724
- self._checking = true;
1169
+ self.eventReporter.intercept("exec", fullCommand);
1170
+ let policyResult = null;
725
1171
  try {
726
- self.eventReporter.intercept("exec", fullCommand);
727
- debugLog(`cp.spawnSync policy_check START command=${fullCommand}`);
728
- const result = self.syncClient.request(
729
- "policy_check",
730
- { operation: "exec", target: fullCommand }
731
- );
732
- debugLog(`cp.spawnSync policy_check DONE allowed=${result.allowed} command=${fullCommand}`);
733
- if (!result.allowed) {
734
- return {
735
- pid: -1,
736
- output: [],
737
- stdout: Buffer.alloc(0),
738
- stderr: Buffer.from(result.reason || "Policy denied"),
739
- status: 1,
740
- signal: null,
741
- error: new PolicyDeniedError(result.reason || "Policy denied")
742
- };
743
- }
1172
+ policyResult = self.syncPolicyCheck(fullCommand);
744
1173
  } catch (error) {
745
- debugLog(`cp.spawnSync policy_check ERROR: ${error.message} command=${fullCommand}`);
746
- if (!self.failOpen) {
747
- return {
748
- pid: -1,
749
- output: [],
750
- stdout: Buffer.alloc(0),
751
- stderr: Buffer.from(error.message),
752
- status: 1,
753
- signal: null,
754
- error
755
- };
756
- }
757
- } finally {
758
- self._checking = false;
1174
+ debugLog(`cp.spawnSync DENIED command=${fullCommand}`);
1175
+ return {
1176
+ pid: -1,
1177
+ output: [],
1178
+ stdout: Buffer.alloc(0),
1179
+ stderr: Buffer.from(
1180
+ error instanceof PolicyDeniedError ? error.message || "Policy denied" : error.message
1181
+ ),
1182
+ status: 1,
1183
+ signal: null,
1184
+ error
1185
+ };
759
1186
  }
760
- debugLog(`cp.spawnSync calling original command=${fullCommand}`);
761
- return original(command, args, options);
1187
+ const wrapped = self.wrapWithSeatbelt(
1188
+ command,
1189
+ Array.from(args || []),
1190
+ options,
1191
+ policyResult
1192
+ );
1193
+ debugLog(`cp.spawnSync calling original command=${wrapped.command}`);
1194
+ return original(
1195
+ wrapped.command,
1196
+ wrapped.args,
1197
+ wrapped.options
1198
+ );
762
1199
  };
763
1200
  }
764
1201
  createInterceptedExecFile() {
765
1202
  const self = this;
766
1203
  const original = this.originalExecFile;
767
- return function interceptedExecFile(file, ...args) {
768
- if (self._checking) {
769
- return original(file, ...args);
1204
+ return function interceptedExecFile(file, ...rest) {
1205
+ if (self._checking || self._executing) {
1206
+ return original(file, ...rest);
770
1207
  }
771
- self.eventReporter.intercept("exec", file);
772
- self.checkPolicy("exec", file).catch((error) => {
773
- self.eventReporter.error("exec", file, error.message);
774
- });
775
- return original(file, ...args);
1208
+ let args = [];
1209
+ let options;
1210
+ let callback;
1211
+ for (const arg of rest) {
1212
+ if (typeof arg === "function") {
1213
+ callback = arg;
1214
+ } else if (Array.isArray(arg)) {
1215
+ args = arg;
1216
+ } else if (typeof arg === "object" && arg !== null) {
1217
+ options = arg;
1218
+ }
1219
+ }
1220
+ const fullCommand = args.length > 0 ? `${file} ${args.join(" ")}` : file;
1221
+ debugLog(`cp.execFile ENTER command=${fullCommand}`);
1222
+ self.eventReporter.intercept("exec", fullCommand);
1223
+ let policyResult = null;
1224
+ try {
1225
+ policyResult = self.syncPolicyCheck(fullCommand);
1226
+ } catch (error) {
1227
+ if (callback) {
1228
+ process.nextTick(() => callback(error, "", ""));
1229
+ }
1230
+ return original("false");
1231
+ }
1232
+ const wrapped = self.wrapWithSeatbelt(file, args, options, policyResult);
1233
+ debugLog(`cp.execFile calling original command=${wrapped.command}`);
1234
+ if (callback) {
1235
+ return original(
1236
+ wrapped.command,
1237
+ wrapped.args,
1238
+ wrapped.options,
1239
+ callback
1240
+ );
1241
+ }
1242
+ return original(
1243
+ wrapped.command,
1244
+ wrapped.args,
1245
+ wrapped.options || {},
1246
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1247
+ () => {
1248
+ }
1249
+ );
776
1250
  };
777
1251
  }
778
1252
  createInterceptedFork() {
779
1253
  const self = this;
780
1254
  const original = this.originalFork;
781
1255
  const interceptedFork = function(modulePath, args, options) {
782
- if (self._checking) {
1256
+ if (self._checking || self._executing) {
783
1257
  return original(modulePath, args, options);
784
1258
  }
785
1259
  const pathStr = modulePath.toString();
786
- self.eventReporter.intercept("exec", `fork:${pathStr}`);
787
- self.checkPolicy("exec", `fork:${pathStr}`).catch((error) => {
788
- self.eventReporter.error("exec", pathStr, error.message);
789
- });
1260
+ const fullCommand = `fork:${pathStr}`;
1261
+ debugLog(`cp.fork ENTER command=${fullCommand}`);
1262
+ self.eventReporter.intercept("exec", fullCommand);
1263
+ let policyResult = null;
1264
+ try {
1265
+ policyResult = self.syncPolicyCheck(fullCommand);
1266
+ } catch (error) {
1267
+ debugLog(`cp.fork DENIED command=${fullCommand}`);
1268
+ const denied = self.originalSpawn("false", [], { stdio: "pipe" });
1269
+ process.nextTick(() => {
1270
+ denied.emit("error", error);
1271
+ });
1272
+ return denied;
1273
+ }
1274
+ if (policyResult?.sandbox) {
1275
+ const sandbox = policyResult.sandbox;
1276
+ const env = { ...options?.env || process.env };
1277
+ if (sandbox.envInjection) {
1278
+ Object.assign(env, sandbox.envInjection);
1279
+ }
1280
+ if (sandbox.envDeny) {
1281
+ for (const key of sandbox.envDeny) {
1282
+ delete env[key];
1283
+ }
1284
+ }
1285
+ options = { ...options, env };
1286
+ }
1287
+ debugLog(`cp.fork calling original module=${pathStr}`);
790
1288
  return original(modulePath, args, options);
791
1289
  };
792
1290
  return interceptedFork;
@@ -880,19 +1378,19 @@ var FsInterceptor = class extends BaseInterceptor {
880
1378
  const key = `fs:${methodName}`;
881
1379
  this.originals.set(key, original);
882
1380
  const self = this;
883
- safeOverride(module2, methodName, function intercepted(path, ...args) {
884
- const pathString = normalizePathArg(path);
1381
+ safeOverride(module2, methodName, function intercepted(path2, ...args) {
1382
+ const pathString = normalizePathArg(path2);
885
1383
  const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
886
1384
  debugLog(`fs.${methodName} ENTER (async) path=${pathString} _checking=${self._checking}`);
887
1385
  if (self._checking) {
888
1386
  debugLog(`fs.${methodName} SKIP (re-entrancy, async) path=${pathString}`);
889
- original.call(module2, path, ...args, callback);
1387
+ original.call(module2, path2, ...args, callback);
890
1388
  return;
891
1389
  }
892
1390
  self.eventReporter.intercept(operation, pathString);
893
1391
  self.checkPolicy(operation, pathString).then(() => {
894
1392
  debugLog(`fs.${methodName} policy OK (async) path=${pathString}`);
895
- original.call(module2, path, ...args, callback);
1393
+ original.call(module2, path2, ...args, callback);
896
1394
  }).catch((error) => {
897
1395
  debugLog(`fs.${methodName} policy ERROR (async): ${error.message} path=${pathString}`);
898
1396
  if (callback) {
@@ -907,12 +1405,12 @@ var FsInterceptor = class extends BaseInterceptor {
907
1405
  const key = `fs:${methodName}`;
908
1406
  this.originals.set(key, original);
909
1407
  const self = this;
910
- safeOverride(module2, methodName, function interceptedSync(path, ...args) {
911
- const pathString = normalizePathArg(path);
1408
+ safeOverride(module2, methodName, function interceptedSync(path2, ...args) {
1409
+ const pathString = normalizePathArg(path2);
912
1410
  debugLog(`fs.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
913
1411
  if (self._checking) {
914
1412
  debugLog(`fs.${methodName} SKIP (re-entrancy) path=${pathString}`);
915
- return original.call(module2, path, ...args);
1413
+ return original.call(module2, path2, ...args);
916
1414
  }
917
1415
  self._checking = true;
918
1416
  try {
@@ -941,7 +1439,7 @@ var FsInterceptor = class extends BaseInterceptor {
941
1439
  self._checking = false;
942
1440
  }
943
1441
  debugLog(`fs.${methodName} calling original path=${pathString}`);
944
- return original.call(module2, path, ...args);
1442
+ return original.call(module2, path2, ...args);
945
1443
  });
946
1444
  }
947
1445
  interceptPromiseMethod(module2, methodName, operation) {
@@ -950,17 +1448,17 @@ var FsInterceptor = class extends BaseInterceptor {
950
1448
  const key = `fsPromises:${methodName}`;
951
1449
  this.originals.set(key, original);
952
1450
  const self = this;
953
- safeOverride(module2, methodName, async function interceptedPromise(path, ...args) {
954
- const pathString = normalizePathArg(path);
1451
+ safeOverride(module2, methodName, async function interceptedPromise(path2, ...args) {
1452
+ const pathString = normalizePathArg(path2);
955
1453
  debugLog(`fsPromises.${methodName} ENTER path=${pathString} _checking=${self._checking}`);
956
1454
  if (self._checking) {
957
1455
  debugLog(`fsPromises.${methodName} SKIP (re-entrancy) path=${pathString}`);
958
- return original.call(module2, path, ...args);
1456
+ return original.call(module2, path2, ...args);
959
1457
  }
960
1458
  self.eventReporter.intercept(operation, pathString);
961
1459
  await self.checkPolicy(operation, pathString);
962
1460
  debugLog(`fsPromises.${methodName} policy OK path=${pathString}`);
963
- return original.call(module2, path, ...args);
1461
+ return original.call(module2, path2, ...args);
964
1462
  });
965
1463
  }
966
1464
  };
@@ -1104,11 +1602,11 @@ var PolicyEvaluator = class {
1104
1602
  * Check if an operation is allowed
1105
1603
  * Always queries the daemon for fresh policy decisions
1106
1604
  */
1107
- async check(operation, target) {
1605
+ async check(operation, target, context) {
1108
1606
  try {
1109
1607
  const result = await this.client.request(
1110
1608
  "policy_check",
1111
- { operation, target }
1609
+ { operation, target, context }
1112
1610
  );
1113
1611
  return result;
1114
1612
  } catch (error) {
@@ -1152,7 +1650,11 @@ var EventReporter = class _EventReporter {
1152
1650
  const level = this.getLogLevel(event);
1153
1651
  if (this.shouldLog(level)) {
1154
1652
  const prefix = event.type === "allow" ? "\u2713" : event.type === "deny" ? "\u2717" : "\u2022";
1155
- console[level](`[AgenShield] ${prefix} ${event.operation}: ${event.target}`);
1653
+ let detail = `${prefix} ${event.operation}: ${event.target}`;
1654
+ if (event.policyId) detail += ` [policy:${event.policyId}]`;
1655
+ if (event.error) detail += ` [reason:${event.error}]`;
1656
+ if (event.duration) detail += ` [${event.duration}ms]`;
1657
+ console[level](`[AgenShield] ${detail}`);
1156
1658
  }
1157
1659
  if (this.queue.length >= 100) {
1158
1660
  this.flush();
@@ -1272,6 +1774,13 @@ function installInterceptors(configOverrides) {
1272
1774
  return;
1273
1775
  }
1274
1776
  const config = createConfig(configOverrides);
1777
+ if (config.logLevel === "debug") {
1778
+ try {
1779
+ const safeConfig = { ...config };
1780
+ console.error("[AgenShield:config]", JSON.stringify(safeConfig, null, 2));
1781
+ } catch {
1782
+ }
1783
+ }
1275
1784
  client = new AsyncClient({
1276
1785
  socketPath: config.socketPath,
1277
1786
  httpHost: config.httpHost,
@@ -1292,7 +1801,8 @@ function installInterceptors(configOverrides) {
1292
1801
  policyEvaluator,
1293
1802
  eventReporter,
1294
1803
  failOpen: config.failOpen,
1295
- brokerHttpPort: config.httpPort
1804
+ brokerHttpPort: config.httpPort,
1805
+ config
1296
1806
  });
1297
1807
  installed.fetch.install();
1298
1808
  log(config, "debug", "Installed fetch interceptor");
@@ -1325,7 +1835,8 @@ function installInterceptors(configOverrides) {
1325
1835
  policyEvaluator,
1326
1836
  eventReporter,
1327
1837
  failOpen: config.failOpen,
1328
- brokerHttpPort: config.httpPort
1838
+ brokerHttpPort: config.httpPort,
1839
+ config
1329
1840
  });
1330
1841
  installed.childProcess.install();
1331
1842
  log(config, "debug", "Installed child_process interceptor");