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