securenow 6.0.2 โ 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONSUMING-APPS-GUIDE.md +455 -0
- package/NPM_README.md +2029 -0
- package/README.md +297 -40
- package/SKILL-API.md +634 -0
- package/SKILL-CLI.md +454 -0
- package/cidr.js +83 -0
- package/cli/apps.js +585 -0
- package/cli/auth.js +280 -0
- package/cli/client.js +115 -0
- package/cli/config.js +173 -0
- package/cli/diagnostics.js +387 -0
- package/cli/firewall.js +100 -0
- package/cli/fp.js +638 -0
- package/cli/init.js +201 -0
- package/cli/monitor.js +440 -0
- package/cli/run.js +148 -0
- package/cli/security.js +980 -0
- package/cli/ui.js +386 -0
- package/cli/utils.js +127 -0
- package/cli.js +466 -455
- package/console-instrumentation.js +147 -136
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +1377 -455
- package/docs/API-KEYS-GUIDE.md +233 -0
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-BODY-CAPTURE.md +1 -1
- package/docs/AUTO-SETUP-SUMMARY.md +331 -0
- package/docs/AUTO-SETUP.md +4 -4
- package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
- package/docs/BODY-CAPTURE-FIX.md +261 -0
- package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
- package/docs/CHANGELOG-NEXTJS.md +1 -35
- package/docs/COMPLETION-REPORT.md +408 -0
- package/docs/CUSTOMER-GUIDE.md +16 -16
- package/docs/EASIEST-SETUP.md +5 -5
- package/docs/ENVIRONMENT-VARIABLES.md +880 -652
- package/docs/EXPRESS-BODY-CAPTURE.md +13 -12
- package/docs/EXPRESS-SETUP-GUIDE.md +719 -720
- package/docs/FINAL-SOLUTION.md +335 -0
- package/docs/FIREWALL-GUIDE.md +426 -0
- package/docs/IMPLEMENTATION-SUMMARY.md +410 -0
- package/docs/INDEX.md +22 -4
- package/docs/LOGGING-GUIDE.md +701 -708
- package/docs/LOGGING-QUICKSTART.md +234 -255
- package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +323 -0
- package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
- package/docs/NEXTJS-GUIDE.md +14 -14
- package/docs/NEXTJS-QUICKSTART.md +1 -1
- package/docs/NEXTJS-SETUP-COMPLETE.md +795 -0
- package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
- package/docs/NUXT-GUIDE.md +166 -0
- package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
- package/docs/REDACTION-EXAMPLES.md +1 -1
- package/docs/REQUEST-BODY-CAPTURE.md +19 -10
- package/docs/SOLUTION-SUMMARY.md +312 -0
- package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
- package/examples/README.md +6 -6
- package/examples/instrumentation-with-auto-capture.ts +1 -1
- package/examples/nextjs-env-example.txt +2 -2
- package/examples/nextjs-instrumentation.js +1 -1
- package/examples/nextjs-instrumentation.ts +1 -1
- package/examples/nextjs-with-logging-example.md +6 -6
- package/examples/nextjs-with-options.ts +1 -1
- package/examples/test-nextjs-setup.js +1 -1
- package/firewall-cloud.js +212 -0
- package/firewall-iptables.js +139 -0
- package/firewall-only.js +38 -0
- package/firewall-tcp.js +74 -0
- package/firewall.js +720 -0
- package/free-trial-banner.js +174 -0
- package/nextjs-auto-capture.js +199 -207
- package/nextjs-middleware.js +186 -181
- package/nextjs-webpack-config.js +88 -53
- package/nextjs-wrapper.js +158 -158
- package/nextjs.d.ts +1 -1
- package/nextjs.js +639 -647
- package/nuxt-server-plugin.mjs +423 -0
- package/nuxt.d.ts +60 -0
- package/nuxt.mjs +75 -0
- package/package.json +186 -164
- package/postinstall.js +6 -6
- package/register.d.ts +1 -1
- package/register.js +39 -4
- package/resolve-ip.js +77 -0
- package/tracing.d.ts +2 -1
- package/tracing.js +295 -34
- package/web-vite.mjs +239 -156
- package/LICENSE +0 -15
package/nextjs.js
CHANGED
|
@@ -1,647 +1,639 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SecureNow Next.js Integration using @vercel/otel
|
|
5
|
-
*
|
|
6
|
-
* Usage in Next.js app:
|
|
7
|
-
*
|
|
8
|
-
* 1.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
'http.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
'http.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
console.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
console.
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
module.exports = {
|
|
645
|
-
registerSecureNow,
|
|
646
|
-
};
|
|
647
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SecureNow Next.js Integration using @vercel/otel
|
|
5
|
+
*
|
|
6
|
+
* Usage in Next.js app:
|
|
7
|
+
*
|
|
8
|
+
* 1. Add serverExternalPackages to next.config.js (REQUIRED to avoid webpack bundling issues):
|
|
9
|
+
*
|
|
10
|
+
* const nextConfig = {
|
|
11
|
+
* serverExternalPackages: [
|
|
12
|
+
* "securenow",
|
|
13
|
+
* "@opentelemetry/sdk-node",
|
|
14
|
+
* "@opentelemetry/auto-instrumentations-node",
|
|
15
|
+
* "@opentelemetry/instrumentation-http",
|
|
16
|
+
* "@opentelemetry/exporter-trace-otlp-http",
|
|
17
|
+
* "@opentelemetry/exporter-logs-otlp-http",
|
|
18
|
+
* "@opentelemetry/sdk-logs",
|
|
19
|
+
* "@opentelemetry/instrumentation",
|
|
20
|
+
* "@opentelemetry/resources",
|
|
21
|
+
* "@opentelemetry/semantic-conventions",
|
|
22
|
+
* "@opentelemetry/api",
|
|
23
|
+
* "@opentelemetry/api-logs",
|
|
24
|
+
* "@vercel/otel",
|
|
25
|
+
* ],
|
|
26
|
+
* };
|
|
27
|
+
*
|
|
28
|
+
* 2. Create instrumentation.ts (or .js) in your project root:
|
|
29
|
+
*
|
|
30
|
+
* import { registerSecureNow } from 'securenow/nextjs';
|
|
31
|
+
* export function register() {
|
|
32
|
+
* registerSecureNow();
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* 3. Set environment variables:
|
|
36
|
+
* SECURENOW_APPID=my-nextjs-app
|
|
37
|
+
* SECURENOW_INSTANCE=http://your-otlp-backend:4318
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const { randomUUID } = require('crypto');
|
|
41
|
+
|
|
42
|
+
const env = k => process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
|
|
43
|
+
|
|
44
|
+
const parseHeaders = str => {
|
|
45
|
+
const out = {}; if (!str) return out;
|
|
46
|
+
for (const raw of String(str).split(',')) {
|
|
47
|
+
const s = raw.trim(); if (!s) continue;
|
|
48
|
+
const i = s.indexOf('='); if (i === -1) continue;
|
|
49
|
+
out[s.slice(0, i).trim().toLowerCase()] = s.slice(i + 1).trim();
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let isRegistered = false;
|
|
55
|
+
|
|
56
|
+
// Default sensitive fields to redact from request bodies
|
|
57
|
+
const DEFAULT_SENSITIVE_FIELDS = [
|
|
58
|
+
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
59
|
+
'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
|
|
60
|
+
'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Redact sensitive fields from an object
|
|
65
|
+
*/
|
|
66
|
+
function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
67
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
68
|
+
|
|
69
|
+
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
70
|
+
|
|
71
|
+
for (const key of Object.keys(redacted)) {
|
|
72
|
+
const lowerKey = key.toLowerCase();
|
|
73
|
+
|
|
74
|
+
if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
|
|
75
|
+
redacted[key] = '[REDACTED]';
|
|
76
|
+
} else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
|
|
77
|
+
// Recursively redact nested objects
|
|
78
|
+
redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return redacted;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function escapeRegex(str) {
|
|
86
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Redact sensitive data from GraphQL query strings
|
|
91
|
+
*/
|
|
92
|
+
function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
93
|
+
if (!query || typeof query !== 'string') return query;
|
|
94
|
+
|
|
95
|
+
let redacted = query;
|
|
96
|
+
|
|
97
|
+
// Redact sensitive fields in GraphQL arguments and variables
|
|
98
|
+
// Matches patterns like: password: "value" or password:"value" or password:'value'
|
|
99
|
+
sensitiveFields.forEach(field => {
|
|
100
|
+
const escaped = escapeRegex(field);
|
|
101
|
+
const patterns = [
|
|
102
|
+
new RegExp(`(${escaped}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
|
|
103
|
+
new RegExp(`(${escaped}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
patterns.forEach(pattern => {
|
|
107
|
+
redacted = redacted.replace(pattern, (match, prefix, value, suffix) => {
|
|
108
|
+
if (suffix) {
|
|
109
|
+
return `${prefix}[REDACTED]${suffix}`;
|
|
110
|
+
} else {
|
|
111
|
+
return `${prefix}[REDACTED]`;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return redacted;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Register SecureNow OpenTelemetry for Next.js using @vercel/otel
|
|
122
|
+
* @param {Object} options - Optional configuration
|
|
123
|
+
* @param {string} options.serviceName - Service name (defaults to SECURENOW_APPID or OTEL_SERVICE_NAME)
|
|
124
|
+
* @param {string} options.endpoint - Traces endpoint (defaults to SECURENOW_INSTANCE)
|
|
125
|
+
* @param {boolean} options.noUuid - Don't append UUID to service name
|
|
126
|
+
*/
|
|
127
|
+
function registerSecureNow(options = {}) {
|
|
128
|
+
// Only register once
|
|
129
|
+
if (isRegistered) {
|
|
130
|
+
console.log('[securenow] Already registered, skipping...');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Skip for Edge runtime
|
|
135
|
+
if (process.env.NEXT_RUNTIME === 'edge') {
|
|
136
|
+
console.log('[securenow] Skipping Edge runtime (Node.js only)');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Detect environment outside try block for error handling
|
|
141
|
+
const isVercel = !!(env('VERCEL') || env('VERCEL_ENV') || env('VERCEL_URL'));
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
console.log('[securenow] Next.js integration loading (pid=%d)', process.pid);
|
|
145
|
+
|
|
146
|
+
// -------- Configuration --------
|
|
147
|
+
const rawBase = (
|
|
148
|
+
options.serviceName ||
|
|
149
|
+
env('OTEL_SERVICE_NAME') ||
|
|
150
|
+
env('SECURENOW_APPID') ||
|
|
151
|
+
''
|
|
152
|
+
).trim().replace(/^['"]|['"]$/g, '');
|
|
153
|
+
|
|
154
|
+
const baseName = rawBase || null;
|
|
155
|
+
const noUuid = options.noUuid ?? (String(env('SECURENOW_NO_UUID')) === '1' || String(env('SECURENOW_NO_UUID')).toLowerCase() === 'true');
|
|
156
|
+
|
|
157
|
+
// service.name
|
|
158
|
+
let serviceName;
|
|
159
|
+
if (baseName) {
|
|
160
|
+
serviceName = noUuid ? baseName : `${baseName}-${randomUUID()}`;
|
|
161
|
+
} else {
|
|
162
|
+
serviceName = `nextjs-app-${randomUUID()}`;
|
|
163
|
+
console.warn('[securenow] โ ๏ธ No SECURENOW_APPID or OTEL_SERVICE_NAME provided. Using fallback: %s', serviceName);
|
|
164
|
+
console.warn('[securenow] ๐ก Set SECURENOW_APPID=your-app-name in .env.local for better tracking');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// -------- Endpoint Configuration --------
|
|
168
|
+
const endpointBase = (
|
|
169
|
+
options.endpoint ||
|
|
170
|
+
env('SECURENOW_INSTANCE') ||
|
|
171
|
+
env('OTEL_EXPORTER_OTLP_ENDPOINT') ||
|
|
172
|
+
'https://freetrial.securenow.ai:4318'
|
|
173
|
+
).replace(/\/$/, '');
|
|
174
|
+
|
|
175
|
+
const tracesUrl = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
|
|
176
|
+
const logsUrl = env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
|
|
177
|
+
|
|
178
|
+
if (!process.env.OTEL_SERVICE_NAME) process.env.OTEL_SERVICE_NAME = serviceName;
|
|
179
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_ENDPOINT = endpointBase;
|
|
180
|
+
if (!process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = tracesUrl;
|
|
181
|
+
|
|
182
|
+
console.log('[securenow] ๐ Next.js App โ service.name=%s', serviceName);
|
|
183
|
+
|
|
184
|
+
// -------- Body Capture Configuration --------
|
|
185
|
+
const captureBody = String(env('SECURENOW_CAPTURE_BODY')) === '1' ||
|
|
186
|
+
String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true' ||
|
|
187
|
+
options.captureBody === true;
|
|
188
|
+
const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
|
|
189
|
+
const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
190
|
+
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
191
|
+
|
|
192
|
+
// -------- Log environment detection --------
|
|
193
|
+
if (!isVercel) {
|
|
194
|
+
console.log('[securenow] ๐ฅ๏ธ Self-hosted environment detected (EC2/PM2) - using vanilla SDK');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// -------- Use different initialization based on environment --------
|
|
198
|
+
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
|
|
199
|
+
|
|
200
|
+
// Configure HTTP instrumentation with comprehensive header capture
|
|
201
|
+
const httpInstrumentation = new HttpInstrumentation({
|
|
202
|
+
requireParentforOutgoingSpans: false,
|
|
203
|
+
requireParentforIncomingSpans: false,
|
|
204
|
+
ignoreIncomingRequestHook: (request) => {
|
|
205
|
+
// Never ignore - we want to trace all requests
|
|
206
|
+
return false;
|
|
207
|
+
},
|
|
208
|
+
requestHook: (span, request) => {
|
|
209
|
+
// SYNCHRONOUS ONLY - no async operations to avoid timing issues
|
|
210
|
+
try {
|
|
211
|
+
// Capture all headers
|
|
212
|
+
const headers = request.headers || {};
|
|
213
|
+
|
|
214
|
+
// ======== IP ADDRESS CAPTURE ========
|
|
215
|
+
// Try different header sources for IP (priority order)
|
|
216
|
+
const forwardedFor = headers['x-forwarded-for'];
|
|
217
|
+
const realIp = headers['x-real-ip'];
|
|
218
|
+
const cfConnectingIp = headers['cf-connecting-ip']; // Cloudflare
|
|
219
|
+
const clientIp = headers['x-client-ip'];
|
|
220
|
+
const socketIp = request.socket?.remoteAddress;
|
|
221
|
+
|
|
222
|
+
const PRIVATE_RE = /^(127\.|::1$|::ffff:127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|f[cd][0-9a-f]{2}:)/;
|
|
223
|
+
const isProxied = socketIp && PRIVATE_RE.test(socketIp);
|
|
224
|
+
let primaryIp = socketIp || 'unknown';
|
|
225
|
+
if (isProxied) {
|
|
226
|
+
if (forwardedFor) {
|
|
227
|
+
const chain = forwardedFor.split(',').map(s => s.trim()).filter(Boolean);
|
|
228
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
229
|
+
if (!PRIVATE_RE.test(chain[i])) { primaryIp = chain[i]; break; }
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
primaryIp = realIp || cfConnectingIp || clientIp || primaryIp;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ======== PROTOCOL & CONNECTION ========
|
|
237
|
+
const scheme = headers['x-forwarded-proto'] ||
|
|
238
|
+
(request.socket?.encrypted ? 'https' : 'http');
|
|
239
|
+
const host = headers['x-forwarded-host'] || headers['host'] || '';
|
|
240
|
+
const port = headers['x-forwarded-port'] || request.socket?.localPort || '';
|
|
241
|
+
|
|
242
|
+
// ======== REQUEST METADATA ========
|
|
243
|
+
const userAgent = headers['user-agent'] || '';
|
|
244
|
+
const referer = headers['referer'] || headers['referrer'] || '';
|
|
245
|
+
const accept = headers['accept'] || '';
|
|
246
|
+
const acceptLanguage = headers['accept-language'] || '';
|
|
247
|
+
const acceptEncoding = headers['accept-encoding'] || '';
|
|
248
|
+
const contentType = headers['content-type'] || '';
|
|
249
|
+
const contentLength = headers['content-length'] || '';
|
|
250
|
+
const origin = headers['origin'] || '';
|
|
251
|
+
|
|
252
|
+
// ======== PROXY & LOAD BALANCER ========
|
|
253
|
+
const originalUri = headers['x-original-uri'] || '';
|
|
254
|
+
const originalMethod = headers['x-original-method'] || '';
|
|
255
|
+
const requestId = headers['x-request-id'] || headers['x-trace-id'] || headers['x-correlation-id'] || '';
|
|
256
|
+
|
|
257
|
+
// ======== SET ALL ATTRIBUTES ========
|
|
258
|
+
const attributes = {
|
|
259
|
+
// IP & Network
|
|
260
|
+
'http.client_ip': primaryIp,
|
|
261
|
+
'http.forwarded_for': forwardedFor || '',
|
|
262
|
+
'http.real_ip': realIp || '',
|
|
263
|
+
'http.socket_ip': socketIp || '',
|
|
264
|
+
|
|
265
|
+
// Protocol & Host
|
|
266
|
+
'http.scheme': scheme,
|
|
267
|
+
'http.host': host,
|
|
268
|
+
'http.port': port.toString(),
|
|
269
|
+
'http.forwarded_proto': headers['x-forwarded-proto'] || '',
|
|
270
|
+
'http.forwarded_host': headers['x-forwarded-host'] || '',
|
|
271
|
+
|
|
272
|
+
// Request Details
|
|
273
|
+
'http.user_agent': userAgent,
|
|
274
|
+
'http.referer': referer,
|
|
275
|
+
'http.origin': origin,
|
|
276
|
+
'http.accept': accept,
|
|
277
|
+
'http.accept_language': acceptLanguage,
|
|
278
|
+
'http.accept_encoding': acceptEncoding,
|
|
279
|
+
'http.content_type': contentType,
|
|
280
|
+
'http.content_length': contentLength,
|
|
281
|
+
|
|
282
|
+
// Proxy & Routing
|
|
283
|
+
'http.original_uri': originalUri,
|
|
284
|
+
'http.original_method': originalMethod,
|
|
285
|
+
'http.request_id': requestId,
|
|
286
|
+
|
|
287
|
+
// Connection Info
|
|
288
|
+
'http.connection': headers['connection'] || '',
|
|
289
|
+
'http.upgrade': headers['upgrade'] || '',
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Set all attributes at once
|
|
293
|
+
span.setAttributes(attributes);
|
|
294
|
+
|
|
295
|
+
// ======== GEOGRAPHIC DATA ========
|
|
296
|
+
// Vercel geo headers
|
|
297
|
+
if (headers['x-vercel-ip-country']) {
|
|
298
|
+
span.setAttributes({
|
|
299
|
+
'http.geo.country': headers['x-vercel-ip-country'],
|
|
300
|
+
'http.geo.region': headers['x-vercel-ip-country-region'] || '',
|
|
301
|
+
'http.geo.city': headers['x-vercel-ip-city'] || '',
|
|
302
|
+
'http.geo.latitude': headers['x-vercel-ip-latitude'] || '',
|
|
303
|
+
'http.geo.longitude': headers['x-vercel-ip-longitude'] || '',
|
|
304
|
+
'http.geo.timezone': headers['x-vercel-ip-timezone'] || '',
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Cloudflare geo headers
|
|
309
|
+
if (headers['cf-ipcountry']) {
|
|
310
|
+
span.setAttributes({
|
|
311
|
+
'http.geo.country': headers['cf-ipcountry'],
|
|
312
|
+
'http.geo.cf_ray': headers['cf-ray'] || '',
|
|
313
|
+
'http.geo.cf_visitor': headers['cf-visitor'] || '',
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Cloudflare additional headers
|
|
318
|
+
if (headers['cf-connecting-ip']) {
|
|
319
|
+
span.setAttribute('http.cf.connecting_ip', headers['cf-connecting-ip']);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ======== SECURITY HEADERS ========
|
|
323
|
+
if (headers['x-csrf-token']) {
|
|
324
|
+
span.setAttribute('http.security.csrf_token_present', 'true');
|
|
325
|
+
}
|
|
326
|
+
if (headers['authorization']) {
|
|
327
|
+
span.setAttribute('http.security.auth_present', 'true');
|
|
328
|
+
// Never log the actual token!
|
|
329
|
+
}
|
|
330
|
+
if (headers['cookie']) {
|
|
331
|
+
span.setAttribute('http.security.cookies_present', 'true');
|
|
332
|
+
// Never log actual cookies!
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Debug log in development
|
|
336
|
+
if (env('NODE_ENV') === 'development' || env('OTEL_LOG_LEVEL') === 'debug') {
|
|
337
|
+
console.log('[securenow] ๐ก Captured IP: %s (from: %s)',
|
|
338
|
+
primaryIp,
|
|
339
|
+
forwardedFor ? 'x-forwarded-for' : realIp ? 'x-real-ip' : socketIp ? 'socket' : 'unknown'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// -------- Request Body NOT captured at HTTP instrumentation level --------
|
|
344
|
+
// IMPORTANT: Do NOT attempt to read request.body or listen to 'data' events
|
|
345
|
+
// Next.js manages request streams internally and reading them here causes conflicts
|
|
346
|
+
// Body capture must be done in Next.js middleware using request.clone()
|
|
347
|
+
|
|
348
|
+
} catch (error) {
|
|
349
|
+
// Silently fail to not break the request
|
|
350
|
+
if (env('OTEL_LOG_LEVEL') === 'debug') {
|
|
351
|
+
console.error('[securenow] โ ๏ธ Error in requestHook:', error.message);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
responseHook: (span, response) => {
|
|
356
|
+
try {
|
|
357
|
+
// Capture response metadata
|
|
358
|
+
span.setAttributes({
|
|
359
|
+
'http.status_code': response.statusCode || 0,
|
|
360
|
+
'http.status_message': response.statusMessage || '',
|
|
361
|
+
'http.response.content_type': response.headers?.['content-type'] || '',
|
|
362
|
+
'http.response.content_length': response.headers?.['content-length'] || '',
|
|
363
|
+
});
|
|
364
|
+
} catch (error) {
|
|
365
|
+
// Silently fail
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// -------- Guard against OTLP exporter socket errors --------
|
|
371
|
+
// The OTLP HTTP exporter uses keep-alive connections that can be reset by
|
|
372
|
+
// the remote end (ECONNRESET / "socket hang up"). These transient errors
|
|
373
|
+
// sometimes escape as unhandled exceptions or rejections. We catch them
|
|
374
|
+
// here and log at debug level instead of crashing the host app.
|
|
375
|
+
const _TRANSIENT_CODES = new Set(['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN']);
|
|
376
|
+
function _isOtlpTransientError(err) {
|
|
377
|
+
if (!err) return false;
|
|
378
|
+
if (_TRANSIENT_CODES.has(err.code)) return true;
|
|
379
|
+
if (typeof err.message === 'string' && /socket hang up|ECONNRESET/.test(err.message)) return true;
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
function _looksLikeOtlpStack(err) {
|
|
383
|
+
const s = err && err.stack;
|
|
384
|
+
if (!s) return false;
|
|
385
|
+
return /OTLPTraceExporter|OTLPLogExporter|otlp|exporter.*http|BatchSpanProcessor|BatchLogRecordProcessor/i.test(s)
|
|
386
|
+
|| /node:_http_client|ClientRequest|TLSSocket/i.test(s);
|
|
387
|
+
}
|
|
388
|
+
const _diagDebug = (env('OTEL_LOG_LEVEL') || '').toLowerCase() === 'debug';
|
|
389
|
+
process.on('uncaughtException', (err, origin) => {
|
|
390
|
+
if (_isOtlpTransientError(err) && _looksLikeOtlpStack(err)) {
|
|
391
|
+
if (_diagDebug) {
|
|
392
|
+
console.debug('[securenow] Suppressed transient OTLP exporter error (%s): %s', origin, err.message);
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
throw err;
|
|
397
|
+
});
|
|
398
|
+
process.on('unhandledRejection', (reason) => {
|
|
399
|
+
if (_isOtlpTransientError(reason) && _looksLikeOtlpStack(reason)) {
|
|
400
|
+
if (_diagDebug) {
|
|
401
|
+
console.debug('[securenow] Suppressed transient OTLP exporter rejection: %s', reason && reason.message);
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
throw reason;
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (isVercel) {
|
|
409
|
+
// -------- Vercel Environment: Use @vercel/otel --------
|
|
410
|
+
const { registerOTel } = require('@vercel/otel');
|
|
411
|
+
|
|
412
|
+
registerOTel({
|
|
413
|
+
serviceName: serviceName,
|
|
414
|
+
attributes: {
|
|
415
|
+
'deployment.environment': env('NODE_ENV') || env('VERCEL_ENV') || 'development',
|
|
416
|
+
'service.version': process.env.npm_package_version || process.env.VERCEL_GIT_COMMIT_SHA || undefined,
|
|
417
|
+
'vercel.region': process.env.VERCEL_REGION || undefined,
|
|
418
|
+
},
|
|
419
|
+
instrumentations: [httpInstrumentation],
|
|
420
|
+
instrumentationConfig: {
|
|
421
|
+
fetch: {
|
|
422
|
+
// Propagate context to your backend APIs
|
|
423
|
+
propagateContextUrls: [
|
|
424
|
+
/^https?:\/\/localhost/,
|
|
425
|
+
/^https?:\/\/.*\.vercel\.app/,
|
|
426
|
+
// Add your backend domains here
|
|
427
|
+
],
|
|
428
|
+
// Optionally ignore certain URLs
|
|
429
|
+
ignoreUrls: [
|
|
430
|
+
/_next\/static/,
|
|
431
|
+
/_next\/image/,
|
|
432
|
+
/\.map$/,
|
|
433
|
+
],
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
} else {
|
|
438
|
+
// -------- Self-Hosted (EC2/PM2): Use Vanilla OpenTelemetry SDK --------
|
|
439
|
+
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
|
440
|
+
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
441
|
+
const { Resource } = require('@opentelemetry/resources');
|
|
442
|
+
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
|
|
443
|
+
|
|
444
|
+
const traceExporter = new OTLPTraceExporter({
|
|
445
|
+
url: tracesUrl,
|
|
446
|
+
headers: parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'))
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const sdk = new NodeSDK({
|
|
450
|
+
serviceName: serviceName,
|
|
451
|
+
traceExporter: traceExporter,
|
|
452
|
+
instrumentations: [httpInstrumentation],
|
|
453
|
+
resource: new Resource({
|
|
454
|
+
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
455
|
+
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env('NODE_ENV') || env('VERCEL_ENV') || 'production',
|
|
456
|
+
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version || undefined,
|
|
457
|
+
}),
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
sdk.start();
|
|
461
|
+
console.log('[securenow] ๐ฏ Vanilla SDK initialized for self-hosted environment');
|
|
462
|
+
|
|
463
|
+
// -------- Logging (self-hosted only) --------
|
|
464
|
+
const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) === '1' || String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true';
|
|
465
|
+
if (loggingEnabled) {
|
|
466
|
+
try {
|
|
467
|
+
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
|
|
468
|
+
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
|
|
469
|
+
|
|
470
|
+
const logExporter = new OTLPLogExporter({
|
|
471
|
+
url: logsUrl,
|
|
472
|
+
headers: parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS')),
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const loggerProvider = new LoggerProvider({
|
|
476
|
+
resource: new Resource({
|
|
477
|
+
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
478
|
+
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env('NODE_ENV') || 'production',
|
|
479
|
+
}),
|
|
480
|
+
});
|
|
481
|
+
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
|
|
482
|
+
|
|
483
|
+
// Patch console to forward logs as OTLP log records
|
|
484
|
+
const logger = loggerProvider.getLogger('console', '1.0.0');
|
|
485
|
+
const SeverityNumber = { INFO: 9, WARN: 13, ERROR: 17 };
|
|
486
|
+
const origLog = console.log;
|
|
487
|
+
const origWarn = console.warn;
|
|
488
|
+
const origError = console.error;
|
|
489
|
+
|
|
490
|
+
const { context: otelContext, trace: otelTrace } = require('@opentelemetry/api');
|
|
491
|
+
function _emitLog(sn, st, args) {
|
|
492
|
+
try {
|
|
493
|
+
const activeCtx = otelContext.active();
|
|
494
|
+
const spanCtx = otelTrace.getSpanContext(activeCtx);
|
|
495
|
+
logger.emit({
|
|
496
|
+
severityNumber: sn,
|
|
497
|
+
severityText: st,
|
|
498
|
+
body: args.map(String).join(' '),
|
|
499
|
+
...(spanCtx && { context: activeCtx }),
|
|
500
|
+
});
|
|
501
|
+
} catch (_) {}
|
|
502
|
+
}
|
|
503
|
+
console.log = (...args) => {
|
|
504
|
+
origLog.apply(console, args);
|
|
505
|
+
_emitLog(SeverityNumber.INFO, 'INFO', args);
|
|
506
|
+
};
|
|
507
|
+
console.warn = (...args) => {
|
|
508
|
+
origWarn.apply(console, args);
|
|
509
|
+
_emitLog(SeverityNumber.WARN, 'WARN', args);
|
|
510
|
+
};
|
|
511
|
+
console.error = (...args) => {
|
|
512
|
+
origError.apply(console, args);
|
|
513
|
+
_emitLog(SeverityNumber.ERROR, 'ERROR', args);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
console.log('[securenow] ๐ Logging: ENABLED โ %s', logsUrl);
|
|
517
|
+
|
|
518
|
+
// Auto-log every incoming HTTP request/response
|
|
519
|
+
try {
|
|
520
|
+
const http = require('http');
|
|
521
|
+
const originalEmit = http.Server.prototype.emit;
|
|
522
|
+
http.Server.prototype.emit = function (event, req, res) {
|
|
523
|
+
if (event === 'request' && req && res) {
|
|
524
|
+
const start = Date.now();
|
|
525
|
+
const method = req.method;
|
|
526
|
+
const url = req.url;
|
|
527
|
+
res.on('finish', () => {
|
|
528
|
+
const reqCtx = otelContext.active();
|
|
529
|
+
const reqSpanCtx = otelTrace.getSpanContext(reqCtx);
|
|
530
|
+
const duration = Date.now() - start;
|
|
531
|
+
const status = res.statusCode;
|
|
532
|
+
const ip = req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.socket?.remoteAddress || '-';
|
|
533
|
+
const ua = req.headers['user-agent'] || '-';
|
|
534
|
+
const body = `${method} ${url} ${status} ${duration}ms ip=${ip} ua=${ua}`;
|
|
535
|
+
const severity = status >= 500 ? SeverityNumber.ERROR : status >= 400 ? SeverityNumber.WARN : SeverityNumber.INFO;
|
|
536
|
+
const severityText = status >= 500 ? 'ERROR' : status >= 400 ? 'WARN' : 'INFO';
|
|
537
|
+
origLog.call(console, '[securenow] %s %s %d %dms', method, url, status, duration);
|
|
538
|
+
try {
|
|
539
|
+
logger.emit({
|
|
540
|
+
severityNumber: severity,
|
|
541
|
+
severityText,
|
|
542
|
+
body,
|
|
543
|
+
attributes: {
|
|
544
|
+
'http.method': method,
|
|
545
|
+
'http.url': url,
|
|
546
|
+
'http.status_code': status,
|
|
547
|
+
'http.duration_ms': duration,
|
|
548
|
+
'http.client_ip': String(ip).split(',')[0].trim(),
|
|
549
|
+
'http.user_agent': ua,
|
|
550
|
+
},
|
|
551
|
+
...(reqSpanCtx && { context: reqCtx }),
|
|
552
|
+
});
|
|
553
|
+
} catch (_) {}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return originalEmit.apply(this, arguments);
|
|
557
|
+
};
|
|
558
|
+
console.log('[securenow] ๐ HTTP request logging: ENABLED');
|
|
559
|
+
} catch (_) {}
|
|
560
|
+
|
|
561
|
+
// Graceful shutdown for logs
|
|
562
|
+
process.on('SIGTERM', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { require('./firewall').shutdown(); } catch (_) {} });
|
|
563
|
+
process.on('SIGINT', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { require('./firewall').shutdown(); } catch (_) {} });
|
|
564
|
+
} catch (e) {
|
|
565
|
+
console.warn('[securenow] โ ๏ธ Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
|
|
566
|
+
}
|
|
567
|
+
} else {
|
|
568
|
+
console.log('[securenow] ๐ Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)');
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
isRegistered = true;
|
|
573
|
+
|
|
574
|
+
// Free trial banner (optional โ may not be bundled in standalone builds)
|
|
575
|
+
try {
|
|
576
|
+
const { isFreeTrial, patchHttpForBanner } = require('./free-trial-banner');
|
|
577
|
+
if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
|
|
578
|
+
patchHttpForBanner();
|
|
579
|
+
}
|
|
580
|
+
} catch (_) {}
|
|
581
|
+
|
|
582
|
+
console.log('[securenow] โ
OpenTelemetry started for Next.js โ %s', tracesUrl);
|
|
583
|
+
console.log('[securenow] ๐ Auto-capturing comprehensive request metadata:');
|
|
584
|
+
console.log('[securenow] โข IP addresses (x-forwarded-for, x-real-ip, socket)');
|
|
585
|
+
console.log('[securenow] โข User-Agent, Referer, Origin, Accept headers');
|
|
586
|
+
console.log('[securenow] โข Protocol, Host, Port (proxy-aware)');
|
|
587
|
+
console.log('[securenow] โข Geographic data (Vercel/Cloudflare)');
|
|
588
|
+
console.log('[securenow] โข Request IDs, CSRF tokens, Auth presence');
|
|
589
|
+
console.log('[securenow] โข Response status, content-type, content-length');
|
|
590
|
+
console.log('[securenow] โ ๏ธ Body capture DISABLED at HTTP instrumentation level (prevents Next.js conflicts)');
|
|
591
|
+
if (captureBody) {
|
|
592
|
+
console.log('[securenow] ๐ก For body capture in Next.js, use: import "securenow/nextjs-auto-capture"');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Optional test span
|
|
596
|
+
if (String(env('SECURENOW_TEST_SPAN')) === '1') {
|
|
597
|
+
const api = require('@opentelemetry/api');
|
|
598
|
+
const tracer = api.trace.getTracer('securenow-nextjs');
|
|
599
|
+
const span = tracer.startSpan('securenow.nextjs.startup');
|
|
600
|
+
span.setAttribute('next.runtime', process.env.NEXT_RUNTIME || 'nodejs');
|
|
601
|
+
span.end();
|
|
602
|
+
console.log('[securenow] ๐งช Test span created');
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.error('[securenow] Failed to initialize OpenTelemetry:', error);
|
|
607
|
+
if (isVercel) {
|
|
608
|
+
console.error('[securenow] Make sure you have @vercel/otel installed: npm install @vercel/otel');
|
|
609
|
+
} else {
|
|
610
|
+
console.error('[securenow] Make sure OpenTelemetry dependencies are installed');
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Firewall โ runs independently from OTel so it works even if tracing fails
|
|
615
|
+
const firewallApiKey = env('SECURENOW_API_KEY');
|
|
616
|
+
if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
|
|
617
|
+
try {
|
|
618
|
+
require('./firewall').init({
|
|
619
|
+
apiKey: firewallApiKey,
|
|
620
|
+
apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
|
|
621
|
+
versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
|
|
622
|
+
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
|
|
623
|
+
failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
|
|
624
|
+
statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
|
|
625
|
+
log: env('SECURENOW_FIREWALL_LOG') !== '0',
|
|
626
|
+
tcp: env('SECURENOW_FIREWALL_TCP') === '1',
|
|
627
|
+
iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
|
|
628
|
+
cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
|
|
629
|
+
});
|
|
630
|
+
} catch (e) {
|
|
631
|
+
console.warn('[securenow] Firewall init failed:', e.message);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
module.exports = {
|
|
637
|
+
registerSecureNow,
|
|
638
|
+
};
|
|
639
|
+
|