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