@agenshield/interceptor 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.
package/index.js ADDED
@@ -0,0 +1,1270 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // libs/shield-interceptor/src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AgenShieldError: () => AgenShieldError,
34
+ AsyncClient: () => AsyncClient,
35
+ ChildProcessInterceptor: () => ChildProcessInterceptor,
36
+ EventReporter: () => EventReporter,
37
+ FetchInterceptor: () => FetchInterceptor,
38
+ FsInterceptor: () => FsInterceptor,
39
+ HttpInterceptor: () => HttpInterceptor,
40
+ PolicyDeniedError: () => PolicyDeniedError,
41
+ PolicyEvaluator: () => PolicyEvaluator,
42
+ SyncClient: () => SyncClient,
43
+ WebSocketInterceptor: () => WebSocketInterceptor,
44
+ createConfig: () => createConfig,
45
+ installInterceptors: () => installInterceptors,
46
+ uninstallInterceptors: () => uninstallInterceptors
47
+ });
48
+ module.exports = __toCommonJS(index_exports);
49
+
50
+ // libs/shield-interceptor/src/config.ts
51
+ function createConfig(overrides) {
52
+ const env = process.env;
53
+ return {
54
+ socketPath: env["AGENSHIELD_SOCKET"] || "/var/run/agenshield/agenshield.sock",
55
+ httpHost: env["AGENSHIELD_HOST"] || "localhost",
56
+ httpPort: parseInt(env["AGENSHIELD_PORT"] || "5201", 10),
57
+ failOpen: env["AGENSHIELD_FAIL_OPEN"] === "true",
58
+ logLevel: env["AGENSHIELD_LOG_LEVEL"] || "warn",
59
+ interceptFetch: env["AGENSHIELD_INTERCEPT_FETCH"] !== "false",
60
+ interceptHttp: env["AGENSHIELD_INTERCEPT_HTTP"] !== "false",
61
+ interceptWs: env["AGENSHIELD_INTERCEPT_WS"] !== "false",
62
+ interceptFs: env["AGENSHIELD_INTERCEPT_FS"] !== "false",
63
+ interceptExec: env["AGENSHIELD_INTERCEPT_EXEC"] !== "false",
64
+ timeout: parseInt(env["AGENSHIELD_TIMEOUT"] || "30000", 10),
65
+ ...overrides
66
+ };
67
+ }
68
+ var defaultConfig = createConfig();
69
+
70
+ // libs/shield-interceptor/src/errors.ts
71
+ var AgenShieldError = class extends Error {
72
+ code;
73
+ operation;
74
+ target;
75
+ constructor(message, code, options) {
76
+ super(message);
77
+ this.name = "AgenShieldError";
78
+ this.code = code;
79
+ this.operation = options?.operation;
80
+ this.target = options?.target;
81
+ Error.captureStackTrace?.(this, this.constructor);
82
+ }
83
+ };
84
+ var PolicyDeniedError = class extends AgenShieldError {
85
+ policyId;
86
+ constructor(message, options) {
87
+ super(message, "POLICY_DENIED", options);
88
+ this.name = "PolicyDeniedError";
89
+ this.policyId = options?.policyId;
90
+ }
91
+ };
92
+ var BrokerUnavailableError = class extends AgenShieldError {
93
+ constructor(message = "AgenShield broker is unavailable") {
94
+ super(message, "BROKER_UNAVAILABLE");
95
+ this.name = "BrokerUnavailableError";
96
+ }
97
+ };
98
+ var TimeoutError = class extends AgenShieldError {
99
+ constructor(message = "Request timed out") {
100
+ super(message, "TIMEOUT");
101
+ this.name = "TimeoutError";
102
+ }
103
+ };
104
+
105
+ // libs/shield-interceptor/src/interceptors/base.ts
106
+ var BaseInterceptor = class {
107
+ client;
108
+ policyEvaluator;
109
+ eventReporter;
110
+ failOpen;
111
+ installed = false;
112
+ constructor(options) {
113
+ this.client = options.client;
114
+ this.policyEvaluator = options.policyEvaluator;
115
+ this.eventReporter = options.eventReporter;
116
+ this.failOpen = options.failOpen;
117
+ }
118
+ /**
119
+ * Check if the interceptor is installed
120
+ */
121
+ isInstalled() {
122
+ return this.installed;
123
+ }
124
+ /**
125
+ * Check policy and handle the result
126
+ */
127
+ async checkPolicy(operation, target) {
128
+ const startTime = Date.now();
129
+ try {
130
+ this.eventReporter.intercept(operation, target);
131
+ const result = await this.policyEvaluator.check(operation, target);
132
+ if (!result.allowed) {
133
+ this.eventReporter.deny(operation, target, result.policyId, result.reason);
134
+ throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
135
+ operation,
136
+ target,
137
+ policyId: result.policyId
138
+ });
139
+ }
140
+ this.eventReporter.allow(
141
+ operation,
142
+ target,
143
+ result.policyId,
144
+ Date.now() - startTime
145
+ );
146
+ } catch (error) {
147
+ if (error instanceof PolicyDeniedError) {
148
+ throw error;
149
+ }
150
+ if (this.failOpen) {
151
+ this.eventReporter.error(
152
+ operation,
153
+ target,
154
+ `Broker unavailable, failing open: ${error.message}`
155
+ );
156
+ return;
157
+ }
158
+ throw new BrokerUnavailableError(error.message);
159
+ }
160
+ }
161
+ /**
162
+ * Log a debug message
163
+ */
164
+ debug(message) {
165
+ console.debug(`[AgenShield:${this.constructor.name}] ${message}`);
166
+ }
167
+ };
168
+
169
+ // libs/shield-interceptor/src/interceptors/fetch.ts
170
+ var FetchInterceptor = class extends BaseInterceptor {
171
+ originalFetch = null;
172
+ constructor(options) {
173
+ super(options);
174
+ }
175
+ install() {
176
+ if (this.installed) return;
177
+ this.originalFetch = globalThis.fetch;
178
+ globalThis.fetch = this.interceptedFetch.bind(this);
179
+ this.installed = true;
180
+ }
181
+ uninstall() {
182
+ if (!this.installed || !this.originalFetch) return;
183
+ globalThis.fetch = this.originalFetch;
184
+ this.originalFetch = null;
185
+ this.installed = false;
186
+ }
187
+ async interceptedFetch(input, init) {
188
+ if (!this.originalFetch) {
189
+ throw new Error("Original fetch not available");
190
+ }
191
+ let url;
192
+ if (typeof input === "string") {
193
+ url = input;
194
+ } else if (input instanceof URL) {
195
+ url = input.toString();
196
+ } else {
197
+ url = input.url;
198
+ }
199
+ if (this.isBrokerUrl(url)) {
200
+ return this.originalFetch(input, init);
201
+ }
202
+ await this.checkPolicy("http_request", url);
203
+ try {
204
+ const method = init?.method || "GET";
205
+ const headers = {};
206
+ if (init?.headers) {
207
+ if (init.headers instanceof Headers) {
208
+ init.headers.forEach((value, key) => {
209
+ headers[key] = value;
210
+ });
211
+ } else if (Array.isArray(init.headers)) {
212
+ for (const [key, value] of init.headers) {
213
+ headers[key] = value;
214
+ }
215
+ } else {
216
+ Object.assign(headers, init.headers);
217
+ }
218
+ }
219
+ let body;
220
+ if (init?.body) {
221
+ if (typeof init.body === "string") {
222
+ body = init.body;
223
+ } else if (init.body instanceof ArrayBuffer) {
224
+ body = Buffer.from(init.body).toString("base64");
225
+ } else {
226
+ body = String(init.body);
227
+ }
228
+ }
229
+ const result = await this.client.request("http_request", {
230
+ url,
231
+ method,
232
+ headers,
233
+ body
234
+ });
235
+ const responseHeaders = new Headers(result.headers);
236
+ return new Response(result.body, {
237
+ status: result.status,
238
+ statusText: result.statusText,
239
+ headers: responseHeaders
240
+ });
241
+ } catch (error) {
242
+ if (error.name === "PolicyDeniedError") {
243
+ throw error;
244
+ }
245
+ if (this.failOpen) {
246
+ return this.originalFetch(input, init);
247
+ }
248
+ throw error;
249
+ }
250
+ }
251
+ isBrokerUrl(url) {
252
+ try {
253
+ const parsed = new URL(url);
254
+ return (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1") && parsed.port === "5200";
255
+ } catch {
256
+ return false;
257
+ }
258
+ }
259
+ };
260
+
261
+ // libs/shield-interceptor/src/interceptors/http.ts
262
+ var httpModule = require("node:http");
263
+ var httpsModule = require("node:https");
264
+ var HttpInterceptor = class extends BaseInterceptor {
265
+ originalHttpRequest = null;
266
+ originalHttpGet = null;
267
+ originalHttpsRequest = null;
268
+ originalHttpsGet = null;
269
+ constructor(options) {
270
+ super(options);
271
+ }
272
+ install() {
273
+ if (this.installed) return;
274
+ this.originalHttpRequest = httpModule.request;
275
+ this.originalHttpGet = httpModule.get;
276
+ this.originalHttpsRequest = httpsModule.request;
277
+ this.originalHttpsGet = httpsModule.get;
278
+ httpModule.request = this.createInterceptedRequest("http", this.originalHttpRequest);
279
+ httpModule.get = this.createInterceptedGet("http", this.originalHttpGet);
280
+ httpsModule.request = this.createInterceptedRequest("https", this.originalHttpsRequest);
281
+ httpsModule.get = this.createInterceptedGet("https", this.originalHttpsGet);
282
+ this.installed = true;
283
+ }
284
+ uninstall() {
285
+ if (!this.installed) return;
286
+ if (this.originalHttpRequest) {
287
+ httpModule.request = this.originalHttpRequest;
288
+ }
289
+ if (this.originalHttpGet) {
290
+ httpModule.get = this.originalHttpGet;
291
+ }
292
+ if (this.originalHttpsRequest) {
293
+ httpsModule.request = this.originalHttpsRequest;
294
+ }
295
+ if (this.originalHttpsGet) {
296
+ httpsModule.get = this.originalHttpsGet;
297
+ }
298
+ this.originalHttpRequest = null;
299
+ this.originalHttpGet = null;
300
+ this.originalHttpsRequest = null;
301
+ this.originalHttpsGet = null;
302
+ this.installed = false;
303
+ }
304
+ createInterceptedRequest(protocol, original) {
305
+ const self = this;
306
+ return function interceptedRequest(urlOrOptions, optionsOrCallback, callback) {
307
+ let url;
308
+ let options;
309
+ let cb;
310
+ if (typeof urlOrOptions === "string" || urlOrOptions instanceof URL) {
311
+ url = urlOrOptions.toString();
312
+ options = typeof optionsOrCallback === "object" ? optionsOrCallback : {};
313
+ cb = typeof optionsOrCallback === "function" ? optionsOrCallback : callback;
314
+ } else {
315
+ options = urlOrOptions;
316
+ url = `${protocol}://${options.hostname || options.host || "localhost"}:${options.port || (protocol === "https" ? 443 : 80)}${options.path || "/"}`;
317
+ cb = optionsOrCallback;
318
+ }
319
+ if (self.isBrokerUrl(url)) {
320
+ return original.call(
321
+ protocol === "http" ? httpModule : httpsModule,
322
+ urlOrOptions,
323
+ optionsOrCallback,
324
+ callback
325
+ );
326
+ }
327
+ const req = original.call(
328
+ protocol === "http" ? httpModule : httpsModule,
329
+ urlOrOptions,
330
+ optionsOrCallback,
331
+ callback
332
+ );
333
+ self.eventReporter.intercept("http_request", url);
334
+ self.checkPolicy("http_request", url).catch((error) => {
335
+ req.destroy(error);
336
+ });
337
+ return req;
338
+ };
339
+ }
340
+ createInterceptedGet(protocol, original) {
341
+ const interceptedRequest = this.createInterceptedRequest(
342
+ protocol,
343
+ protocol === "http" ? this.originalHttpRequest : this.originalHttpsRequest
344
+ );
345
+ return function interceptedGet(urlOrOptions, optionsOrCallback, callback) {
346
+ const req = interceptedRequest(urlOrOptions, optionsOrCallback, callback);
347
+ req.end();
348
+ return req;
349
+ };
350
+ }
351
+ isBrokerUrl(url) {
352
+ try {
353
+ const parsed = new URL(url);
354
+ return (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1") && parsed.port === "5200";
355
+ } catch {
356
+ return false;
357
+ }
358
+ }
359
+ };
360
+
361
+ // libs/shield-interceptor/src/interceptors/websocket.ts
362
+ var WebSocketInterceptor = class extends BaseInterceptor {
363
+ originalWebSocket = null;
364
+ constructor(options) {
365
+ super(options);
366
+ }
367
+ install() {
368
+ if (this.installed) return;
369
+ if (typeof globalThis.WebSocket === "undefined") {
370
+ this.debug("WebSocket not available in this environment");
371
+ return;
372
+ }
373
+ this.originalWebSocket = globalThis.WebSocket;
374
+ const self = this;
375
+ const OriginalWebSocket = this.originalWebSocket;
376
+ class InterceptedWebSocket extends OriginalWebSocket {
377
+ constructor(url, protocols) {
378
+ const urlString = url.toString();
379
+ if (self.isBrokerUrl(urlString)) {
380
+ super(url, protocols);
381
+ return;
382
+ }
383
+ self.eventReporter.intercept("websocket", urlString);
384
+ super(url, protocols);
385
+ self.checkPolicy("websocket", urlString).catch((error) => {
386
+ this.close(1008, "Policy denied");
387
+ const errorEvent = new Event("error");
388
+ this.dispatchEvent(errorEvent);
389
+ });
390
+ }
391
+ }
392
+ globalThis.WebSocket = InterceptedWebSocket;
393
+ this.installed = true;
394
+ }
395
+ uninstall() {
396
+ if (!this.installed || !this.originalWebSocket) return;
397
+ globalThis.WebSocket = this.originalWebSocket;
398
+ this.originalWebSocket = null;
399
+ this.installed = false;
400
+ }
401
+ isBrokerUrl(url) {
402
+ try {
403
+ const parsed = new URL(url);
404
+ return (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1") && parsed.port === "5200";
405
+ } catch {
406
+ return false;
407
+ }
408
+ }
409
+ };
410
+
411
+ // libs/shield-interceptor/src/client/sync-client.ts
412
+ var import_node_child_process = require("node:child_process");
413
+ var fs = __toESM(require("node:fs"), 1);
414
+ var import_node_crypto = require("node:crypto");
415
+ var _existsSync = fs.existsSync.bind(fs);
416
+ var _readFileSync = fs.readFileSync.bind(fs);
417
+ var _unlinkSync = fs.unlinkSync.bind(fs);
418
+ var _spawnSync = import_node_child_process.spawnSync;
419
+ var _execSync = import_node_child_process.execSync;
420
+ var SyncClient = class {
421
+ socketPath;
422
+ httpHost;
423
+ httpPort;
424
+ timeout;
425
+ constructor(options) {
426
+ this.socketPath = options.socketPath;
427
+ this.httpHost = options.httpHost;
428
+ this.httpPort = options.httpPort;
429
+ this.timeout = options.timeout;
430
+ }
431
+ /**
432
+ * Send a synchronous request to the broker
433
+ */
434
+ request(method, params) {
435
+ try {
436
+ return this.socketRequestSync(method, params);
437
+ } catch {
438
+ return this.httpRequestSync(method, params);
439
+ }
440
+ }
441
+ /**
442
+ * Synchronous socket request
443
+ *
444
+ * This uses a blocking approach with a temporary file for the response.
445
+ */
446
+ socketRequestSync(method, params) {
447
+ const id = (0, import_node_crypto.randomUUID)();
448
+ const request = JSON.stringify({
449
+ jsonrpc: "2.0",
450
+ id,
451
+ method,
452
+ params
453
+ }) + "\n";
454
+ const tmpFile = `/tmp/agenshield-sync-${id}.json`;
455
+ const script = `
456
+ const net = require('net');
457
+ const fs = require('fs');
458
+
459
+ const socket = net.createConnection('${this.socketPath}');
460
+ let data = '';
461
+
462
+ socket.on('connect', () => {
463
+ socket.write(${JSON.stringify(request)});
464
+ });
465
+
466
+ socket.on('data', (chunk) => {
467
+ data += chunk.toString();
468
+ if (data.includes('\\n')) {
469
+ socket.end();
470
+ fs.writeFileSync('${tmpFile}', data.split('\\n')[0]);
471
+ }
472
+ });
473
+
474
+ socket.on('error', (err) => {
475
+ fs.writeFileSync('${tmpFile}', JSON.stringify({ error: err.message }));
476
+ });
477
+
478
+ setTimeout(() => {
479
+ socket.destroy();
480
+ fs.writeFileSync('${tmpFile}', JSON.stringify({ error: 'timeout' }));
481
+ }, ${this.timeout});
482
+ `;
483
+ try {
484
+ _spawnSync("node", ["-e", script], {
485
+ timeout: this.timeout + 1e3,
486
+ stdio: "ignore"
487
+ });
488
+ if (_existsSync(tmpFile)) {
489
+ const response = JSON.parse(_readFileSync(tmpFile, "utf-8"));
490
+ _unlinkSync(tmpFile);
491
+ if (response.error) {
492
+ throw new Error(response.error);
493
+ }
494
+ return response.result;
495
+ }
496
+ throw new Error("No response from broker");
497
+ } finally {
498
+ try {
499
+ if (_existsSync(tmpFile)) {
500
+ _unlinkSync(tmpFile);
501
+ }
502
+ } catch {
503
+ }
504
+ }
505
+ }
506
+ /**
507
+ * Synchronous HTTP request using curl
508
+ */
509
+ httpRequestSync(method, params) {
510
+ const url = `http://${this.httpHost}:${this.httpPort}/rpc`;
511
+ const id = (0, import_node_crypto.randomUUID)();
512
+ const request = JSON.stringify({
513
+ jsonrpc: "2.0",
514
+ id,
515
+ method,
516
+ params
517
+ });
518
+ try {
519
+ const result = _execSync(
520
+ `curl -s -X POST -H "Content-Type: application/json" -d '${request.replace(/'/g, "\\'")}' "${url}"`,
521
+ {
522
+ timeout: this.timeout,
523
+ encoding: "utf-8"
524
+ }
525
+ );
526
+ const response = JSON.parse(result);
527
+ if (response.error) {
528
+ throw new Error(response.error.message);
529
+ }
530
+ return response.result;
531
+ } catch (error) {
532
+ throw new Error(`Sync request failed: ${error.message}`);
533
+ }
534
+ }
535
+ /**
536
+ * Check if broker is available
537
+ */
538
+ ping() {
539
+ try {
540
+ this.request("ping", {});
541
+ return true;
542
+ } catch {
543
+ return false;
544
+ }
545
+ }
546
+ };
547
+
548
+ // libs/shield-interceptor/src/interceptors/child-process.ts
549
+ var childProcessModule = require("node:child_process");
550
+ var ChildProcessInterceptor = class extends BaseInterceptor {
551
+ syncClient;
552
+ originalExec = null;
553
+ originalExecSync = null;
554
+ originalSpawn = null;
555
+ originalSpawnSync = null;
556
+ originalExecFile = null;
557
+ originalFork = null;
558
+ constructor(options) {
559
+ super(options);
560
+ this.syncClient = new SyncClient({
561
+ socketPath: "/var/run/agenshield/agenshield.sock",
562
+ httpHost: "localhost",
563
+ httpPort: 5201,
564
+ // Broker uses 5201
565
+ timeout: 3e4
566
+ });
567
+ }
568
+ install() {
569
+ if (this.installed) return;
570
+ this.originalExec = childProcessModule.exec;
571
+ this.originalExecSync = childProcessModule.execSync;
572
+ this.originalSpawn = childProcessModule.spawn;
573
+ this.originalSpawnSync = childProcessModule.spawnSync;
574
+ this.originalExecFile = childProcessModule.execFile;
575
+ this.originalFork = childProcessModule.fork;
576
+ childProcessModule.exec = this.createInterceptedExec();
577
+ childProcessModule.execSync = this.createInterceptedExecSync();
578
+ childProcessModule.spawn = this.createInterceptedSpawn();
579
+ childProcessModule.spawnSync = this.createInterceptedSpawnSync();
580
+ childProcessModule.execFile = this.createInterceptedExecFile();
581
+ childProcessModule.fork = this.createInterceptedFork();
582
+ this.installed = true;
583
+ }
584
+ uninstall() {
585
+ if (!this.installed) return;
586
+ if (this.originalExec) childProcessModule.exec = this.originalExec;
587
+ if (this.originalExecSync) childProcessModule.execSync = this.originalExecSync;
588
+ if (this.originalSpawn) childProcessModule.spawn = this.originalSpawn;
589
+ if (this.originalSpawnSync) childProcessModule.spawnSync = this.originalSpawnSync;
590
+ if (this.originalExecFile) childProcessModule.execFile = this.originalExecFile;
591
+ if (this.originalFork) childProcessModule.fork = this.originalFork;
592
+ this.originalExec = null;
593
+ this.originalExecSync = null;
594
+ this.originalSpawn = null;
595
+ this.originalSpawnSync = null;
596
+ this.originalExecFile = null;
597
+ this.originalFork = null;
598
+ this.installed = false;
599
+ }
600
+ createInterceptedExec() {
601
+ const self = this;
602
+ const original = this.originalExec;
603
+ return function interceptedExec(command, ...args) {
604
+ const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
605
+ self.eventReporter.intercept("exec", command);
606
+ self.checkPolicy("exec", command).then(() => {
607
+ original(command, ...args, callback);
608
+ }).catch((error) => {
609
+ if (callback) {
610
+ callback(error, "", "");
611
+ }
612
+ });
613
+ return original('echo ""');
614
+ };
615
+ }
616
+ createInterceptedExecSync() {
617
+ const self = this;
618
+ const original = this.originalExecSync;
619
+ const interceptedExecSync = function(command, options) {
620
+ self.eventReporter.intercept("exec", command);
621
+ try {
622
+ const result = self.syncClient.request(
623
+ "policy_check",
624
+ { operation: "exec", target: command }
625
+ );
626
+ if (!result.allowed) {
627
+ throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
628
+ operation: "exec",
629
+ target: command
630
+ });
631
+ }
632
+ } catch (error) {
633
+ if (error instanceof PolicyDeniedError) {
634
+ throw error;
635
+ }
636
+ if (!self.failOpen) {
637
+ throw error;
638
+ }
639
+ }
640
+ return original(command, options);
641
+ };
642
+ return interceptedExecSync;
643
+ }
644
+ createInterceptedSpawn() {
645
+ const self = this;
646
+ const original = this.originalSpawn;
647
+ const interceptedSpawn = function(command, args, options) {
648
+ const fullCommand = args ? `${command} ${args.join(" ")}` : command;
649
+ self.eventReporter.intercept("exec", fullCommand);
650
+ self.checkPolicy("exec", fullCommand).catch((error) => {
651
+ self.eventReporter.error("exec", fullCommand, error.message);
652
+ });
653
+ return original(command, args, options || {});
654
+ };
655
+ return interceptedSpawn;
656
+ }
657
+ createInterceptedSpawnSync() {
658
+ const self = this;
659
+ const original = this.originalSpawnSync;
660
+ return function interceptedSpawnSync(command, args, options) {
661
+ const fullCommand = args ? `${command} ${args.join(" ")}` : command;
662
+ self.eventReporter.intercept("exec", fullCommand);
663
+ try {
664
+ const result = self.syncClient.request(
665
+ "policy_check",
666
+ { operation: "exec", target: fullCommand }
667
+ );
668
+ if (!result.allowed) {
669
+ return {
670
+ pid: -1,
671
+ output: [],
672
+ stdout: Buffer.alloc(0),
673
+ stderr: Buffer.from(result.reason || "Policy denied"),
674
+ status: 1,
675
+ signal: null,
676
+ error: new PolicyDeniedError(result.reason || "Policy denied")
677
+ };
678
+ }
679
+ } catch (error) {
680
+ if (!self.failOpen) {
681
+ return {
682
+ pid: -1,
683
+ output: [],
684
+ stdout: Buffer.alloc(0),
685
+ stderr: Buffer.from(error.message),
686
+ status: 1,
687
+ signal: null,
688
+ error
689
+ };
690
+ }
691
+ }
692
+ return original(command, args, options);
693
+ };
694
+ }
695
+ createInterceptedExecFile() {
696
+ const self = this;
697
+ const original = this.originalExecFile;
698
+ return function interceptedExecFile(file, ...args) {
699
+ self.eventReporter.intercept("exec", file);
700
+ self.checkPolicy("exec", file).catch((error) => {
701
+ self.eventReporter.error("exec", file, error.message);
702
+ });
703
+ return original(file, ...args);
704
+ };
705
+ }
706
+ createInterceptedFork() {
707
+ const self = this;
708
+ const original = this.originalFork;
709
+ const interceptedFork = function(modulePath, args, options) {
710
+ const pathStr = modulePath.toString();
711
+ self.eventReporter.intercept("exec", `fork:${pathStr}`);
712
+ self.checkPolicy("exec", `fork:${pathStr}`).catch((error) => {
713
+ self.eventReporter.error("exec", pathStr, error.message);
714
+ });
715
+ return original(modulePath, args, options);
716
+ };
717
+ return interceptedFork;
718
+ }
719
+ };
720
+
721
+ // libs/shield-interceptor/src/interceptors/fs.ts
722
+ var fsModule = require("node:fs");
723
+ var fsPromisesModule = require("node:fs/promises");
724
+ function safeOverride(target, prop, value) {
725
+ try {
726
+ target[prop] = value;
727
+ } catch {
728
+ Object.defineProperty(target, prop, {
729
+ value,
730
+ writable: true,
731
+ configurable: true,
732
+ enumerable: true
733
+ });
734
+ }
735
+ }
736
+ var FsInterceptor = class extends BaseInterceptor {
737
+ syncClient;
738
+ originals = /* @__PURE__ */ new Map();
739
+ constructor(options) {
740
+ super(options);
741
+ this.syncClient = new SyncClient({
742
+ socketPath: "/var/run/agenshield/agenshield.sock",
743
+ httpHost: "localhost",
744
+ httpPort: 5201,
745
+ // Broker uses 5201
746
+ timeout: 3e4
747
+ });
748
+ }
749
+ install() {
750
+ if (this.installed) return;
751
+ this.interceptMethod(fsModule, "readFile", "file_read");
752
+ this.interceptMethod(fsModule, "writeFile", "file_write");
753
+ this.interceptMethod(fsModule, "appendFile", "file_write");
754
+ this.interceptMethod(fsModule, "unlink", "file_write");
755
+ this.interceptMethod(fsModule, "mkdir", "file_write");
756
+ this.interceptMethod(fsModule, "rmdir", "file_write");
757
+ this.interceptMethod(fsModule, "rm", "file_write");
758
+ this.interceptMethod(fsModule, "readdir", "file_list");
759
+ this.interceptSyncMethod(fsModule, "readFileSync", "file_read");
760
+ this.interceptSyncMethod(fsModule, "writeFileSync", "file_write");
761
+ this.interceptSyncMethod(fsModule, "appendFileSync", "file_write");
762
+ this.interceptSyncMethod(fsModule, "unlinkSync", "file_write");
763
+ this.interceptSyncMethod(fsModule, "mkdirSync", "file_write");
764
+ this.interceptSyncMethod(fsModule, "rmdirSync", "file_write");
765
+ this.interceptSyncMethod(fsModule, "rmSync", "file_write");
766
+ this.interceptSyncMethod(fsModule, "readdirSync", "file_list");
767
+ this.interceptPromiseMethod(fsPromisesModule, "readFile", "file_read");
768
+ this.interceptPromiseMethod(fsPromisesModule, "writeFile", "file_write");
769
+ this.interceptPromiseMethod(fsPromisesModule, "appendFile", "file_write");
770
+ this.interceptPromiseMethod(fsPromisesModule, "unlink", "file_write");
771
+ this.interceptPromiseMethod(fsPromisesModule, "mkdir", "file_write");
772
+ this.interceptPromiseMethod(fsPromisesModule, "rmdir", "file_write");
773
+ this.interceptPromiseMethod(fsPromisesModule, "rm", "file_write");
774
+ this.interceptPromiseMethod(fsPromisesModule, "readdir", "file_list");
775
+ this.installed = true;
776
+ }
777
+ uninstall() {
778
+ if (!this.installed) return;
779
+ for (const [key, original] of this.originals) {
780
+ const [moduleName, methodName] = key.split(":");
781
+ const module2 = moduleName === "fs" ? fsModule : fsPromisesModule;
782
+ safeOverride(module2, methodName, original);
783
+ }
784
+ this.originals.clear();
785
+ this.installed = false;
786
+ }
787
+ interceptMethod(module2, methodName, operation) {
788
+ const original = module2[methodName];
789
+ if (!original) return;
790
+ const key = `fs:${methodName}`;
791
+ this.originals.set(key, original);
792
+ const self = this;
793
+ safeOverride(module2, methodName, function intercepted(path, ...args) {
794
+ const pathString = path.toString();
795
+ const callback = typeof args[args.length - 1] === "function" ? args.pop() : void 0;
796
+ self.eventReporter.intercept(operation, pathString);
797
+ self.checkPolicy(operation, pathString).then(() => {
798
+ original.call(module2, path, ...args, callback);
799
+ }).catch((error) => {
800
+ if (callback) {
801
+ callback(error);
802
+ }
803
+ });
804
+ });
805
+ }
806
+ interceptSyncMethod(module2, methodName, operation) {
807
+ const original = module2[methodName];
808
+ if (!original) return;
809
+ const key = `fs:${methodName}`;
810
+ this.originals.set(key, original);
811
+ const self = this;
812
+ safeOverride(module2, methodName, function interceptedSync(path, ...args) {
813
+ const pathString = path.toString();
814
+ self.eventReporter.intercept(operation, pathString);
815
+ try {
816
+ const result = self.syncClient.request(
817
+ "policy_check",
818
+ { operation, target: pathString }
819
+ );
820
+ if (!result.allowed) {
821
+ throw new PolicyDeniedError(result.reason || "Operation denied by policy", {
822
+ operation,
823
+ target: pathString
824
+ });
825
+ }
826
+ } catch (error) {
827
+ if (error instanceof PolicyDeniedError) {
828
+ throw error;
829
+ }
830
+ if (!self.failOpen) {
831
+ throw error;
832
+ }
833
+ }
834
+ return original.call(module2, path, ...args);
835
+ });
836
+ }
837
+ interceptPromiseMethod(module2, methodName, operation) {
838
+ const original = module2[methodName];
839
+ if (!original) return;
840
+ const key = `fsPromises:${methodName}`;
841
+ this.originals.set(key, original);
842
+ const self = this;
843
+ safeOverride(module2, methodName, async function interceptedPromise(path, ...args) {
844
+ const pathString = path.toString();
845
+ self.eventReporter.intercept(operation, pathString);
846
+ await self.checkPolicy(operation, pathString);
847
+ return original.call(module2, path, ...args);
848
+ });
849
+ }
850
+ };
851
+
852
+ // libs/shield-interceptor/src/client/http-client.ts
853
+ var net = __toESM(require("node:net"), 1);
854
+ var import_node_crypto2 = require("node:crypto");
855
+ var AsyncClient = class {
856
+ socketPath;
857
+ httpHost;
858
+ httpPort;
859
+ timeout;
860
+ constructor(options) {
861
+ this.socketPath = options.socketPath;
862
+ this.httpHost = options.httpHost;
863
+ this.httpPort = options.httpPort;
864
+ this.timeout = options.timeout;
865
+ }
866
+ /**
867
+ * Send a request to the broker
868
+ */
869
+ async request(method, params) {
870
+ try {
871
+ return await this.socketRequest(method, params);
872
+ } catch (socketError) {
873
+ try {
874
+ return await this.httpRequest(method, params);
875
+ } catch (httpError) {
876
+ throw new BrokerUnavailableError(
877
+ `Failed to connect to broker: ${socketError.message}`
878
+ );
879
+ }
880
+ }
881
+ }
882
+ /**
883
+ * Send request via Unix socket
884
+ */
885
+ async socketRequest(method, params) {
886
+ return new Promise((resolve, reject) => {
887
+ const socket = net.createConnection(this.socketPath);
888
+ const id = (0, import_node_crypto2.randomUUID)();
889
+ let responseData = "";
890
+ let timeoutId;
891
+ socket.on("connect", () => {
892
+ const request = {
893
+ jsonrpc: "2.0",
894
+ id,
895
+ method,
896
+ params
897
+ };
898
+ socket.write(JSON.stringify(request) + "\n");
899
+ timeoutId = setTimeout(() => {
900
+ socket.destroy();
901
+ reject(new TimeoutError());
902
+ }, this.timeout);
903
+ });
904
+ socket.on("data", (data) => {
905
+ responseData += data.toString();
906
+ const newlineIndex = responseData.indexOf("\n");
907
+ if (newlineIndex !== -1) {
908
+ clearTimeout(timeoutId);
909
+ socket.end();
910
+ try {
911
+ const response = JSON.parse(
912
+ responseData.slice(0, newlineIndex)
913
+ );
914
+ if (response.error) {
915
+ reject(new Error(response.error.message));
916
+ } else {
917
+ resolve(response.result);
918
+ }
919
+ } catch {
920
+ reject(new Error("Invalid response from broker"));
921
+ }
922
+ }
923
+ });
924
+ socket.on("error", (error) => {
925
+ clearTimeout(timeoutId);
926
+ reject(error);
927
+ });
928
+ });
929
+ }
930
+ /**
931
+ * Send request via HTTP
932
+ */
933
+ async httpRequest(method, params) {
934
+ const url = `http://${this.httpHost}:${this.httpPort}/rpc`;
935
+ const id = (0, import_node_crypto2.randomUUID)();
936
+ const request = {
937
+ jsonrpc: "2.0",
938
+ id,
939
+ method,
940
+ params
941
+ };
942
+ const controller = new AbortController();
943
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
944
+ try {
945
+ const response = await fetch(url, {
946
+ method: "POST",
947
+ headers: { "Content-Type": "application/json" },
948
+ body: JSON.stringify(request),
949
+ signal: controller.signal
950
+ });
951
+ clearTimeout(timeoutId);
952
+ if (!response.ok) {
953
+ throw new Error(`HTTP error: ${response.status}`);
954
+ }
955
+ const jsonResponse = await response.json();
956
+ if (jsonResponse.error) {
957
+ throw new Error(jsonResponse.error.message);
958
+ }
959
+ return jsonResponse.result;
960
+ } catch (error) {
961
+ clearTimeout(timeoutId);
962
+ if (error.name === "AbortError") {
963
+ throw new TimeoutError();
964
+ }
965
+ throw error;
966
+ }
967
+ }
968
+ /**
969
+ * Check if broker is available
970
+ */
971
+ async ping() {
972
+ try {
973
+ await this.request("ping", {});
974
+ return true;
975
+ } catch {
976
+ return false;
977
+ }
978
+ }
979
+ };
980
+
981
+ // libs/shield-interceptor/src/policy/evaluator.ts
982
+ var PolicyEvaluator = class {
983
+ client;
984
+ constructor(options) {
985
+ this.client = options.client;
986
+ }
987
+ /**
988
+ * Check if an operation is allowed
989
+ * Always queries the daemon for fresh policy decisions
990
+ */
991
+ async check(operation, target) {
992
+ try {
993
+ const result = await this.client.request(
994
+ "policy_check",
995
+ { operation, target }
996
+ );
997
+ return result;
998
+ } catch (error) {
999
+ return {
1000
+ allowed: false,
1001
+ reason: `Policy check failed: ${error.message}`
1002
+ };
1003
+ }
1004
+ }
1005
+ };
1006
+
1007
+ // libs/shield-interceptor/src/events/reporter.ts
1008
+ var EventReporter = class _EventReporter {
1009
+ client;
1010
+ logLevel;
1011
+ queue = [];
1012
+ flushInterval = null;
1013
+ failedFlushCount = 0;
1014
+ static MAX_QUEUE_SIZE = 500;
1015
+ static MAX_RETRIES = 3;
1016
+ levelPriority = {
1017
+ debug: 0,
1018
+ info: 1,
1019
+ warn: 2,
1020
+ error: 3
1021
+ };
1022
+ constructor(options) {
1023
+ this.client = options.client;
1024
+ this.logLevel = options.logLevel;
1025
+ this.flushInterval = setInterval(() => this.flush(), 5e3);
1026
+ }
1027
+ /**
1028
+ * Report an event
1029
+ */
1030
+ report(event) {
1031
+ this.queue.push(event);
1032
+ if (this.queue.length > _EventReporter.MAX_QUEUE_SIZE) {
1033
+ this.queue.splice(0, this.queue.length - _EventReporter.MAX_QUEUE_SIZE);
1034
+ }
1035
+ const level = this.getLogLevel(event);
1036
+ if (this.shouldLog(level)) {
1037
+ const prefix = event.type === "allow" ? "\u2713" : event.type === "deny" ? "\u2717" : "\u2022";
1038
+ console[level](`[AgenShield] ${prefix} ${event.operation}: ${event.target}`);
1039
+ }
1040
+ if (this.queue.length >= 100) {
1041
+ this.flush();
1042
+ }
1043
+ }
1044
+ /**
1045
+ * Report an interception
1046
+ */
1047
+ intercept(operation, target) {
1048
+ this.report({
1049
+ type: "intercept",
1050
+ operation,
1051
+ target,
1052
+ timestamp: /* @__PURE__ */ new Date()
1053
+ });
1054
+ }
1055
+ /**
1056
+ * Report an allowed operation
1057
+ */
1058
+ allow(operation, target, policyId, duration) {
1059
+ this.report({
1060
+ type: "allow",
1061
+ operation,
1062
+ target,
1063
+ timestamp: /* @__PURE__ */ new Date(),
1064
+ policyId,
1065
+ duration
1066
+ });
1067
+ }
1068
+ /**
1069
+ * Report a denied operation
1070
+ */
1071
+ deny(operation, target, policyId, reason) {
1072
+ this.report({
1073
+ type: "deny",
1074
+ operation,
1075
+ target,
1076
+ timestamp: /* @__PURE__ */ new Date(),
1077
+ policyId,
1078
+ error: reason
1079
+ });
1080
+ }
1081
+ /**
1082
+ * Report an error
1083
+ */
1084
+ error(operation, target, error) {
1085
+ this.report({
1086
+ type: "error",
1087
+ operation,
1088
+ target,
1089
+ timestamp: /* @__PURE__ */ new Date(),
1090
+ error
1091
+ });
1092
+ }
1093
+ /**
1094
+ * Flush the event queue
1095
+ */
1096
+ async flush() {
1097
+ if (this.queue.length === 0) return;
1098
+ const events = this.queue.splice(0, this.queue.length);
1099
+ try {
1100
+ await this.client.request("events_batch", { events });
1101
+ this.failedFlushCount = 0;
1102
+ } catch {
1103
+ this.failedFlushCount++;
1104
+ if (this.failedFlushCount < _EventReporter.MAX_RETRIES) {
1105
+ this.queue.unshift(...events);
1106
+ if (this.queue.length > _EventReporter.MAX_QUEUE_SIZE) {
1107
+ this.queue.splice(0, this.queue.length - _EventReporter.MAX_QUEUE_SIZE);
1108
+ }
1109
+ }
1110
+ }
1111
+ }
1112
+ /**
1113
+ * Stop the reporter
1114
+ */
1115
+ stop() {
1116
+ if (this.flushInterval) {
1117
+ clearInterval(this.flushInterval);
1118
+ this.flushInterval = null;
1119
+ }
1120
+ this.flush();
1121
+ }
1122
+ /**
1123
+ * Get the appropriate log level for an event
1124
+ */
1125
+ getLogLevel(event) {
1126
+ switch (event.type) {
1127
+ case "intercept":
1128
+ return "debug";
1129
+ case "allow":
1130
+ return "debug";
1131
+ case "deny":
1132
+ return "warn";
1133
+ case "error":
1134
+ return "error";
1135
+ default:
1136
+ return "info";
1137
+ }
1138
+ }
1139
+ /**
1140
+ * Check if we should log at the given level
1141
+ */
1142
+ shouldLog(level) {
1143
+ return this.levelPriority[level] >= this.levelPriority[this.logLevel];
1144
+ }
1145
+ };
1146
+
1147
+ // libs/shield-interceptor/src/installer.ts
1148
+ var installed = null;
1149
+ var client = null;
1150
+ var policyEvaluator = null;
1151
+ var eventReporter = null;
1152
+ function installInterceptors(configOverrides) {
1153
+ if (installed) {
1154
+ console.warn("AgenShield interceptors already installed");
1155
+ return;
1156
+ }
1157
+ const config = createConfig(configOverrides);
1158
+ client = new AsyncClient({
1159
+ socketPath: config.socketPath,
1160
+ httpHost: config.httpHost,
1161
+ httpPort: config.httpPort,
1162
+ timeout: config.timeout
1163
+ });
1164
+ policyEvaluator = new PolicyEvaluator({
1165
+ client
1166
+ });
1167
+ eventReporter = new EventReporter({
1168
+ client,
1169
+ logLevel: config.logLevel
1170
+ });
1171
+ installed = {};
1172
+ if (config.interceptFetch) {
1173
+ installed.fetch = new FetchInterceptor({
1174
+ client,
1175
+ policyEvaluator,
1176
+ eventReporter,
1177
+ failOpen: config.failOpen
1178
+ });
1179
+ installed.fetch.install();
1180
+ log(config, "debug", "Installed fetch interceptor");
1181
+ }
1182
+ if (config.interceptHttp) {
1183
+ installed.http = new HttpInterceptor({
1184
+ client,
1185
+ policyEvaluator,
1186
+ eventReporter,
1187
+ failOpen: config.failOpen
1188
+ });
1189
+ installed.http.install();
1190
+ log(config, "debug", "Installed http/https interceptor");
1191
+ }
1192
+ if (config.interceptWs) {
1193
+ installed.websocket = new WebSocketInterceptor({
1194
+ client,
1195
+ policyEvaluator,
1196
+ eventReporter,
1197
+ failOpen: config.failOpen
1198
+ });
1199
+ installed.websocket.install();
1200
+ log(config, "debug", "Installed WebSocket interceptor");
1201
+ }
1202
+ if (config.interceptExec) {
1203
+ installed.childProcess = new ChildProcessInterceptor({
1204
+ client,
1205
+ policyEvaluator,
1206
+ eventReporter,
1207
+ failOpen: config.failOpen
1208
+ });
1209
+ installed.childProcess.install();
1210
+ log(config, "debug", "Installed child_process interceptor");
1211
+ }
1212
+ if (config.interceptFs) {
1213
+ installed.fs = new FsInterceptor({
1214
+ client,
1215
+ policyEvaluator,
1216
+ eventReporter,
1217
+ failOpen: config.failOpen
1218
+ });
1219
+ installed.fs.install();
1220
+ log(config, "debug", "Installed fs interceptor");
1221
+ }
1222
+ log(config, "info", "AgenShield interceptors installed");
1223
+ }
1224
+ function uninstallInterceptors() {
1225
+ if (!installed) {
1226
+ return;
1227
+ }
1228
+ if (installed.fetch) {
1229
+ installed.fetch.uninstall();
1230
+ }
1231
+ if (installed.http) {
1232
+ installed.http.uninstall();
1233
+ }
1234
+ if (installed.websocket) {
1235
+ installed.websocket.uninstall();
1236
+ }
1237
+ if (installed.childProcess) {
1238
+ installed.childProcess.uninstall();
1239
+ }
1240
+ if (installed.fs) {
1241
+ installed.fs.uninstall();
1242
+ }
1243
+ installed = null;
1244
+ client = null;
1245
+ policyEvaluator = null;
1246
+ eventReporter = null;
1247
+ }
1248
+ function log(config, level, message) {
1249
+ const levels = { debug: 0, info: 1, warn: 2, error: 3 };
1250
+ if (levels[level] >= levels[config.logLevel]) {
1251
+ console[level](`[AgenShield] ${message}`);
1252
+ }
1253
+ }
1254
+ // Annotate the CommonJS export names for ESM import in node:
1255
+ 0 && (module.exports = {
1256
+ AgenShieldError,
1257
+ AsyncClient,
1258
+ ChildProcessInterceptor,
1259
+ EventReporter,
1260
+ FetchInterceptor,
1261
+ FsInterceptor,
1262
+ HttpInterceptor,
1263
+ PolicyDeniedError,
1264
+ PolicyEvaluator,
1265
+ SyncClient,
1266
+ WebSocketInterceptor,
1267
+ createConfig,
1268
+ installInterceptors,
1269
+ uninstallInterceptors
1270
+ });