trusera-sdk 1.0.0 → 1.1.1
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/dist/index.cjs +202 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +202 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -42,28 +42,56 @@ __export(index_exports, {
|
|
|
42
42
|
module.exports = __toCommonJS(index_exports);
|
|
43
43
|
|
|
44
44
|
// src/client.ts
|
|
45
|
+
var SDK_VERSION = "1.0.0";
|
|
45
46
|
var TruseraClient = class {
|
|
46
47
|
apiKey;
|
|
47
48
|
baseUrl;
|
|
48
49
|
batchSize;
|
|
50
|
+
maxQueueSize;
|
|
49
51
|
flushInterval;
|
|
50
52
|
debug;
|
|
51
53
|
agentId;
|
|
52
54
|
eventQueue = [];
|
|
53
55
|
flushTimer;
|
|
54
56
|
isClosed = false;
|
|
57
|
+
// Fleet auto-registration
|
|
58
|
+
autoRegister;
|
|
59
|
+
agentName;
|
|
60
|
+
agentType;
|
|
61
|
+
environment;
|
|
62
|
+
heartbeatInterval;
|
|
63
|
+
fleetAgentId;
|
|
64
|
+
heartbeatTimer;
|
|
55
65
|
constructor(options) {
|
|
56
66
|
this.apiKey = options.apiKey;
|
|
57
67
|
this.baseUrl = options.baseUrl ?? "https://api.trusera.io";
|
|
58
68
|
this.agentId = options.agentId;
|
|
59
69
|
this.batchSize = options.batchSize ?? 100;
|
|
70
|
+
this.maxQueueSize = options.maxQueueSize ?? 1e4;
|
|
60
71
|
this.flushInterval = options.flushInterval ?? 5e3;
|
|
61
72
|
this.debug = options.debug ?? false;
|
|
73
|
+
const envAuto = (typeof process !== "undefined" ? process.env?.TRUSERA_AUTO_REGISTER : void 0) ?? "";
|
|
74
|
+
if (envAuto.toLowerCase() === "true" || envAuto === "1") {
|
|
75
|
+
this.autoRegister = true;
|
|
76
|
+
} else if (envAuto.toLowerCase() === "false" || envAuto === "0") {
|
|
77
|
+
this.autoRegister = false;
|
|
78
|
+
} else {
|
|
79
|
+
this.autoRegister = options.autoRegister ?? false;
|
|
80
|
+
}
|
|
81
|
+
const hostname = typeof process !== "undefined" && process.env?.HOSTNAME ? process.env.HOSTNAME : typeof globalThis !== "undefined" && "navigator" in globalThis ? "browser" : "unknown";
|
|
82
|
+
this.agentName = options.agentName ?? (typeof process !== "undefined" ? process.env?.TRUSERA_AGENT_NAME : void 0) ?? hostname;
|
|
83
|
+
this.agentType = options.agentType ?? (typeof process !== "undefined" ? process.env?.TRUSERA_AGENT_TYPE : void 0) ?? "";
|
|
84
|
+
this.environment = options.environment ?? (typeof process !== "undefined" ? process.env?.TRUSERA_ENVIRONMENT : void 0) ?? "";
|
|
85
|
+
const envHb = typeof process !== "undefined" ? process.env?.TRUSERA_HEARTBEAT_INTERVAL : void 0;
|
|
86
|
+
this.heartbeatInterval = envHb ? parseInt(envHb, 10) * 1e3 : options.heartbeatInterval ?? 6e4;
|
|
62
87
|
if (!this.apiKey.startsWith("tsk_")) {
|
|
63
88
|
throw new Error("Invalid API key format. Must start with 'tsk_'");
|
|
64
89
|
}
|
|
65
90
|
this.startFlushTimer();
|
|
66
|
-
this.
|
|
91
|
+
if (this.autoRegister) {
|
|
92
|
+
void this.registerWithFleet();
|
|
93
|
+
}
|
|
94
|
+
this.log("TruseraClient initialized", { baseUrl: this.baseUrl, batchSize: this.batchSize, autoRegister: this.autoRegister });
|
|
67
95
|
}
|
|
68
96
|
/**
|
|
69
97
|
* Registers a new agent with Trusera backend.
|
|
@@ -85,7 +113,7 @@ var TruseraClient = class {
|
|
|
85
113
|
name,
|
|
86
114
|
framework,
|
|
87
115
|
metadata: {
|
|
88
|
-
sdk_version:
|
|
116
|
+
sdk_version: SDK_VERSION,
|
|
89
117
|
runtime: "node",
|
|
90
118
|
node_version: process.version
|
|
91
119
|
}
|
|
@@ -115,10 +143,14 @@ var TruseraClient = class {
|
|
|
115
143
|
metadata: {
|
|
116
144
|
...event.metadata,
|
|
117
145
|
agent_id: this.agentId,
|
|
118
|
-
sdk_version:
|
|
146
|
+
sdk_version: SDK_VERSION
|
|
119
147
|
}
|
|
120
148
|
};
|
|
121
149
|
this.eventQueue.push(enrichedEvent);
|
|
150
|
+
while (this.eventQueue.length > this.maxQueueSize) {
|
|
151
|
+
this.eventQueue.shift();
|
|
152
|
+
this.log("Event queue overflow, dropping oldest event");
|
|
153
|
+
}
|
|
122
154
|
this.log("Event tracked", { type: event.type, name: event.name, queueSize: this.eventQueue.length });
|
|
123
155
|
if (this.eventQueue.length >= this.batchSize) {
|
|
124
156
|
void this.flush();
|
|
@@ -167,6 +199,7 @@ var TruseraClient = class {
|
|
|
167
199
|
this.log("Closing client");
|
|
168
200
|
this.isClosed = true;
|
|
169
201
|
this.stopFlushTimer();
|
|
202
|
+
this.stopHeartbeat();
|
|
170
203
|
await this.flush();
|
|
171
204
|
this.log("Client closed");
|
|
172
205
|
}
|
|
@@ -182,6 +215,102 @@ var TruseraClient = class {
|
|
|
182
215
|
getAgentId() {
|
|
183
216
|
return this.agentId;
|
|
184
217
|
}
|
|
218
|
+
// -- Fleet auto-registration --
|
|
219
|
+
getProcessInfo() {
|
|
220
|
+
if (typeof process === "undefined") return {};
|
|
221
|
+
return {
|
|
222
|
+
pid: process.pid,
|
|
223
|
+
argv: process.argv?.slice(0, 2),
|
|
224
|
+
node_version: process.version,
|
|
225
|
+
platform: process.platform,
|
|
226
|
+
arch: process.arch
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
getNetworkInfo() {
|
|
230
|
+
const info = {};
|
|
231
|
+
if (typeof process !== "undefined") {
|
|
232
|
+
try {
|
|
233
|
+
const os = require("os");
|
|
234
|
+
info.hostname = os.hostname?.();
|
|
235
|
+
} catch {
|
|
236
|
+
info.hostname = process.env?.HOSTNAME ?? "unknown";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return info;
|
|
240
|
+
}
|
|
241
|
+
async registerWithFleet() {
|
|
242
|
+
const payload = {
|
|
243
|
+
name: this.agentName,
|
|
244
|
+
discovery_method: "sdk",
|
|
245
|
+
sdk_version: SDK_VERSION,
|
|
246
|
+
hostname: this.agentName,
|
|
247
|
+
process_info: this.getProcessInfo(),
|
|
248
|
+
network_info: this.getNetworkInfo()
|
|
249
|
+
};
|
|
250
|
+
if (this.agentType) payload.framework = this.agentType;
|
|
251
|
+
if (this.environment) payload.environment = this.environment;
|
|
252
|
+
try {
|
|
253
|
+
const response = await fetch(`${this.baseUrl}/api/v1/fleet/register`, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: {
|
|
256
|
+
"Content-Type": "application/json",
|
|
257
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
258
|
+
},
|
|
259
|
+
body: JSON.stringify(payload)
|
|
260
|
+
});
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
this.log("Fleet auto-register failed", { status: response.status });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const data = await response.json();
|
|
266
|
+
const agentData = data.data ?? data;
|
|
267
|
+
const fleetId = agentData.id;
|
|
268
|
+
if (fleetId) {
|
|
269
|
+
this.fleetAgentId = String(fleetId);
|
|
270
|
+
this.startHeartbeat();
|
|
271
|
+
this.log("Fleet auto-register succeeded", { fleetAgentId: this.fleetAgentId });
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
this.log("Fleet auto-register error (continuing without)", { error: String(err) });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
startHeartbeat() {
|
|
278
|
+
this.heartbeatTimer = setInterval(() => {
|
|
279
|
+
void this.sendHeartbeat();
|
|
280
|
+
}, this.heartbeatInterval);
|
|
281
|
+
if (this.heartbeatTimer.unref) {
|
|
282
|
+
this.heartbeatTimer.unref();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
stopHeartbeat() {
|
|
286
|
+
if (this.heartbeatTimer) {
|
|
287
|
+
clearInterval(this.heartbeatTimer);
|
|
288
|
+
this.heartbeatTimer = void 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async sendHeartbeat() {
|
|
292
|
+
if (!this.fleetAgentId) return;
|
|
293
|
+
try {
|
|
294
|
+
const payload = {
|
|
295
|
+
process_info: this.getProcessInfo(),
|
|
296
|
+
network_info: this.getNetworkInfo()
|
|
297
|
+
};
|
|
298
|
+
const response = await fetch(`${this.baseUrl}/api/v1/fleet/${this.fleetAgentId}/heartbeat`, {
|
|
299
|
+
method: "POST",
|
|
300
|
+
headers: {
|
|
301
|
+
"Content-Type": "application/json",
|
|
302
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
303
|
+
},
|
|
304
|
+
body: JSON.stringify(payload)
|
|
305
|
+
});
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
this.log("Fleet heartbeat failed", { status: response.status });
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
this.log("Fleet heartbeat error", { error: String(err) });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// -- Timers --
|
|
185
314
|
startFlushTimer() {
|
|
186
315
|
this.flushTimer = setInterval(() => {
|
|
187
316
|
void this.flush();
|
|
@@ -239,13 +368,47 @@ function tryRequire(moduleName) {
|
|
|
239
368
|
return null;
|
|
240
369
|
}
|
|
241
370
|
}
|
|
371
|
+
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
372
|
+
"authorization",
|
|
373
|
+
"cookie",
|
|
374
|
+
"set-cookie",
|
|
375
|
+
"x-api-key",
|
|
376
|
+
"x-n8n-api-key",
|
|
377
|
+
"proxy-authorization"
|
|
378
|
+
]);
|
|
379
|
+
function redactHeaders(headers) {
|
|
380
|
+
const redacted = {};
|
|
381
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
382
|
+
redacted[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? "[REDACTED]" : value;
|
|
383
|
+
}
|
|
384
|
+
return redacted;
|
|
385
|
+
}
|
|
386
|
+
var MAX_PATTERN_LENGTH = 500;
|
|
387
|
+
var EVIL_REGEX_PATTERNS = /(\.\*){2,}|(\w\+){2,}|\(\[^[^\]]*\]\*\)\*|\(\.\+\)\+/;
|
|
388
|
+
function safeRegExp(pattern) {
|
|
389
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
390
|
+
console.warn(`[Trusera] Exclude pattern too long (${pattern.length} > ${MAX_PATTERN_LENGTH}), skipping`);
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
if (EVIL_REGEX_PATTERNS.test(pattern)) {
|
|
394
|
+
console.warn(`[Trusera] Potentially dangerous regex pattern detected, skipping: ${pattern.slice(0, 50)}`);
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
return new RegExp(pattern);
|
|
399
|
+
} catch (e) {
|
|
400
|
+
console.warn(`[Trusera] Invalid regex pattern, skipping: ${pattern.slice(0, 50)}`);
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
242
404
|
var TruseraInterceptor = class {
|
|
243
405
|
client = null;
|
|
244
406
|
options = {
|
|
245
407
|
enforcement: "log",
|
|
246
408
|
policyUrl: "",
|
|
247
409
|
excludePatterns: [],
|
|
248
|
-
debug: false
|
|
410
|
+
debug: false,
|
|
411
|
+
failClosed: false
|
|
249
412
|
};
|
|
250
413
|
excludeRegexes = [];
|
|
251
414
|
isInstalled = false;
|
|
@@ -282,9 +445,10 @@ var TruseraInterceptor = class {
|
|
|
282
445
|
enforcement: options.enforcement ?? "log",
|
|
283
446
|
policyUrl: options.policyUrl ?? "",
|
|
284
447
|
excludePatterns: options.excludePatterns ?? [],
|
|
285
|
-
debug: options.debug ?? false
|
|
448
|
+
debug: options.debug ?? false,
|
|
449
|
+
failClosed: options.failClosed ?? false
|
|
286
450
|
};
|
|
287
|
-
this.excludeRegexes = this.options.excludePatterns.map((pattern) =>
|
|
451
|
+
this.excludeRegexes = this.options.excludePatterns.map((pattern) => safeRegExp(pattern)).filter((r) => r !== null);
|
|
288
452
|
if (originalFetch === null) {
|
|
289
453
|
originalFetch = globalThis.fetch;
|
|
290
454
|
}
|
|
@@ -331,7 +495,7 @@ var TruseraInterceptor = class {
|
|
|
331
495
|
const event = createEvent(
|
|
332
496
|
"api_call" /* API_CALL */,
|
|
333
497
|
eventName,
|
|
334
|
-
{ method, url, headers },
|
|
498
|
+
{ method, url, headers: redactHeaders(headers) },
|
|
335
499
|
{ interception_mode: this.options.enforcement }
|
|
336
500
|
);
|
|
337
501
|
if (this.options.policyUrl) {
|
|
@@ -606,7 +770,7 @@ var TruseraInterceptor = class {
|
|
|
606
770
|
response.headers.forEach((v, k) => {
|
|
607
771
|
h[k] = v;
|
|
608
772
|
});
|
|
609
|
-
return h;
|
|
773
|
+
return redactHeaders(h);
|
|
610
774
|
})()
|
|
611
775
|
}
|
|
612
776
|
);
|
|
@@ -651,7 +815,7 @@ var TruseraInterceptor = class {
|
|
|
651
815
|
body = "[Binary data]";
|
|
652
816
|
}
|
|
653
817
|
}
|
|
654
|
-
return { headers, body };
|
|
818
|
+
return { headers: redactHeaders(headers), body };
|
|
655
819
|
}
|
|
656
820
|
/**
|
|
657
821
|
* Evaluates request against Cedar policies.
|
|
@@ -678,11 +842,21 @@ var TruseraInterceptor = class {
|
|
|
678
842
|
});
|
|
679
843
|
if (!response.ok) {
|
|
680
844
|
console.error(`[Trusera] Policy evaluation failed: ${response.status}`);
|
|
845
|
+
if (this.options.failClosed && this.options.enforcement === "block") {
|
|
846
|
+
console.warn("[Trusera] FAIL-CLOSED: Policy service returned error, denying request");
|
|
847
|
+
return { decision: "Deny", reasons: [`Policy service error: ${response.status}`] };
|
|
848
|
+
}
|
|
849
|
+
console.warn("[Trusera] FAIL-OPEN: Policy service returned error, allowing request");
|
|
681
850
|
return { decision: "Allow" };
|
|
682
851
|
}
|
|
683
852
|
return await response.json();
|
|
684
853
|
} catch (error) {
|
|
685
854
|
console.error("[Trusera] Policy evaluation error:", error);
|
|
855
|
+
if (this.options.failClosed && this.options.enforcement === "block") {
|
|
856
|
+
console.warn("[Trusera] FAIL-CLOSED: Policy evaluation failed, denying request");
|
|
857
|
+
return { decision: "Deny", reasons: ["Policy evaluation failed (fail-closed mode)"] };
|
|
858
|
+
}
|
|
859
|
+
console.warn("[Trusera] FAIL-OPEN: Policy evaluation failed, allowing request");
|
|
686
860
|
return { decision: "Allow" };
|
|
687
861
|
}
|
|
688
862
|
}
|
|
@@ -887,6 +1061,24 @@ var CedarEvaluator = class {
|
|
|
887
1061
|
};
|
|
888
1062
|
|
|
889
1063
|
// src/standalone.ts
|
|
1064
|
+
var MAX_PATTERN_LENGTH2 = 500;
|
|
1065
|
+
var EVIL_REGEX_PATTERNS2 = /(\.\*){2,}|(\w\+){2,}|\(\[^[^\]]*\]\*\)\*|\(\.\+\)\+/;
|
|
1066
|
+
function safeRegExp2(pattern) {
|
|
1067
|
+
if (pattern.length > MAX_PATTERN_LENGTH2) {
|
|
1068
|
+
console.warn(`[Trusera Standalone] Exclude pattern too long, skipping`);
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
if (EVIL_REGEX_PATTERNS2.test(pattern)) {
|
|
1072
|
+
console.warn(`[Trusera Standalone] Potentially dangerous regex pattern, skipping`);
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
try {
|
|
1076
|
+
return new RegExp(pattern);
|
|
1077
|
+
} catch {
|
|
1078
|
+
console.warn(`[Trusera Standalone] Invalid regex pattern, skipping`);
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
890
1082
|
var activeStandaloneInterceptor = null;
|
|
891
1083
|
var originalFetch2 = null;
|
|
892
1084
|
var StandaloneInterceptor = class {
|
|
@@ -903,9 +1095,7 @@ var StandaloneInterceptor = class {
|
|
|
903
1095
|
excludePatterns: options.excludePatterns ?? [],
|
|
904
1096
|
debug: options.debug ?? false
|
|
905
1097
|
};
|
|
906
|
-
this.excludeRegexes = this.options.excludePatterns.map(
|
|
907
|
-
(pattern) => new RegExp(pattern)
|
|
908
|
-
);
|
|
1098
|
+
this.excludeRegexes = this.options.excludePatterns.map((pattern) => safeRegExp2(pattern)).filter((r) => r !== null);
|
|
909
1099
|
}
|
|
910
1100
|
/**
|
|
911
1101
|
* Installs the standalone interceptor.
|