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