@shieldpress/tracker 1.0.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/dist/index.mjs ADDED
@@ -0,0 +1,1403 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/config.ts
9
+ var DEFAULT_API_URL = "https://api.shieldpress.net";
10
+ function resolveConfig(config) {
11
+ if (!config.apiKey) throw new Error("[ShieldPress] apiKey is required");
12
+ if (!config.siteId) throw new Error("[ShieldPress] siteId is required");
13
+ return {
14
+ apiKey: config.apiKey,
15
+ siteId: config.siteId,
16
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
17
+ reportInterval: config.reportInterval ?? 60,
18
+ systemMetrics: config.systemMetrics ?? true,
19
+ httpTracking: config.httpTracking ?? true,
20
+ errorTracking: config.errorTracking ?? true,
21
+ securityTracking: config.securityTracking ?? true,
22
+ depAudit: config.depAudit ?? true,
23
+ depAuditInterval: config.depAuditInterval ?? 3600,
24
+ runtimeMetrics: config.runtimeMetrics ?? true,
25
+ envSecurity: config.envSecurity ?? true,
26
+ heartbeat: config.heartbeat ?? true,
27
+ appName: config.appName || "node-app",
28
+ appVersion: config.appVersion || "0.0.0",
29
+ environment: config.environment || process.env.NODE_ENV || "production",
30
+ tags: config.tags || {},
31
+ ignorePaths: config.ignorePaths || [
32
+ "/health",
33
+ "/healthz",
34
+ "/ready",
35
+ "/favicon.ico",
36
+ "/_next/static/*",
37
+ "/_next/image/*"
38
+ ],
39
+ maxErrorBuffer: config.maxErrorBuffer ?? 100,
40
+ maxSecurityBuffer: config.maxSecurityBuffer ?? 500,
41
+ debug: config.debug ?? false
42
+ };
43
+ }
44
+
45
+ // src/logger.ts
46
+ function createLogger(debug) {
47
+ return {
48
+ info: (...args) => {
49
+ if (debug) console.log("[ShieldPress]", ...args);
50
+ },
51
+ warn: (...args) => {
52
+ console.warn("[ShieldPress]", ...args);
53
+ },
54
+ error: (...args) => {
55
+ console.error("[ShieldPress]", ...args);
56
+ }
57
+ };
58
+ }
59
+
60
+ // src/collectors/system.ts
61
+ import os, { networkInterfaces } from "os";
62
+ import { readFileSync, statfsSync } from "fs";
63
+ function getPrivateIp() {
64
+ try {
65
+ const nets = networkInterfaces();
66
+ for (const name of Object.keys(nets)) {
67
+ for (const net of nets[name] || []) {
68
+ if (net.family === "IPv4" && !net.internal) {
69
+ return net.address;
70
+ }
71
+ }
72
+ }
73
+ return "";
74
+ } catch {
75
+ return "";
76
+ }
77
+ }
78
+ var cachedPublicIp = "";
79
+ var publicIpLastFetch = 0;
80
+ function getPublicIpCached() {
81
+ if (Date.now() - publicIpLastFetch > 6e5) {
82
+ publicIpLastFetch = Date.now();
83
+ try {
84
+ const http2 = __require("https");
85
+ const req = http2.get("https://api.ipify.org", { timeout: 3e3 }, (res) => {
86
+ let data = "";
87
+ res.on("data", (chunk) => {
88
+ data += chunk;
89
+ });
90
+ res.on("end", () => {
91
+ cachedPublicIp = data.trim();
92
+ });
93
+ });
94
+ req.on("error", () => {
95
+ });
96
+ req.on("timeout", () => {
97
+ req.destroy();
98
+ });
99
+ } catch {
100
+ }
101
+ }
102
+ return cachedPublicIp;
103
+ }
104
+ function getDiskUsage() {
105
+ try {
106
+ if (typeof statfsSync === "function") {
107
+ const root = os.platform() === "win32" ? "C:\\" : "/";
108
+ const stats = statfsSync(root);
109
+ const totalBytes = stats.blocks * stats.bsize;
110
+ const freeBytes = stats.bfree * stats.bsize;
111
+ const usedBytes = totalBytes - freeBytes;
112
+ return {
113
+ totalGb: Math.round(totalBytes / 1073741824 * 100) / 100,
114
+ usedGb: Math.round(usedBytes / 1073741824 * 100) / 100,
115
+ percent: totalBytes > 0 ? Math.round(usedBytes / totalBytes * 1e4) / 100 : 0
116
+ };
117
+ }
118
+ if (os.platform() !== "win32") {
119
+ const { execSync } = __require("child_process");
120
+ const output = execSync("df -k / 2>/dev/null", { timeout: 3e3, encoding: "utf-8" });
121
+ const lines = output.trim().split("\n");
122
+ if (lines.length >= 2) {
123
+ const parts = lines[1].split(/\s+/);
124
+ const totalKb = parseInt(parts[1]) || 0;
125
+ const usedKb = parseInt(parts[2]) || 0;
126
+ return {
127
+ totalGb: Math.round(totalKb / 1048576 * 100) / 100,
128
+ usedGb: Math.round(usedKb / 1048576 * 100) / 100,
129
+ percent: totalKb > 0 ? Math.round(usedKb / totalKb * 1e4) / 100 : 0
130
+ };
131
+ }
132
+ }
133
+ return { usedGb: 0, totalGb: 0, percent: 0 };
134
+ } catch {
135
+ return { usedGb: 0, totalGb: 0, percent: 0 };
136
+ }
137
+ }
138
+ function getLinuxOsInfo() {
139
+ try {
140
+ const content = readFileSync("/etc/os-release", "utf-8");
141
+ const lines = content.split("\n");
142
+ let name = "Linux";
143
+ let version = "";
144
+ for (const line of lines) {
145
+ if (line.startsWith("NAME=")) {
146
+ name = line.split("=")[1]?.replace(/"/g, "") || "Linux";
147
+ }
148
+ if (line.startsWith("VERSION_ID=")) {
149
+ version = line.split("=")[1]?.replace(/"/g, "") || "";
150
+ }
151
+ }
152
+ return { name, version };
153
+ } catch {
154
+ return { name: "Linux", version: "" };
155
+ }
156
+ }
157
+ function getOsInfo() {
158
+ const platform = os.platform();
159
+ if (platform === "linux") {
160
+ return getLinuxOsInfo();
161
+ }
162
+ if (platform === "darwin") {
163
+ return { name: "macOS", version: os.release() };
164
+ }
165
+ if (platform === "win32") {
166
+ return { name: "Windows", version: os.release() };
167
+ }
168
+ return { name: platform, version: os.release() };
169
+ }
170
+ var prevCpuTimes = null;
171
+ function getCpuPercent() {
172
+ const cpus = os.cpus();
173
+ let idle = 0;
174
+ let total = 0;
175
+ for (const cpu of cpus) {
176
+ idle += cpu.times.idle;
177
+ total += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq;
178
+ }
179
+ if (!prevCpuTimes) {
180
+ prevCpuTimes = { idle, total };
181
+ return 0;
182
+ }
183
+ const idleDiff = idle - prevCpuTimes.idle;
184
+ const totalDiff = total - prevCpuTimes.total;
185
+ prevCpuTimes = { idle, total };
186
+ if (totalDiff === 0) return 0;
187
+ return Math.round((1 - idleDiff / totalDiff) * 1e4) / 100;
188
+ }
189
+ function collectSystemMetrics() {
190
+ try {
191
+ const mem = process.memoryUsage();
192
+ const totalMem = os.totalmem();
193
+ const freeMem = os.freemem();
194
+ const loadAvg = os.loadavg();
195
+ const osInfo = getOsInfo();
196
+ const disk = getDiskUsage();
197
+ return {
198
+ cpuPercent: getCpuPercent(),
199
+ cpuCount: os.cpus().length,
200
+ memUsedMb: Math.round((totalMem - freeMem) / 1048576),
201
+ memTotalMb: Math.round(totalMem / 1048576),
202
+ memPercent: Math.round((totalMem - freeMem) / totalMem * 1e4) / 100,
203
+ heapUsedMb: Math.round(mem.heapUsed / 1048576),
204
+ heapTotalMb: Math.round(mem.heapTotal / 1048576),
205
+ externalMb: Math.round(mem.external / 1048576),
206
+ rss: Math.round(mem.rss / 1048576),
207
+ uptimeSeconds: Math.round(process.uptime()),
208
+ loadAvg,
209
+ platform: os.platform(),
210
+ nodeVersion: process.version,
211
+ pid: process.pid,
212
+ osName: osInfo.name,
213
+ osVersion: osInfo.version,
214
+ osKernel: os.release(),
215
+ osArch: os.arch(),
216
+ osRelease: (() => {
217
+ try {
218
+ return os.version();
219
+ } catch {
220
+ return os.release();
221
+ }
222
+ })(),
223
+ diskUsedGb: disk.usedGb,
224
+ diskTotalGb: disk.totalGb,
225
+ diskPercent: disk.percent,
226
+ // Network
227
+ publicIp: getPublicIpCached(),
228
+ privateIp: getPrivateIp(),
229
+ hostname: os.hostname(),
230
+ // Process details
231
+ ppid: process.ppid || 0,
232
+ uid: process.getuid?.() ?? null,
233
+ cwd: process.cwd(),
234
+ execPath: process.execPath,
235
+ nodeArgs: process.execArgv.join(" ")
236
+ };
237
+ } catch {
238
+ return {
239
+ cpuPercent: 0,
240
+ cpuCount: 0,
241
+ memUsedMb: 0,
242
+ memTotalMb: 0,
243
+ memPercent: 0,
244
+ heapUsedMb: 0,
245
+ heapTotalMb: 0,
246
+ externalMb: 0,
247
+ rss: 0,
248
+ uptimeSeconds: 0,
249
+ loadAvg: [0, 0, 0],
250
+ platform: process.platform || "unknown",
251
+ nodeVersion: process.version || "unknown",
252
+ pid: process.pid || 0,
253
+ osName: "unknown",
254
+ osVersion: "",
255
+ osKernel: "",
256
+ osArch: "",
257
+ osRelease: "",
258
+ diskUsedGb: 0,
259
+ diskTotalGb: 0,
260
+ diskPercent: 0,
261
+ publicIp: "",
262
+ privateIp: "",
263
+ hostname: "",
264
+ ppid: 0,
265
+ uid: null,
266
+ cwd: "",
267
+ execPath: "",
268
+ nodeArgs: ""
269
+ };
270
+ }
271
+ }
272
+
273
+ // src/collectors/http.ts
274
+ var HttpCollector = class {
275
+ constructor() {
276
+ this.records = [];
277
+ this.windowStart = Date.now();
278
+ }
279
+ record(req) {
280
+ try {
281
+ if (this.records.length >= 1e4) {
282
+ this.records.splice(0, 1e3);
283
+ }
284
+ this.records.push({ ...req, timestamp: Date.now() });
285
+ } catch {
286
+ }
287
+ }
288
+ flush() {
289
+ try {
290
+ return this._flush();
291
+ } catch {
292
+ this.records = [];
293
+ return null;
294
+ }
295
+ }
296
+ _flush() {
297
+ if (this.records.length === 0) return null;
298
+ const records = this.records;
299
+ this.records = [];
300
+ const windowMs = Date.now() - this.windowStart;
301
+ this.windowStart = Date.now();
302
+ const durations = records.map((r) => r.durationMs).sort((a, b) => a - b);
303
+ const statusCodes = {};
304
+ const pathMap = /* @__PURE__ */ new Map();
305
+ for (const r of records) {
306
+ const key = `${r.statusCode}`;
307
+ statusCodes[key] = (statusCodes[key] || 0) + 1;
308
+ const pathKey = `${r.method}:${r.path}`;
309
+ if (!pathMap.has(pathKey)) pathMap.set(pathKey, []);
310
+ pathMap.get(pathKey).push(r);
311
+ }
312
+ const topPaths = [...pathMap.entries()].map(([key, recs]) => {
313
+ const [method, path] = key.split(":", 2);
314
+ const pathDurations = recs.map((r) => r.durationMs).sort((a, b) => a - b);
315
+ return {
316
+ path,
317
+ method,
318
+ count: recs.length,
319
+ avgMs: Math.round(pathDurations.reduce((a, b) => a + b, 0) / recs.length),
320
+ p95Ms: percentile(pathDurations, 95),
321
+ errorCount: recs.filter((r) => r.statusCode >= 400).length
322
+ };
323
+ }).sort((a, b) => b.count - a.count).slice(0, 20);
324
+ return {
325
+ totalRequests: records.length,
326
+ totalErrors: records.filter((r) => r.statusCode >= 500).length,
327
+ avgResponseMs: Math.round(durations.reduce((a, b) => a + b, 0) / durations.length),
328
+ p50ResponseMs: percentile(durations, 50),
329
+ p95ResponseMs: percentile(durations, 95),
330
+ p99ResponseMs: percentile(durations, 99),
331
+ maxResponseMs: durations[durations.length - 1] || 0,
332
+ statusCodes,
333
+ topPaths,
334
+ requestsPerSecond: Math.round(records.length / (windowMs / 1e3) * 100) / 100
335
+ };
336
+ }
337
+ };
338
+ function percentile(sorted, p) {
339
+ if (sorted.length === 0) return 0;
340
+ const idx = Math.ceil(p / 100 * sorted.length) - 1;
341
+ return sorted[Math.max(0, idx)];
342
+ }
343
+
344
+ // src/collectors/errors.ts
345
+ var ErrorCollector = class {
346
+ constructor(maxBuffer) {
347
+ this.buffer = [];
348
+ this.maxBuffer = maxBuffer;
349
+ }
350
+ capture(error, meta) {
351
+ try {
352
+ if (this.buffer.length >= this.maxBuffer) {
353
+ this.buffer.shift();
354
+ }
355
+ const err = typeof error === "string" ? new Error(error) : error;
356
+ const message = (err.message || "").slice(0, 4096);
357
+ const stack = err.stack ? err.stack.slice(0, 4096) : void 0;
358
+ this.buffer.push({
359
+ message,
360
+ stack,
361
+ type: err.constructor.name || "Error",
362
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
363
+ url: meta?.url ? String(meta.url).slice(0, 2048) : void 0,
364
+ method: meta?.method ? String(meta.method).slice(0, 10) : void 0,
365
+ statusCode: meta?.statusCode,
366
+ meta: meta ? Object.fromEntries(
367
+ Object.entries(meta).filter(([k]) => !["url", "method", "statusCode"].includes(k))
368
+ ) : void 0
369
+ });
370
+ } catch {
371
+ }
372
+ }
373
+ flush() {
374
+ const errors = this.buffer;
375
+ this.buffer = [];
376
+ return errors;
377
+ }
378
+ get size() {
379
+ return this.buffer.length;
380
+ }
381
+ };
382
+
383
+ // src/collectors/security.ts
384
+ var SQL_INJECTION_PATTERNS = [
385
+ /(\b(union|select|insert|update|delete|drop|alter|create|exec|execute)\b.*\b(from|into|table|database|where)\b)/i,
386
+ /(--|;|\/\*|\*\/|xp_|sp_|0x[0-9a-f]+)/i,
387
+ /('|\"|`).*(\bor\b|\band\b).*('|\"|`).*=/i,
388
+ /(\bwaitfor\b\s+\bdelay\b|\bsleep\b\s*\(|\bbenchmark\b\s*\()/i
389
+ ];
390
+ var XSS_PATTERNS = [
391
+ /<script[\s>]/i,
392
+ /javascript\s*:/i,
393
+ /on(load|error|click|mouseover|focus|blur|submit|change|input)\s*=/i,
394
+ /<(iframe|object|embed|form|img)\b[^>]*(src|data|action)\s*=\s*['"]/i,
395
+ /\beval\s*\(/i,
396
+ /\bdocument\.(cookie|location|write)/i
397
+ ];
398
+ var CMD_INJECTION_PATTERNS = [
399
+ /[;&|`$]\s*(cat|ls|rm|mv|cp|wget|curl|nc|bash|sh|python|perl|ruby|node)\b/i,
400
+ /\$\(.*\)/,
401
+ /`[^`]*`/,
402
+ /\|\|\s*\w+/,
403
+ /&&\s*(rm|cat|wget|curl)/i
404
+ ];
405
+ var PATH_TRAVERSAL_PATTERNS = [
406
+ /\.\.\//,
407
+ /\.\.\\/,
408
+ /%2e%2e/i,
409
+ /%252e%252e/i,
410
+ /\.\.\%00/
411
+ ];
412
+ var SUSPICIOUS_PATHS = [
413
+ /\/(wp-admin|wp-login|xmlrpc|wp-content|wp-includes)\b/i,
414
+ /\/(phpmyadmin|adminer|phpinfo|server-status|server-info)\b/i,
415
+ /\.(env|git|svn|htaccess|htpasswd|bak|sql|dump|log|ini|cfg|conf)$/i,
416
+ /\/(\.git|\.svn|\.env|\.aws|\.ssh|\.docker)\b/,
417
+ /\/(config|backup|admin|debug|console|shell|cmd)\b/i,
418
+ /\/(actuator|swagger|api-docs|graphiql)\b/i
419
+ ];
420
+ var BOT_USER_AGENTS = [
421
+ /sqlmap/i,
422
+ /nikto/i,
423
+ /nmap/i,
424
+ /masscan/i,
425
+ /zap\//i,
426
+ /burp/i,
427
+ /dirbuster/i,
428
+ /gobuster/i,
429
+ /wfuzz/i,
430
+ /hydra/i,
431
+ /metasploit/i,
432
+ /nessus/i,
433
+ /openvas/i,
434
+ /acunetix/i,
435
+ /qualys/i,
436
+ /nuclei/i,
437
+ /whatweb/i,
438
+ /wpscan/i,
439
+ /joomscan/i,
440
+ /skipfish/i
441
+ ];
442
+ var SENSITIVE_DATA_PATTERNS = [
443
+ // API keys / tokens
444
+ /(?:api[_-]?key|apikey|access[_-]?token|auth[_-]?token|secret[_-]?key)\s*[=:]\s*['"][a-zA-Z0-9_\-]{20,}/i,
445
+ // AWS keys
446
+ /AKIA[0-9A-Z]{16}/,
447
+ // Private keys
448
+ /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/,
449
+ // JWT tokens
450
+ /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/,
451
+ // Passwords in URLs
452
+ /[?&](password|passwd|pwd|secret|token)=[^&\s]{3,}/i
453
+ ];
454
+ var SecurityCollector = class {
455
+ constructor(maxBuffer) {
456
+ this.events = [];
457
+ this.ipCounter = /* @__PURE__ */ new Map();
458
+ this.maxBuffer = maxBuffer;
459
+ }
460
+ /** Record a security event directly */
461
+ record(event) {
462
+ if (this.events.length >= this.maxBuffer) {
463
+ this.events.shift();
464
+ }
465
+ this.events.push({ ...event, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
466
+ if (event.ip) this.trackIp(event.ip, event.type);
467
+ }
468
+ /** Analyze an incoming HTTP request for security threats */
469
+ analyzeRequest(req) {
470
+ try {
471
+ this._analyzeRequest(req);
472
+ } catch {
473
+ }
474
+ }
475
+ _analyzeRequest(req) {
476
+ const ip = req.ip || "unknown";
477
+ const ua = (typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : "") || "";
478
+ const url = req.url;
479
+ const fullInput = `${url} ${req.body || ""} ${JSON.stringify(req.query || {})}`;
480
+ for (const pattern of PATH_TRAVERSAL_PATTERNS) {
481
+ if (pattern.test(url)) {
482
+ this.record({
483
+ type: "suspicious_path",
484
+ severity: "high",
485
+ message: `Path traversal attempt: ${url.slice(0, 200)}`,
486
+ ip,
487
+ userAgent: ua,
488
+ url,
489
+ method: req.method
490
+ });
491
+ break;
492
+ }
493
+ }
494
+ for (const pattern of SUSPICIOUS_PATHS) {
495
+ if (pattern.test(url)) {
496
+ this.record({
497
+ type: "suspicious_path",
498
+ severity: "medium",
499
+ message: `Suspicious path access: ${url.slice(0, 200)}`,
500
+ ip,
501
+ userAgent: ua,
502
+ url,
503
+ method: req.method
504
+ });
505
+ break;
506
+ }
507
+ }
508
+ for (const pattern of SQL_INJECTION_PATTERNS) {
509
+ if (pattern.test(fullInput)) {
510
+ this.record({
511
+ type: "sql_injection",
512
+ severity: "critical",
513
+ message: `SQL injection pattern detected in request`,
514
+ ip,
515
+ userAgent: ua,
516
+ url,
517
+ method: req.method,
518
+ payload: fullInput.slice(0, 500)
519
+ });
520
+ break;
521
+ }
522
+ }
523
+ for (const pattern of XSS_PATTERNS) {
524
+ if (pattern.test(fullInput)) {
525
+ this.record({
526
+ type: "xss_attempt",
527
+ severity: "high",
528
+ message: `XSS payload detected in request`,
529
+ ip,
530
+ userAgent: ua,
531
+ url,
532
+ method: req.method,
533
+ payload: fullInput.slice(0, 500)
534
+ });
535
+ break;
536
+ }
537
+ }
538
+ for (const pattern of CMD_INJECTION_PATTERNS) {
539
+ if (pattern.test(fullInput)) {
540
+ this.record({
541
+ type: "command_injection",
542
+ severity: "critical",
543
+ message: `Command injection pattern detected`,
544
+ ip,
545
+ userAgent: ua,
546
+ url,
547
+ method: req.method,
548
+ payload: fullInput.slice(0, 500)
549
+ });
550
+ break;
551
+ }
552
+ }
553
+ for (const pattern of BOT_USER_AGENTS) {
554
+ if (pattern.test(ua)) {
555
+ this.record({
556
+ type: "bot_detected",
557
+ severity: "medium",
558
+ message: `Security scanner/bot detected: ${ua.slice(0, 100)}`,
559
+ ip,
560
+ userAgent: ua,
561
+ url,
562
+ method: req.method
563
+ });
564
+ break;
565
+ }
566
+ }
567
+ const contentLength = parseInt(String(req.headers["content-length"] || "0"), 10);
568
+ if (contentLength > 10 * 1024 * 1024) {
569
+ this.record({
570
+ type: "oversized_payload",
571
+ severity: "medium",
572
+ message: `Oversized request: ${(contentLength / 1024 / 1024).toFixed(1)}MB`,
573
+ ip,
574
+ userAgent: ua,
575
+ url,
576
+ method: req.method
577
+ });
578
+ }
579
+ for (const pattern of SENSITIVE_DATA_PATTERNS) {
580
+ if (pattern.test(url)) {
581
+ this.record({
582
+ type: "api_key_exposed",
583
+ severity: "critical",
584
+ message: `Sensitive data exposed in URL`,
585
+ ip,
586
+ url: url.slice(0, 100) + "...[REDACTED]",
587
+ method: req.method
588
+ });
589
+ break;
590
+ }
591
+ }
592
+ this.checkBruteForce(ip);
593
+ }
594
+ /** Analyze response for sensitive data leaks */
595
+ analyzeResponse(data) {
596
+ try {
597
+ if (!data.body) return;
598
+ const contentType = data.headers?.["content-type"] || "";
599
+ if (!contentType.includes("json") && !contentType.includes("html") && !contentType.includes("text")) return;
600
+ const sample = data.body.slice(0, 10240);
601
+ for (const pattern of SENSITIVE_DATA_PATTERNS) {
602
+ if (pattern.test(sample)) {
603
+ this.record({
604
+ type: "sensitive_data_exposed",
605
+ severity: "critical",
606
+ message: `Sensitive data detected in response body for ${data.url}`,
607
+ url: data.url,
608
+ meta: { statusCode: data.statusCode }
609
+ });
610
+ break;
611
+ }
612
+ }
613
+ } catch {
614
+ }
615
+ }
616
+ /** Record an authentication failure */
617
+ recordAuthFailure(data) {
618
+ this.record({
619
+ type: "auth_failure",
620
+ severity: "medium",
621
+ message: `Authentication failed${data.reason ? `: ${data.reason}` : ""}`,
622
+ ...data
623
+ });
624
+ }
625
+ /** Record a rate limit event */
626
+ recordRateLimit(data) {
627
+ this.record({
628
+ type: "rate_limit_hit",
629
+ severity: "low",
630
+ message: `Rate limit triggered${data.limit ? ` (limit: ${data.limit})` : ""}`,
631
+ ...data
632
+ });
633
+ }
634
+ /** Record a CORS violation */
635
+ recordCorsViolation(data) {
636
+ this.record({
637
+ type: "cors_violation",
638
+ severity: "medium",
639
+ message: `CORS policy blocked request from origin: ${data.origin || "unknown"}`,
640
+ ...data
641
+ });
642
+ }
643
+ /** Record a custom security event */
644
+ recordCustom(type, severity, message, meta) {
645
+ this.record({ type, severity, message, meta });
646
+ }
647
+ /** Flush and return aggregated security metrics */
648
+ flush() {
649
+ try {
650
+ if (this.events.length === 0 && this.ipCounter.size === 0) return null;
651
+ const events = this.events;
652
+ this.events = [];
653
+ const bySeverity = {
654
+ critical: 0,
655
+ high: 0,
656
+ medium: 0,
657
+ low: 0,
658
+ info: 0
659
+ };
660
+ const byType = {};
661
+ for (const e of events) {
662
+ bySeverity[e.severity]++;
663
+ byType[e.type] = (byType[e.type] || 0) + 1;
664
+ }
665
+ const topOffendingIps = [...this.ipCounter.entries()].map(([ip, data]) => ({ ip, count: data.count, types: [...data.types] })).sort((a, b) => b.count - a.count).slice(0, 10);
666
+ const cutoff = Date.now() - 36e5;
667
+ for (const [ip, data] of this.ipCounter.entries()) {
668
+ if (data.firstSeen < cutoff) this.ipCounter.delete(ip);
669
+ }
670
+ return {
671
+ totalEvents: events.length,
672
+ bySeverity,
673
+ byType,
674
+ topOffendingIps,
675
+ events: events.slice(-50)
676
+ // Send last 50 events per report
677
+ };
678
+ } catch {
679
+ this.events = [];
680
+ return null;
681
+ }
682
+ }
683
+ trackIp(ip, type) {
684
+ const entry = this.ipCounter.get(ip);
685
+ if (entry) {
686
+ entry.count++;
687
+ entry.types.add(type);
688
+ } else {
689
+ this.ipCounter.set(ip, { count: 1, types: /* @__PURE__ */ new Set([type]), firstSeen: Date.now() });
690
+ }
691
+ }
692
+ checkBruteForce(ip) {
693
+ const entry = this.ipCounter.get(ip);
694
+ if (!entry) return;
695
+ if (entry.count === 50) {
696
+ this.record({
697
+ type: "auth_brute_force",
698
+ severity: "critical",
699
+ message: `Possible brute-force/scan from IP: ${ip} (${entry.count} events)`,
700
+ ip
701
+ });
702
+ }
703
+ }
704
+ };
705
+
706
+ // src/collectors/dep-audit.ts
707
+ import { execFile } from "child_process";
708
+ import { readFile } from "fs/promises";
709
+ import { join } from "path";
710
+ var DepAuditCollector = class {
711
+ constructor(auditIntervalMs, logger) {
712
+ this.lastResult = null;
713
+ this.lastAuditTime = 0;
714
+ this.auditing = false;
715
+ this.auditIntervalMs = auditIntervalMs;
716
+ this.logger = logger;
717
+ }
718
+ /** Run npm audit and parse results */
719
+ async collect() {
720
+ if (Date.now() - this.lastAuditTime < this.auditIntervalMs) {
721
+ return this.lastResult;
722
+ }
723
+ if (this.auditing) return this.lastResult;
724
+ this.auditing = true;
725
+ try {
726
+ const [auditResult, pkgInfo] = await Promise.all([
727
+ this.runNpmAudit(),
728
+ this.readPackageInfo()
729
+ ]);
730
+ this.lastResult = {
731
+ totalDeps: pkgInfo.totalDeps,
732
+ totalVulnerabilities: auditResult.vulnerabilities.length,
733
+ bySeverity: auditResult.bySeverity,
734
+ vulnerabilities: auditResult.vulnerabilities.slice(0, 50),
735
+ // Top 50
736
+ outdatedCount: pkgInfo.outdatedCount,
737
+ lastAuditAt: (/* @__PURE__ */ new Date()).toISOString()
738
+ };
739
+ this.lastAuditTime = Date.now();
740
+ this.logger.info(`Dep audit: ${this.lastResult.totalVulnerabilities} vulnerabilities found`);
741
+ return this.lastResult;
742
+ } catch (err) {
743
+ this.logger.error("Dep audit failed:", err instanceof Error ? err.message : err);
744
+ return this.lastResult;
745
+ } finally {
746
+ this.auditing = false;
747
+ }
748
+ }
749
+ runNpmAudit() {
750
+ return new Promise((resolve) => {
751
+ execFile("npm", ["audit", "--json"], { timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
752
+ try {
753
+ const data = JSON.parse(stdout || "{}");
754
+ const vulnerabilities = [];
755
+ const bySeverity = { critical: 0, high: 0, moderate: 0, low: 0 };
756
+ if (data.vulnerabilities) {
757
+ for (const [name, vuln] of Object.entries(data.vulnerabilities)) {
758
+ const severity = vuln.severity || "low";
759
+ bySeverity[severity] = (bySeverity[severity] || 0) + 1;
760
+ vulnerabilities.push({
761
+ package: name,
762
+ installedVersion: vuln.range || "unknown",
763
+ severity,
764
+ title: vuln.title || vuln.name || name,
765
+ url: vuln.url,
766
+ patchedVersions: vuln.fixAvailable?.version,
767
+ cwe: vuln.cwe ? Array.isArray(vuln.cwe) ? vuln.cwe : [vuln.cwe] : void 0
768
+ });
769
+ }
770
+ }
771
+ const severityOrder = { critical: 0, high: 1, moderate: 2, low: 3 };
772
+ vulnerabilities.sort(
773
+ (a, b) => (severityOrder[a.severity] ?? 4) - (severityOrder[b.severity] ?? 4)
774
+ );
775
+ resolve({ vulnerabilities, bySeverity });
776
+ } catch {
777
+ resolve({ vulnerabilities: [], bySeverity: {} });
778
+ }
779
+ });
780
+ });
781
+ }
782
+ async readPackageInfo() {
783
+ try {
784
+ const lockPath = join(process.cwd(), "package-lock.json");
785
+ const content = await readFile(lockPath, "utf-8");
786
+ const lock = JSON.parse(content);
787
+ const packages = lock.packages || {};
788
+ const totalDeps = Object.keys(packages).filter((k) => k !== "").length;
789
+ return { totalDeps, outdatedCount: 0 };
790
+ } catch {
791
+ try {
792
+ const pkgPath = join(process.cwd(), "package.json");
793
+ const content = await readFile(pkgPath, "utf-8");
794
+ const pkg = JSON.parse(content);
795
+ const deps = Object.keys(pkg.dependencies || {}).length;
796
+ const devDeps = Object.keys(pkg.devDependencies || {}).length;
797
+ return { totalDeps: deps + devDeps, outdatedCount: 0 };
798
+ } catch {
799
+ return { totalDeps: 0, outdatedCount: 0 };
800
+ }
801
+ }
802
+ }
803
+ };
804
+
805
+ // src/collectors/runtime.ts
806
+ import v8 from "v8";
807
+ var lastLagCheck = process.hrtime.bigint();
808
+ var lagSamples = [];
809
+ function startLagSampling() {
810
+ const interval = setInterval(() => {
811
+ const now = process.hrtime.bigint();
812
+ const expectedMs = 100;
813
+ const actualMs = Number(now - lastLagCheck) / 1e6;
814
+ const lag = Math.max(0, actualMs - expectedMs);
815
+ lagSamples.push(lag);
816
+ if (lagSamples.length > 600) lagSamples.shift();
817
+ lastLagCheck = now;
818
+ }, 100);
819
+ interval.unref();
820
+ return interval;
821
+ }
822
+ function collectRuntimeMetrics() {
823
+ try {
824
+ const heapSpaces = v8.getHeapSpaceStatistics();
825
+ const sortedLag = [...lagSamples].sort((a, b) => a - b);
826
+ const avgLag = sortedLag.length > 0 ? sortedLag.reduce((a, b) => a + b, 0) / sortedLag.length : 0;
827
+ const p99Lag = sortedLag.length > 0 ? sortedLag[Math.ceil(sortedLag.length * 0.99) - 1] : 0;
828
+ lagSamples = [];
829
+ let activeHandles = 0;
830
+ let activeRequests = 0;
831
+ try {
832
+ activeHandles = process._getActiveHandles?.()?.length ?? 0;
833
+ activeRequests = process._getActiveRequests?.()?.length ?? 0;
834
+ } catch {
835
+ }
836
+ let gcPausesMsTotal = 0;
837
+ let gcPausesCount = 0;
838
+ try {
839
+ const { performance } = __require("perf_hooks");
840
+ const entries = performance.getEntriesByType("gc");
841
+ for (const entry of entries) {
842
+ gcPausesMsTotal += entry.duration;
843
+ gcPausesCount++;
844
+ }
845
+ performance.clearMarks();
846
+ } catch {
847
+ }
848
+ const heapSpaceInfos = heapSpaces.map((space) => ({
849
+ name: space.space_name,
850
+ sizeMb: Math.round(space.space_size / 1048576 * 100) / 100,
851
+ usedMb: Math.round(space.space_used_size / 1048576 * 100) / 100,
852
+ availableMb: Math.round(space.space_available_size / 1048576 * 100) / 100
853
+ }));
854
+ return {
855
+ eventLoopLagMs: Math.round(avgLag * 100) / 100,
856
+ eventLoopLagP99Ms: Math.round(p99Lag * 100) / 100,
857
+ activeHandles,
858
+ activeRequests,
859
+ gcPausesMsTotal: Math.round(gcPausesMsTotal * 100) / 100,
860
+ gcPausesCount,
861
+ heapSpaces: heapSpaceInfos
862
+ };
863
+ } catch {
864
+ lagSamples = [];
865
+ return {
866
+ eventLoopLagMs: 0,
867
+ eventLoopLagP99Ms: 0,
868
+ activeHandles: 0,
869
+ activeRequests: 0,
870
+ gcPausesMsTotal: 0,
871
+ gcPausesCount: 0,
872
+ heapSpaces: []
873
+ };
874
+ }
875
+ }
876
+
877
+ // src/collectors/env-security.ts
878
+ import { readFile as readFile2, access } from "fs/promises";
879
+ import { join as join2 } from "path";
880
+ import { execFile as execFile2 } from "child_process";
881
+ var SENSITIVE_ENV_PATTERNS = [
882
+ /^(DATABASE_URL|DB_PASSWORD|DB_HOST)/i,
883
+ /^(AWS_SECRET|AWS_ACCESS_KEY|AWS_SESSION_TOKEN)/i,
884
+ /^(STRIPE_SECRET|PAYPAL_SECRET)/i,
885
+ /^(PRIVATE_KEY|SECRET_KEY|ENCRYPTION_KEY|JWT_SECRET)/i,
886
+ /^(SMTP_PASSWORD|MAIL_PASSWORD|EMAIL_PASSWORD)/i,
887
+ /^(GITHUB_TOKEN|GITLAB_TOKEN|NPM_TOKEN)/i,
888
+ /^(REDIS_PASSWORD|REDIS_URL)/i,
889
+ /^(SENTRY_DSN)/i,
890
+ /^(TWILIO_AUTH_TOKEN|SENDGRID_API_KEY)/i
891
+ ];
892
+ var EOL_NODE_MAJOR = [0, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21];
893
+ var RECOMMENDED_HEADERS = [
894
+ { header: "strict-transport-security", recommendation: "Add HSTS header: max-age=31536000; includeSubDomains" },
895
+ { header: "x-content-type-options", recommendation: "Set to 'nosniff' to prevent MIME sniffing" },
896
+ { header: "x-frame-options", recommendation: "Set to 'DENY' or 'SAMEORIGIN' to prevent clickjacking" },
897
+ { header: "x-xss-protection", recommendation: "Set to '0' (let CSP handle it) or '1; mode=block'" },
898
+ { header: "content-security-policy", recommendation: "Add CSP header to prevent XSS and data injection" },
899
+ { header: "referrer-policy", recommendation: "Set to 'strict-origin-when-cross-origin'" },
900
+ { header: "permissions-policy", recommendation: "Restrict browser features: camera, microphone, geolocation" },
901
+ { header: "x-dns-prefetch-control", recommendation: "Set to 'off' for security-sensitive apps" },
902
+ { header: "cross-origin-opener-policy", recommendation: "Set to 'same-origin' for cross-origin isolation" },
903
+ { header: "cross-origin-resource-policy", recommendation: "Set to 'same-origin' or 'cross-origin' as needed" }
904
+ ];
905
+ function getMemoryLimit() {
906
+ try {
907
+ for (const arg of process.execArgv) {
908
+ const match = arg.match(/--max-old-space-size=(\d+)/);
909
+ if (match) return parseInt(match[1]);
910
+ }
911
+ const v82 = __require("v8");
912
+ const heapStats = v82.getHeapStatistics();
913
+ return Math.round(heapStats.heap_size_limit / 1048576);
914
+ } catch {
915
+ return null;
916
+ }
917
+ }
918
+ function getDnsServers() {
919
+ try {
920
+ const dns = __require("dns");
921
+ return dns.getServers?.() || [];
922
+ } catch {
923
+ return [];
924
+ }
925
+ }
926
+ var EnvSecurityCollector = class {
927
+ constructor() {
928
+ this.cachedReport = null;
929
+ this.lastCheck = 0;
930
+ }
931
+ /** Collect environment security report (cached for 5 minutes) */
932
+ async collect() {
933
+ if (this.cachedReport && Date.now() - this.lastCheck < 3e5) {
934
+ return this.cachedReport;
935
+ }
936
+ const nodeVersion = process.version;
937
+ const nodeMajor = parseInt(nodeVersion.replace("v", ""), 10);
938
+ const nodeVersionSecure = !EOL_NODE_MAJOR.includes(nodeMajor);
939
+ const envVarWarnings = this.checkEnvVars();
940
+ const debugModeEnabled = this.checkDebugMode();
941
+ const sourceMapExposed = await this.checkSourceMaps();
942
+ const frameworkInfo = await this.detectFramework();
943
+ const npmVersion = await this.getNpmVersion();
944
+ this.cachedReport = {
945
+ nodeVersion,
946
+ nodeVersionSecure,
947
+ npmVersion,
948
+ frameworkName: frameworkInfo.name,
949
+ frameworkVersion: frameworkInfo.version,
950
+ securityHeaders: [],
951
+ // Populated by response analysis
952
+ envVarWarnings,
953
+ debugModeEnabled,
954
+ sourceMapExposed,
955
+ // Runtime details
956
+ v8Version: process.versions.v8 || "",
957
+ uvVersion: process.versions.uv || "",
958
+ opensslVersion: process.versions.openssl || "",
959
+ zlibVersion: process.versions.zlib || "",
960
+ icuVersion: process.versions.icu || "",
961
+ // Process config
962
+ nodeEnv: process.env.NODE_ENV || "",
963
+ tz: process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone || "",
964
+ memoryLimitMb: getMemoryLimit(),
965
+ // Server info (detect from env/process)
966
+ port: parseInt(process.env.PORT || "0") || null,
967
+ host: process.env.HOST || process.env.HOSTNAME || "",
968
+ // DNS
969
+ dnsServers: getDnsServers()
970
+ };
971
+ this.lastCheck = Date.now();
972
+ return this.cachedReport;
973
+ }
974
+ /** Check response headers against security recommendations */
975
+ checkResponseHeaders(headers) {
976
+ const normalizedHeaders = {};
977
+ for (const [key, val] of Object.entries(headers)) {
978
+ normalizedHeaders[key.toLowerCase()] = Array.isArray(val) ? val.join(", ") : val || "";
979
+ }
980
+ return RECOMMENDED_HEADERS.map(({ header, recommendation }) => ({
981
+ header,
982
+ present: header in normalizedHeaders,
983
+ value: normalizedHeaders[header],
984
+ recommendation: header in normalizedHeaders ? void 0 : recommendation
985
+ }));
986
+ }
987
+ checkEnvVars() {
988
+ const warnings = [];
989
+ const envKeys = Object.keys(process.env);
990
+ for (const key of envKeys) {
991
+ for (const pattern of SENSITIVE_ENV_PATTERNS) {
992
+ if (pattern.test(key)) {
993
+ warnings.push(`Sensitive env var '${key}' is set \u2014 ensure it's not exposed to client`);
994
+ break;
995
+ }
996
+ }
997
+ }
998
+ if (process.getuid && process.getuid() === 0) {
999
+ warnings.push("Process is running as root \u2014 use a non-privileged user");
1000
+ }
1001
+ if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
1002
+ warnings.push("NODE_ENV is not set to 'production' \u2014 ensure proper security config");
1003
+ }
1004
+ return warnings;
1005
+ }
1006
+ checkDebugMode() {
1007
+ return !!(process.env.DEBUG || process.env.NODE_DEBUG || process.execArgv.some((arg) => arg.includes("--inspect") || arg.includes("--debug")));
1008
+ }
1009
+ async checkSourceMaps() {
1010
+ const paths = [
1011
+ join2(process.cwd(), ".next", "static"),
1012
+ join2(process.cwd(), "dist"),
1013
+ join2(process.cwd(), "build")
1014
+ ];
1015
+ for (const dir of paths) {
1016
+ try {
1017
+ await access(dir);
1018
+ return process.env.NODE_ENV === "production" && !!process.env.GENERATE_SOURCEMAP;
1019
+ } catch {
1020
+ continue;
1021
+ }
1022
+ }
1023
+ return false;
1024
+ }
1025
+ async detectFramework() {
1026
+ try {
1027
+ const pkgPath = join2(process.cwd(), "package.json");
1028
+ const content = await readFile2(pkgPath, "utf-8");
1029
+ const pkg = JSON.parse(content);
1030
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1031
+ if (allDeps.next) return { name: "next", version: allDeps.next };
1032
+ if (allDeps.nuxt) return { name: "nuxt", version: allDeps.nuxt };
1033
+ if (allDeps.express) return { name: "express", version: allDeps.express };
1034
+ if (allDeps.fastify) return { name: "fastify", version: allDeps.fastify };
1035
+ if (allDeps.koa) return { name: "koa", version: allDeps.koa };
1036
+ if (allDeps.hono) return { name: "hono", version: allDeps.hono };
1037
+ if (allDeps["@nestjs/core"]) return { name: "nestjs", version: allDeps["@nestjs/core"] };
1038
+ if (allDeps.remix || allDeps["@remix-run/node"]) return { name: "remix", version: allDeps["@remix-run/node"] || allDeps.remix };
1039
+ if (allDeps.astro) return { name: "astro", version: allDeps.astro };
1040
+ if (allDeps["@sveltejs/kit"]) return { name: "sveltekit", version: allDeps["@sveltejs/kit"] };
1041
+ if (allDeps.svelte) return { name: "svelte", version: allDeps.svelte };
1042
+ if (allDeps["@adonisjs/core"]) return { name: "adonisjs", version: allDeps["@adonisjs/core"] };
1043
+ if (allDeps.meteor) return { name: "meteor", version: allDeps.meteor };
1044
+ if (allDeps.strapi) return { name: "strapi", version: allDeps.strapi };
1045
+ if (allDeps.keystone) return { name: "keystonejs", version: allDeps.keystone };
1046
+ if (allDeps.payload) return { name: "payload", version: allDeps.payload };
1047
+ return {};
1048
+ } catch {
1049
+ return {};
1050
+ }
1051
+ }
1052
+ getNpmVersion() {
1053
+ return new Promise((resolve) => {
1054
+ execFile2("npm", ["--version"], { timeout: 5e3 }, (err, stdout) => {
1055
+ resolve(err ? void 0 : stdout.trim());
1056
+ });
1057
+ });
1058
+ }
1059
+ };
1060
+
1061
+ // src/reporter.ts
1062
+ import http from "http";
1063
+ import https from "https";
1064
+ import { URL } from "url";
1065
+ var MAX_PAYLOAD_BYTES = 512 * 1024;
1066
+ var MAX_STRING_LENGTH = 4096;
1067
+ var MAX_ARRAY_LENGTH = 200;
1068
+ function maskApiKey(key) {
1069
+ if (key.length <= 6) return "***";
1070
+ return key.slice(0, 6) + "***";
1071
+ }
1072
+ function truncStr(s, maxLen) {
1073
+ if (s == null) return s;
1074
+ return s.length > maxLen ? s.slice(0, maxLen) + "...[truncated]" : s;
1075
+ }
1076
+ function boundArray(arr, maxLen) {
1077
+ if (!arr) return arr;
1078
+ return arr.length > maxLen ? arr.slice(0, maxLen) : arr;
1079
+ }
1080
+ function sanitizePayload(payload) {
1081
+ const p = { ...payload };
1082
+ if (p.errors) {
1083
+ p.errors = boundArray(p.errors, MAX_ARRAY_LENGTH);
1084
+ for (let i = 0; i < p.errors.length; i++) {
1085
+ const e = { ...p.errors[i] };
1086
+ e.message = truncStr(e.message, MAX_STRING_LENGTH) || "";
1087
+ e.stack = truncStr(e.stack, MAX_STRING_LENGTH);
1088
+ p.errors[i] = e;
1089
+ }
1090
+ }
1091
+ if (p.security) {
1092
+ p.security = { ...p.security };
1093
+ p.security.events = boundArray(p.security.events, 50) || [];
1094
+ for (let i = 0; i < p.security.events.length; i++) {
1095
+ const ev = { ...p.security.events[i] };
1096
+ ev.message = truncStr(ev.message, MAX_STRING_LENGTH) || "";
1097
+ ev.payload = truncStr(ev.payload, 500);
1098
+ p.security.events[i] = ev;
1099
+ }
1100
+ p.security.topOffendingIps = boundArray(p.security.topOffendingIps, 10) || [];
1101
+ }
1102
+ if (p.depAudit) {
1103
+ p.depAudit = { ...p.depAudit };
1104
+ p.depAudit.vulnerabilities = boundArray(p.depAudit.vulnerabilities, 50) || [];
1105
+ }
1106
+ if (p.http) {
1107
+ p.http = { ...p.http };
1108
+ p.http.topPaths = boundArray(p.http.topPaths, 20) || [];
1109
+ }
1110
+ if (p.runtime) {
1111
+ p.runtime = { ...p.runtime };
1112
+ p.runtime.heapSpaces = boundArray(p.runtime.heapSpaces, 20);
1113
+ }
1114
+ if (p.envSecurity) {
1115
+ p.envSecurity = { ...p.envSecurity };
1116
+ p.envSecurity.envVarWarnings = boundArray(p.envSecurity.envVarWarnings, 50) || [];
1117
+ p.envSecurity.securityHeaders = boundArray(p.envSecurity.securityHeaders, 20) || [];
1118
+ }
1119
+ return p;
1120
+ }
1121
+ var Reporter = class {
1122
+ constructor(apiUrl, apiKey, logger) {
1123
+ this.queue = [];
1124
+ this.sending = false;
1125
+ this.apiUrl = apiUrl.replace(/\/$/, "");
1126
+ this.apiKey = apiKey;
1127
+ this.logger = logger;
1128
+ }
1129
+ async send(payload) {
1130
+ try {
1131
+ this.queue.push(payload);
1132
+ if (this.sending) return true;
1133
+ this.sending = true;
1134
+ try {
1135
+ while (this.queue.length > 0) {
1136
+ const batch = this.queue.splice(0, 10);
1137
+ await this.transmit(batch);
1138
+ }
1139
+ return true;
1140
+ } finally {
1141
+ this.sending = false;
1142
+ }
1143
+ } catch (err) {
1144
+ this.logger.error(
1145
+ `Failed to send report (key=${maskApiKey(this.apiKey)}):`,
1146
+ err instanceof Error ? err.message : err
1147
+ );
1148
+ return false;
1149
+ }
1150
+ }
1151
+ async transmit(payloads) {
1152
+ const url = `${this.apiUrl}/api/tracker/report`;
1153
+ const sanitized = payloads.map(sanitizePayload);
1154
+ let body = JSON.stringify(sanitized.length === 1 ? sanitized[0] : { batch: sanitized });
1155
+ if (body.length > MAX_PAYLOAD_BYTES) {
1156
+ this.logger.warn(
1157
+ `Payload too large (${body.length} bytes), truncating arrays (key=${maskApiKey(this.apiKey)})`
1158
+ );
1159
+ for (const p of sanitized) {
1160
+ if (p.security) {
1161
+ p.security.events = p.security.events.slice(0, 10);
1162
+ }
1163
+ if (p.errors) {
1164
+ p.errors = p.errors.slice(0, 20);
1165
+ }
1166
+ if (p.depAudit) {
1167
+ p.depAudit.vulnerabilities = p.depAudit.vulnerabilities.slice(0, 10);
1168
+ }
1169
+ }
1170
+ body = JSON.stringify(sanitized.length === 1 ? sanitized[0] : { batch: sanitized });
1171
+ }
1172
+ this.logger.info(
1173
+ `Sending report to ${url} (${payloads.length} payload(s), ${body.length} bytes, key=${maskApiKey(this.apiKey)})`
1174
+ );
1175
+ return new Promise((resolve, reject) => {
1176
+ try {
1177
+ const parsed = new URL(url);
1178
+ const isHttps = parsed.protocol === "https:";
1179
+ const mod = isHttps ? https : http;
1180
+ const options = {
1181
+ hostname: parsed.hostname,
1182
+ port: parsed.port || (isHttps ? 443 : 80),
1183
+ path: parsed.pathname + parsed.search,
1184
+ method: "POST",
1185
+ headers: {
1186
+ "Content-Type": "application/json",
1187
+ "Content-Length": Buffer.byteLength(body, "utf-8"),
1188
+ Authorization: `Bearer ${this.apiKey}`,
1189
+ "User-Agent": "shieldpress-tracker/1.0.0"
1190
+ },
1191
+ timeout: 1e4
1192
+ };
1193
+ const req = mod.request(options, (res) => {
1194
+ let data = "";
1195
+ res.on("data", (chunk) => {
1196
+ if (data.length < 1024) {
1197
+ data += chunk;
1198
+ }
1199
+ });
1200
+ res.on("end", () => {
1201
+ const statusCode = res.statusCode || 0;
1202
+ if (statusCode >= 200 && statusCode < 300) {
1203
+ this.logger.info("Report sent successfully");
1204
+ resolve();
1205
+ } else {
1206
+ reject(new Error(`HTTP ${statusCode}: ${data.slice(0, 200)}`));
1207
+ }
1208
+ });
1209
+ res.on("error", (err) => {
1210
+ reject(new Error(`Response error: ${err.message}`));
1211
+ });
1212
+ });
1213
+ req.setTimeout(1e4, () => {
1214
+ req.destroy(new Error("Request timed out after 10000ms"));
1215
+ });
1216
+ req.on("error", (err) => {
1217
+ reject(new Error(`Request error: ${err.message}`));
1218
+ });
1219
+ req.write(body);
1220
+ req.end();
1221
+ } catch (err) {
1222
+ reject(err instanceof Error ? err : new Error(String(err)));
1223
+ }
1224
+ });
1225
+ }
1226
+ };
1227
+
1228
+ // src/tracker.ts
1229
+ var ShieldPressTracker = class {
1230
+ constructor(userConfig) {
1231
+ this.timer = null;
1232
+ this.lagTimer = null;
1233
+ this.started = false;
1234
+ this.headersChecked = false;
1235
+ this.config = resolveConfig(userConfig);
1236
+ this.logger = createLogger(this.config.debug);
1237
+ this.reporter = new Reporter(this.config.apiUrl, this.config.apiKey, this.logger);
1238
+ this.httpCollector = new HttpCollector();
1239
+ this.errorCollector = new ErrorCollector(this.config.maxErrorBuffer);
1240
+ this.securityCollector = new SecurityCollector(this.config.maxSecurityBuffer);
1241
+ this.depAuditCollector = new DepAuditCollector(this.config.depAuditInterval * 1e3, this.logger);
1242
+ this.envSecurityCollector = new EnvSecurityCollector();
1243
+ }
1244
+ /** Start the tracker — begins periodic reporting */
1245
+ start() {
1246
+ if (this.started) return this;
1247
+ this.started = true;
1248
+ this.logger.info(
1249
+ `Starting tracker for site=${this.config.siteId} env=${this.config.environment} interval=${this.config.reportInterval}s`
1250
+ );
1251
+ if (this.config.errorTracking) {
1252
+ process.on("uncaughtException", (err) => {
1253
+ this.errorCollector.capture(err, { type: "uncaughtException" });
1254
+ this.flush().catch(() => {
1255
+ });
1256
+ });
1257
+ process.on("unhandledRejection", (reason) => {
1258
+ const err = reason instanceof Error ? reason : new Error(String(reason));
1259
+ this.errorCollector.capture(err, { type: "unhandledRejection" });
1260
+ });
1261
+ }
1262
+ if (this.config.systemMetrics) {
1263
+ collectSystemMetrics();
1264
+ }
1265
+ if (this.config.runtimeMetrics) {
1266
+ this.lagTimer = startLagSampling();
1267
+ }
1268
+ this.timer = setInterval(() => {
1269
+ this.flush().catch((err) => {
1270
+ this.logger.error("Flush failed:", err);
1271
+ });
1272
+ }, this.config.reportInterval * 1e3);
1273
+ this.timer.unref();
1274
+ const shutdown = () => {
1275
+ this.flush().catch(() => {
1276
+ }).finally(() => process.exit(0));
1277
+ };
1278
+ process.on("SIGTERM", shutdown);
1279
+ process.on("SIGINT", shutdown);
1280
+ return this;
1281
+ }
1282
+ /** Stop the tracker */
1283
+ stop() {
1284
+ if (this.timer) {
1285
+ clearInterval(this.timer);
1286
+ this.timer = null;
1287
+ }
1288
+ if (this.lagTimer) {
1289
+ clearInterval(this.lagTimer);
1290
+ this.lagTimer = null;
1291
+ }
1292
+ this.started = false;
1293
+ this.logger.info("Tracker stopped");
1294
+ }
1295
+ /** Manually record an HTTP request */
1296
+ recordRequest(data) {
1297
+ if (!this.config.httpTracking) return;
1298
+ if (this.shouldIgnorePath(data.path)) return;
1299
+ this.httpCollector.record(data);
1300
+ }
1301
+ /** Analyze an incoming request for security threats */
1302
+ analyzeRequest(req) {
1303
+ if (!this.config.securityTracking) return;
1304
+ if (this.shouldIgnorePath(req.url)) return;
1305
+ this.securityCollector.analyzeRequest(req);
1306
+ }
1307
+ /** Analyze a response for security issues (headers, data leaks) */
1308
+ analyzeResponse(data) {
1309
+ if (!this.config.securityTracking) return;
1310
+ if (!this.headersChecked && data.headers && this.config.envSecurity) {
1311
+ const headerChecks = this.envSecurityCollector.checkResponseHeaders(data.headers);
1312
+ this.headersChecked = true;
1313
+ this.envSecurityCollector.collect().then((report) => {
1314
+ report.securityHeaders = headerChecks;
1315
+ }).catch(() => {
1316
+ });
1317
+ }
1318
+ this.securityCollector.analyzeResponse(data);
1319
+ }
1320
+ /** Record an authentication failure */
1321
+ recordAuthFailure(data) {
1322
+ if (!this.config.securityTracking) return;
1323
+ this.securityCollector.recordAuthFailure(data);
1324
+ }
1325
+ /** Record a rate limit event */
1326
+ recordRateLimit(data) {
1327
+ if (!this.config.securityTracking) return;
1328
+ this.securityCollector.recordRateLimit(data);
1329
+ }
1330
+ /** Record a CORS violation */
1331
+ recordCorsViolation(data) {
1332
+ if (!this.config.securityTracking) return;
1333
+ this.securityCollector.recordCorsViolation(data);
1334
+ }
1335
+ /** Manually capture an error */
1336
+ captureError(error, meta) {
1337
+ if (!this.config.errorTracking) return;
1338
+ this.errorCollector.capture(error, meta);
1339
+ }
1340
+ /** Flush all collected data and send report */
1341
+ async flush() {
1342
+ const payload = {
1343
+ siteId: this.config.siteId,
1344
+ appName: this.config.appName,
1345
+ appVersion: this.config.appVersion,
1346
+ environment: this.config.environment,
1347
+ tags: this.config.tags,
1348
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1349
+ };
1350
+ if (this.config.systemMetrics) {
1351
+ payload.system = collectSystemMetrics();
1352
+ }
1353
+ if (this.config.httpTracking) {
1354
+ const httpMetrics = this.httpCollector.flush();
1355
+ if (httpMetrics) payload.http = httpMetrics;
1356
+ }
1357
+ if (this.config.errorTracking) {
1358
+ const errors = this.errorCollector.flush();
1359
+ if (errors.length > 0) payload.errors = errors;
1360
+ }
1361
+ if (this.config.securityTracking) {
1362
+ const security = this.securityCollector.flush();
1363
+ if (security) payload.security = security;
1364
+ }
1365
+ if (this.config.depAudit) {
1366
+ const depAudit = await this.depAuditCollector.collect().catch(() => null);
1367
+ if (depAudit) payload.depAudit = depAudit;
1368
+ }
1369
+ if (this.config.runtimeMetrics) {
1370
+ payload.runtime = collectRuntimeMetrics();
1371
+ }
1372
+ if (this.config.envSecurity) {
1373
+ const envReport = await this.envSecurityCollector.collect().catch(() => null);
1374
+ if (envReport) payload.envSecurity = envReport;
1375
+ }
1376
+ if (this.config.heartbeat) {
1377
+ payload.heartbeat = true;
1378
+ }
1379
+ return this.reporter.send(payload);
1380
+ }
1381
+ shouldIgnorePath(path) {
1382
+ return this.config.ignorePaths.some((pattern) => {
1383
+ if (pattern.endsWith("/*")) {
1384
+ return path.startsWith(pattern.slice(0, -1));
1385
+ }
1386
+ return path === pattern;
1387
+ });
1388
+ }
1389
+ };
1390
+ export {
1391
+ DepAuditCollector,
1392
+ EnvSecurityCollector,
1393
+ ErrorCollector,
1394
+ HttpCollector,
1395
+ Reporter,
1396
+ SecurityCollector,
1397
+ ShieldPressTracker,
1398
+ collectRuntimeMetrics,
1399
+ collectSystemMetrics,
1400
+ resolveConfig,
1401
+ startLagSampling
1402
+ };
1403
+ //# sourceMappingURL=index.mjs.map