securenow 5.18.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +40 -239
  3. package/cli.js +455 -415
  4. package/console-instrumentation.js +136 -147
  5. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
  6. package/docs/ARCHITECTURE.md +3 -3
  7. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  8. package/docs/AUTO-SETUP.md +4 -4
  9. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  10. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  11. package/docs/CHANGELOG-NEXTJS.md +35 -1
  12. package/docs/CUSTOMER-GUIDE.md +16 -16
  13. package/docs/EASIEST-SETUP.md +5 -5
  14. package/docs/ENVIRONMENT-VARIABLES.md +652 -880
  15. package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
  16. package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
  17. package/docs/INDEX.md +4 -22
  18. package/docs/LOGGING-GUIDE.md +708 -701
  19. package/docs/LOGGING-QUICKSTART.md +255 -234
  20. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  21. package/docs/NEXTJS-GUIDE.md +14 -14
  22. package/docs/NEXTJS-QUICKSTART.md +1 -1
  23. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  24. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  25. package/docs/REDACTION-EXAMPLES.md +1 -1
  26. package/docs/REQUEST-BODY-CAPTURE.md +10 -19
  27. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  28. package/examples/README.md +6 -6
  29. package/examples/instrumentation-with-auto-capture.ts +1 -1
  30. package/examples/nextjs-env-example.txt +2 -2
  31. package/examples/nextjs-instrumentation.js +1 -1
  32. package/examples/nextjs-instrumentation.ts +1 -1
  33. package/examples/nextjs-with-logging-example.md +6 -6
  34. package/examples/nextjs-with-options.ts +1 -1
  35. package/examples/test-nextjs-setup.js +1 -1
  36. package/nextjs-auto-capture.js +207 -199
  37. package/nextjs-middleware.js +181 -186
  38. package/nextjs-webpack-config.js +53 -88
  39. package/nextjs-wrapper.js +158 -158
  40. package/nextjs.d.ts +1 -1
  41. package/nextjs.js +198 -186
  42. package/package.json +45 -67
  43. package/postinstall.js +6 -6
  44. package/register.d.ts +1 -1
  45. package/register.js +4 -39
  46. package/tracing.d.ts +1 -2
  47. package/tracing.js +26 -286
  48. package/web-vite.mjs +156 -239
  49. package/CONSUMING-APPS-GUIDE.md +0 -455
  50. package/NPM_README.md +0 -1933
  51. package/SKILL-API.md +0 -600
  52. package/SKILL-CLI.md +0 -409
  53. package/cidr.js +0 -83
  54. package/cli/apps.js +0 -585
  55. package/cli/auth.js +0 -280
  56. package/cli/client.js +0 -115
  57. package/cli/config.js +0 -173
  58. package/cli/firewall.js +0 -100
  59. package/cli/fp.js +0 -638
  60. package/cli/init.js +0 -201
  61. package/cli/monitor.js +0 -440
  62. package/cli/run.js +0 -133
  63. package/cli/security.js +0 -1064
  64. package/cli/ui.js +0 -386
  65. package/docs/API-KEYS-GUIDE.md +0 -233
  66. package/docs/AUTO-SETUP-SUMMARY.md +0 -331
  67. package/docs/BODY-CAPTURE-FIX.md +0 -261
  68. package/docs/COMPLETION-REPORT.md +0 -408
  69. package/docs/FINAL-SOLUTION.md +0 -335
  70. package/docs/FIREWALL-GUIDE.md +0 -426
  71. package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
  72. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
  73. package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
  74. package/docs/NUXT-GUIDE.md +0 -166
  75. package/docs/SOLUTION-SUMMARY.md +0 -312
  76. package/firewall-cloud.js +0 -212
  77. package/firewall-iptables.js +0 -139
  78. package/firewall-only.js +0 -38
  79. package/firewall-tcp.js +0 -74
  80. package/firewall.js +0 -720
  81. package/free-trial-banner.js +0 -174
  82. package/nuxt-server-plugin.mjs +0 -423
  83. package/nuxt.d.ts +0 -60
  84. package/nuxt.mjs +0 -75
  85. package/resolve-ip.js +0 -77
@@ -55,24 +55,16 @@ SECURENOW_SENSITIVE_FIELDS=credit_card,email,phone
55
55
  ```
56
56
  Parsed into object and sensitive fields redacted.
57
57
 
58
- 4. **Multipart** (`multipart/form-data`) - Streaming Metadata Capture (v5.8.0+)
59
-
60
- Requires `SECURENOW_CAPTURE_MULTIPART=1`. Uses a streaming parser — file binary content is never buffered.
61
-
62
- ```json
63
- {
64
- "fields": { "description": "Profile update", "token": "[REDACTED]" },
65
- "files": [
66
- { "field": "avatar", "filename": "photo.jpg", "contentType": "image/jpeg", "size": 524288 }
67
- ]
68
- }
58
+ 4. **Multipart** (`multipart/form-data`) - NOT Captured
59
+ ```
60
+ [MULTIPART - NOT CAPTURED]
69
61
  ```
70
- Text field values are captured with sensitive-field redaction. File parts record metadata only (field, filename, content-type, size). Memory stays at ~few KB regardless of upload size.
62
+ *File uploads are not captured at all (by design) - too large and unnecessary*
71
63
 
72
64
  ### ❌ What's NOT Captured
73
65
 
74
66
  - GET requests (no body)
75
- - File binary content (only metadata when multipart capture is enabled)
67
+ - File uploads (too large)
76
68
  - Bodies larger than max size
77
69
  - Binary data
78
70
  - Non-POST/PUT/PATCH requests
@@ -226,7 +218,6 @@ mutation Login {
226
218
  | `SECURENOW_CAPTURE_BODY` | `0` (disabled) | Enable body capture |
227
219
  | `SECURENOW_MAX_BODY_SIZE` | `10240` (10KB) | Maximum body size to capture |
228
220
  | `SECURENOW_SENSITIVE_FIELDS` | `` | Comma-separated custom sensitive fields |
229
- | `SECURENOW_CAPTURE_MULTIPART` | `0` (disabled) | Enable multipart/form-data streaming capture (v5.8.0+) |
230
221
 
231
222
  ### Programmatic (Next.js)
232
223
 
@@ -244,7 +235,7 @@ export function register() {
244
235
 
245
236
  ---
246
237
 
247
- ## 🔍 Viewing in SecureNow
238
+ ## 🔍 Viewing in SigNoz
248
239
 
249
240
  ### Query Examples
250
241
 
@@ -286,7 +277,7 @@ Request bodies may contain personal data. Consider:
286
277
 
287
278
  1. **Legal Basis** - Ensure you have legitimate interest or consent
288
279
  2. **Data Minimization** - Only capture what you need
289
- 3. **Retention** - Configure SecureNow retention policies
280
+ 3. **Retention** - Configure SigNoz retention policies
290
281
  4. **Anonymization** - Add more fields to redact list
291
282
 
292
283
  ### PCI-DSS Compliance
@@ -406,7 +397,7 @@ Field matching is case-insensitive and uses `includes()`:
406
397
  }
407
398
  ```
408
399
 
409
- 3. **Filter in SecureNow**
400
+ 3. **Filter in SigNoz**
410
401
  - Use sampling to reduce volume
411
402
  - Set up trace tail sampling
412
403
 
@@ -538,7 +529,7 @@ SECURENOW_SENSITIVE_FIELDS="" # Don't do this!
538
529
 
539
530
  ### Q: Does this work with file uploads?
540
531
 
541
- **A:** Yes! Since v5.8.0, set `SECURENOW_CAPTURE_MULTIPART=1` to capture multipart/form-data requests. The streaming parser extracts text field values and file metadata (name, filename, content-type, size) without ever buffering file binary content. Memory stays bounded at ~few KB regardless of upload size.
532
+ **A:** No, multipart/form-data is not captured (by design). Only metadata is logged.
542
533
 
543
534
  ### Q: What's the performance impact?
544
535
 
@@ -573,7 +564,7 @@ SECURENOW_SENSITIVE_FIELDS="" # Don't do this!
573
564
 
574
565
  3. **Deploy!** Bodies are captured with sensitive data automatically redacted.
575
566
 
576
- **View in SecureNow:**
567
+ **View in SigNoz:**
577
568
  - `http.request.body` - The captured body (redacted)
578
569
  - `http.request.body.size` - Body size in bytes
579
570
  - `http.request.body.type` - Content type (json, graphql, form)
@@ -62,7 +62,7 @@ export function register() {
62
62
  ```bash
63
63
  # .env.local
64
64
  SECURENOW_APPID=my-nextjs-app
65
- SECURENOW_INSTANCE=http://your-otlp-backend:4318
65
+ SECURENOW_INSTANCE=http://your-signoz:4318
66
66
  ```
67
67
 
68
68
  ### What They Get
@@ -89,7 +89,7 @@ SECURENOW_INSTANCE=http://your-otlp-backend:4318
89
89
  - `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`
90
90
  3. SecureNow calls `@vercel/otel`'s `registerOTel()`
91
91
  4. @vercel/otel handles all the OpenTelemetry setup
92
- 5. Traces flow to SecureNow
92
+ 5. Traces flow to SigNoz
93
93
 
94
94
  ### What @vercel/otel Does
95
95
 
@@ -185,7 +185,7 @@ All options still work:
185
185
  ```typescript
186
186
  registerSecureNow({
187
187
  serviceName: 'my-app',
188
- endpoint: 'http://otel-collector:4318',
188
+ endpoint: 'http://signoz:4318',
189
189
  noUuid: false,
190
190
  });
191
191
  ```
@@ -160,7 +160,7 @@ OTEL_LOG_LEVEL=info
160
160
  ```bash
161
161
  # Set in Vercel dashboard:
162
162
  SECURENOW_APPID=my-app
163
- SECURENOW_INSTANCE=http://your-collector-host:4318
163
+ SECURENOW_INSTANCE=http://your-signoz:4318
164
164
  OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
165
165
  ```
166
166
 
@@ -180,7 +180,7 @@ npm install securenow@latest
180
180
  1. Check console for `[securenow] ✅ OpenTelemetry started`
181
181
  2. Enable debug mode: `OTEL_LOG_LEVEL=debug`
182
182
  3. Run test script: `node examples/test-nextjs-setup.js`
183
- 4. Verify OTLP collector accessibility: `curl http://your-collector-host:4318/v1/traces`
183
+ 4. Verify SigNoz accessibility: `curl http://your-signoz:4318/v1/traces`
184
184
 
185
185
  ### Too many spans
186
186
 
@@ -206,7 +206,7 @@ registerSecureNow({
206
206
  ```typescript
207
207
  registerSecureNow({
208
208
  headers: {
209
- 'x-api-key': process.env.SECURENOW_API_KEY,
209
+ 'x-api-key': process.env.SIGNOZ_API_KEY,
210
210
  'x-environment': process.env.NODE_ENV,
211
211
  },
212
212
  });
@@ -229,7 +229,7 @@ After setting up:
229
229
 
230
230
  1. **Run your app** and verify traces appear
231
231
  2. **Test key user flows** to see end-to-end tracing
232
- 3. **Check SecureNow dashboard** for service map and traces
232
+ 3. **Check SigNoz dashboard** for service map and traces
233
233
  4. **Adjust configuration** based on your needs
234
234
  5. **Deploy to production** with proper environment variables
235
235
 
@@ -242,12 +242,12 @@ $ npm run dev
242
242
 
243
243
  [securenow] Next.js integration loading (pid=12345)
244
244
  [securenow] 🚀 Next.js App → service.name=my-app-abc123
245
- [securenow] ✅ OpenTelemetry started for Next.js → http://your-collector-host:4318/v1/traces
245
+ [securenow] ✅ OpenTelemetry started for Next.js → http://signoz:4318/v1/traces
246
246
 
247
247
  ✓ Ready in 1.2s
248
248
  ```
249
249
 
250
- Then in SecureNow:
250
+ Then in SigNoz:
251
251
  - ✅ See your service in service map
252
252
  - ✅ View traces for requests
253
253
  - ✅ Analyze performance metrics
@@ -30,7 +30,7 @@ export function register() {
30
30
  *
31
31
  * Configuration in .env.local:
32
32
  * SECURENOW_APPID=my-app
33
- * SECURENOW_INSTANCE=http://localhost:4318
33
+ * SECURENOW_INSTANCE=http://signoz:4318
34
34
  * SECURENOW_CAPTURE_BODY=1
35
35
  * SECURENOW_MAX_BODY_SIZE=10240
36
36
  * SECURENOW_SENSITIVE_FIELDS=custom_field
@@ -4,9 +4,9 @@
4
4
  # Required: Your application identifier
5
5
  SECURENOW_APPID=my-nextjs-app
6
6
 
7
- # Optional: Your OTLP collector endpoint (SecureNow or any OTLP-compatible backend)
7
+ # Optional: Your SigNoz/OpenTelemetry collector endpoint
8
8
  # Default: https://freetrial.securenow.ai:4318
9
- SECURENOW_INSTANCE=http://your-otlp-collector:4318
9
+ SECURENOW_INSTANCE=http://your-signoz-server:4318
10
10
 
11
11
  # Optional: API Key or authentication headers
12
12
  OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-api-key-here"
@@ -21,7 +21,7 @@ export function register() {
21
21
  * SECURENOW_APPID=my-nextjs-app
22
22
  *
23
23
  * Optional:
24
- * SECURENOW_INSTANCE=http://your-otlp-collector:4318
24
+ * SECURENOW_INSTANCE=http://your-signoz-server:4318
25
25
  * SECURENOW_NO_UUID=1 # Don't append UUID to service name
26
26
  * OTEL_LOG_LEVEL=info # debug|info|warn|error
27
27
  * SECURENOW_DISABLE_INSTRUMENTATIONS=fs # Comma-separated list
@@ -21,7 +21,7 @@ export function register() {
21
21
  * SECURENOW_APPID=my-nextjs-app
22
22
  *
23
23
  * Optional:
24
- * SECURENOW_INSTANCE=http://your-otlp-collector:4318
24
+ * SECURENOW_INSTANCE=http://your-signoz-server:4318
25
25
  * SECURENOW_NO_UUID=1 # Don't append UUID to service name
26
26
  * OTEL_LOG_LEVEL=info # debug|info|warn|error
27
27
  * SECURENOW_DISABLE_INSTRUMENTATIONS=fs # Comma-separated list
@@ -39,9 +39,9 @@ SECURENOW_LOGGING_ENABLED=1
39
39
  SECURENOW_APPID=my-nextjs-app
40
40
  SECURENOW_INSTANCE=http://localhost:4318
41
41
 
42
- # For SecureNow or managed OTLP (example):
43
- # SECURENOW_INSTANCE=https://ingest.<region>.securenow.ai:443
44
- # OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<your-key>"
42
+ # For SigNoz Cloud:
43
+ # SECURENOW_INSTANCE=https://ingest.<region>.signoz.cloud:443
44
+ # OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-key>"
45
45
  ```
46
46
 
47
47
  ### 4. Enable Instrumentation in `next.config.js`
@@ -239,9 +239,9 @@ You should see in the console:
239
239
  SecureNow initialized with logging enabled
240
240
  ```
241
241
 
242
- ## View Logs in SecureNow
242
+ ## View Logs in SigNoz
243
243
 
244
- 1. Open your SecureNow dashboard
244
+ 1. Open your SigNoz dashboard
245
245
  2. Navigate to **Logs** section
246
246
  3. Filter by `service.name = my-nextjs-app`
247
247
  4. See all your application logs with:
@@ -298,4 +298,4 @@ await import('securenow/console-instrumentation'); // Second
298
298
 
299
299
  - [Complete Logging Guide](../docs/LOGGING-GUIDE.md)
300
300
  - [Next.js Complete Guide](../docs/NEXTJS-GUIDE.md)
301
- - [SecureNow](https://securenow.ai/)
301
+ - [View Logs in SigNoz](https://signoz.io/docs/logs-management/overview/)
@@ -10,7 +10,7 @@ import { registerSecureNow } from 'securenow/nextjs';
10
10
  export function register() {
11
11
  registerSecureNow({
12
12
  serviceName: 'my-nextjs-app',
13
- endpoint: 'http://your-otlp-collector:4318',
13
+ endpoint: 'http://your-signoz-server:4318',
14
14
  noUuid: false,
15
15
  disableInstrumentations: ['fs', 'dns'],
16
16
  headers: {
@@ -58,7 +58,7 @@ setTimeout(() => {
58
58
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
59
59
  console.log('✅ All tests passed!');
60
60
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
61
- console.log('\nCheck your SecureNow dashboard for traces from "test-nextjs-app"\n');
61
+ console.log('\nCheck your SigNoz dashboard for traces from "test-nextjs-app"\n');
62
62
  process.exit(0);
63
63
  }, 2000);
64
64
 
@@ -1,199 +1,207 @@
1
- /**
2
- * SecureNow Next.js Automatic Body Capture
3
- *
4
- * This module automatically patches Next.js request handling to capture bodies
5
- * WITHOUT requiring customers to wrap their handlers or change their code.
6
- *
7
- * Usage in instrumentation.ts:
8
- *
9
- * import { registerSecureNow } from 'securenow/nextjs';
10
- * import 'securenow/nextjs-auto-capture'; // Just import this line!
11
- *
12
- * export function register() {
13
- * registerSecureNow();
14
- * }
15
- *
16
- * That's it! Bodies are now captured automatically.
17
- */
18
-
19
- const { trace } = require('@opentelemetry/api');
20
-
21
- // Default sensitive fields to redact
22
- const DEFAULT_SENSITIVE_FIELDS = [
23
- 'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
24
- 'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
25
- 'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
26
- ];
27
-
28
- /**
29
- * Redact sensitive fields from an object
30
- */
31
- function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
32
- if (!obj || typeof obj !== 'object') return obj;
33
-
34
- const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
35
-
36
- for (const key of Object.keys(redacted)) {
37
- const lowerKey = key.toLowerCase();
38
-
39
- if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
40
- redacted[key] = '[REDACTED]';
41
- } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
42
- redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
43
- }
44
- }
45
-
46
- return redacted;
47
- }
48
-
49
- /**
50
- * Safe body capture that doesn't interfere with Next.js
51
- */
52
- async function safeBodyCapture(request, span) {
53
- if (!span) return;
54
-
55
- try {
56
- const contentType = request.headers.get('content-type') || '';
57
- const maxBodySize = Math.max(1024, parseInt(process.env.SECURENOW_MAX_BODY_SIZE, 10) || 10240);
58
- const customSensitiveFields = (process.env.SECURENOW_SENSITIVE_FIELDS || '').split(',').map(s => s.trim()).filter(Boolean);
59
- const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
60
-
61
- // Only for supported types
62
- if (!contentType.includes('application/json') &&
63
- !contentType.includes('application/graphql') &&
64
- !contentType.includes('application/x-www-form-urlencoded')) {
65
- return;
66
- }
67
-
68
- // Try to read from cache if available (Next.js may have already read it)
69
- let bodyText;
70
-
71
- // Attempt 1: Check if body was already cached by Next.js
72
- if (request._bodyText) {
73
- bodyText = request._bodyText;
74
- } else {
75
- // Attempt 2: Try to clone and read
76
- try {
77
- const cloned = request.clone();
78
- bodyText = await cloned.text();
79
- // Cache it for Next.js
80
- request._bodyText = bodyText;
81
- } catch (e) {
82
- // If clone fails, body was already consumed - skip silently
83
- return;
84
- }
85
- }
86
-
87
- if (bodyText.length > maxBodySize) {
88
- span.setAttribute('http.request.body', `[TOO LARGE: ${bodyText.length} bytes]`);
89
- return;
90
- }
91
-
92
- // Parse and redact
93
- if (contentType.includes('application/json') || contentType.includes('application/graphql')) {
94
- try {
95
- const parsed = JSON.parse(bodyText);
96
- const redacted = redactSensitiveData(parsed, allSensitiveFields);
97
- span.setAttributes({
98
- 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
99
- 'http.request.body.type': contentType.includes('graphql') ? 'graphql' : 'json',
100
- 'http.request.body.size': bodyText.length,
101
- });
102
- } catch (e) {
103
- // Parse error - skip
104
- }
105
- } else if (contentType.includes('application/x-www-form-urlencoded')) {
106
- try {
107
- const params = new URLSearchParams(bodyText);
108
- const parsed = Object.fromEntries(params);
109
- const redacted = redactSensitiveData(parsed, allSensitiveFields);
110
- span.setAttributes({
111
- 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
112
- 'http.request.body.type': 'form',
113
- 'http.request.body.size': bodyText.length,
114
- });
115
- } catch (e) {
116
- // Parse error - skip
117
- }
118
- }
119
- } catch (error) {
120
- // Silently fail - never break the request
121
- }
122
- }
123
-
124
- /**
125
- * Check if body capture is enabled
126
- */
127
- function isBodyCaptureEnabled() {
128
- const enabled = String(process.env.SECURENOW_CAPTURE_BODY) === '1' ||
129
- String(process.env.SECURENOW_CAPTURE_BODY).toLowerCase() === 'true';
130
- return enabled;
131
- }
132
-
133
- /**
134
- * Patch Next.js Request to cache body text
135
- * This allows us to read the body without consuming it
136
- */
137
- function patchNextRequest() {
138
- if (typeof Request === 'undefined') return;
139
-
140
- const originalText = Request.prototype.text;
141
- const originalJson = Request.prototype.json;
142
-
143
- // Patch text() to cache result
144
- Request.prototype.text = async function() {
145
- if (this._bodyText !== undefined) {
146
- return this._bodyText;
147
- }
148
- const text = await originalText.call(this);
149
- this._bodyText = text;
150
-
151
- // Capture for tracing if enabled
152
- if (isBodyCaptureEnabled() && ['POST', 'PUT', 'PATCH'].includes(this.method)) {
153
- const span = trace.getActiveSpan();
154
- if (span) {
155
- // Schedule capture after this call (non-blocking)
156
- setImmediate(() => {
157
- safeBodyCapture(this, span).catch(() => {});
158
- });
159
- }
160
- }
161
-
162
- return text;
163
- };
164
-
165
- // Patch json() to cache and capture
166
- Request.prototype.json = async function() {
167
- // First get text
168
- const text = await this.text();
169
- // Then parse
170
- return JSON.parse(text);
171
- };
172
-
173
- console.log('[securenow] ✅ Auto-capture: Patched Next.js Request for automatic body capture');
174
- }
175
-
176
- // Auto-patch when module is imported
177
- if (isBodyCaptureEnabled()) {
178
- try {
179
- patchNextRequest();
180
- console.log('[securenow] 📝 Automatic body capture: ENABLED');
181
- console.log('[securenow] 💡 No code changes needed - bodies captured automatically!');
182
- } catch (error) {
183
- console.warn('[securenow] ⚠️ Auto-capture patch failed:', error.message);
184
- console.warn('[securenow] 💡 Body capture disabled. Use manual approach if needed.');
185
- }
186
- } else {
187
- console.log('[securenow] 📝 Automatic body capture: DISABLED (set SECURENOW_CAPTURE_BODY=1 to enable)');
188
- }
189
-
190
- module.exports = {
191
- patchNextRequest,
192
- safeBodyCapture,
193
- redactSensitiveData,
194
- isBodyCaptureEnabled,
195
- };
196
-
197
-
198
-
199
-
1
+ /**
2
+ * SecureNow Next.js Automatic Body Capture
3
+ *
4
+ * This module automatically patches Next.js request handling to capture bodies
5
+ * WITHOUT requiring customers to wrap their handlers or change their code.
6
+ *
7
+ * Usage in instrumentation.ts:
8
+ *
9
+ * import { registerSecureNow } from 'securenow/nextjs';
10
+ * import 'securenow/nextjs-auto-capture'; // Just import this line!
11
+ *
12
+ * export function register() {
13
+ * registerSecureNow();
14
+ * }
15
+ *
16
+ * That's it! Bodies are now captured automatically.
17
+ */
18
+
19
+ const { trace } = require('@opentelemetry/api');
20
+
21
+ // Default sensitive fields to redact
22
+ const DEFAULT_SENSITIVE_FIELDS = [
23
+ 'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
24
+ 'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
25
+ 'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
26
+ ];
27
+
28
+ /**
29
+ * Redact sensitive fields from an object
30
+ */
31
+ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
32
+ if (!obj || typeof obj !== 'object') return obj;
33
+
34
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
35
+
36
+ for (const key in redacted) {
37
+ const lowerKey = key.toLowerCase();
38
+
39
+ if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
40
+ redacted[key] = '[REDACTED]';
41
+ } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
42
+ redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
43
+ }
44
+ }
45
+
46
+ return redacted;
47
+ }
48
+
49
+ /**
50
+ * Safe body capture that doesn't interfere with Next.js
51
+ */
52
+ async function safeBodyCapture(request, span) {
53
+ if (!span) return;
54
+
55
+ try {
56
+ const contentType = request.headers.get('content-type') || '';
57
+ const maxBodySize = parseInt(process.env.SECURENOW_MAX_BODY_SIZE || '10240');
58
+ const customSensitiveFields = (process.env.SECURENOW_SENSITIVE_FIELDS || '').split(',').map(s => s.trim()).filter(Boolean);
59
+ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
60
+
61
+ // Only for supported types
62
+ if (!contentType.includes('application/json') &&
63
+ !contentType.includes('application/graphql') &&
64
+ !contentType.includes('application/x-www-form-urlencoded')) {
65
+ return;
66
+ }
67
+
68
+ // Try to read from cache if available (Next.js may have already read it)
69
+ let bodyText;
70
+
71
+ // Attempt 1: Check if body was already cached by Next.js
72
+ if (request._bodyText) {
73
+ bodyText = request._bodyText;
74
+ } else {
75
+ // Attempt 2: Try to clone and read
76
+ try {
77
+ const cloned = request.clone();
78
+ bodyText = await cloned.text();
79
+ // Cache it for Next.js
80
+ request._bodyText = bodyText;
81
+ } catch (e) {
82
+ // If clone fails, body was already consumed - skip silently
83
+ return;
84
+ }
85
+ }
86
+
87
+ if (bodyText.length > maxBodySize) {
88
+ span.setAttribute('http.request.body', `[TOO LARGE: ${bodyText.length} bytes]`);
89
+ return;
90
+ }
91
+
92
+ // Parse and redact
93
+ if (contentType.includes('application/json') || contentType.includes('application/graphql')) {
94
+ try {
95
+ const parsed = JSON.parse(bodyText);
96
+ const redacted = redactSensitiveData(parsed, allSensitiveFields);
97
+ span.setAttributes({
98
+ 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
99
+ 'http.request.body.type': contentType.includes('graphql') ? 'graphql' : 'json',
100
+ 'http.request.body.size': bodyText.length,
101
+ });
102
+ } catch (e) {
103
+ // Parse error - skip
104
+ }
105
+ } else if (contentType.includes('application/x-www-form-urlencoded')) {
106
+ try {
107
+ const params = new URLSearchParams(bodyText);
108
+ const parsed = Object.fromEntries(params);
109
+ const redacted = redactSensitiveData(parsed, allSensitiveFields);
110
+ span.setAttributes({
111
+ 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
112
+ 'http.request.body.type': 'form',
113
+ 'http.request.body.size': bodyText.length,
114
+ });
115
+ } catch (e) {
116
+ // Parse error - skip
117
+ }
118
+ }
119
+ } catch (error) {
120
+ // Silently fail - never break the request
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Check if body capture is enabled
126
+ */
127
+ function isBodyCaptureEnabled() {
128
+ const enabled = String(process.env.SECURENOW_CAPTURE_BODY) === '1' ||
129
+ String(process.env.SECURENOW_CAPTURE_BODY).toLowerCase() === 'true';
130
+ return enabled;
131
+ }
132
+
133
+ /**
134
+ * Patch Next.js Request to cache body text
135
+ * This allows us to read the body without consuming it
136
+ */
137
+ function patchNextRequest() {
138
+ if (typeof Request === 'undefined') return;
139
+
140
+ const originalText = Request.prototype.text;
141
+ const originalJson = Request.prototype.json;
142
+ const originalFormData = Request.prototype.formData;
143
+
144
+ // Patch text() to cache result
145
+ Request.prototype.text = async function() {
146
+ if (this._bodyText !== undefined) {
147
+ return this._bodyText;
148
+ }
149
+ const text = await originalText.call(this);
150
+ this._bodyText = text;
151
+
152
+ // Capture for tracing if enabled
153
+ if (isBodyCaptureEnabled() && ['POST', 'PUT', 'PATCH'].includes(this.method)) {
154
+ const span = trace.getActiveSpan();
155
+ if (span) {
156
+ // Schedule capture after this call (non-blocking)
157
+ setImmediate(() => {
158
+ safeBodyCapture(this, span).catch(() => {});
159
+ });
160
+ }
161
+ }
162
+
163
+ return text;
164
+ };
165
+
166
+ // Patch json() to cache and capture
167
+ Request.prototype.json = async function() {
168
+ // First get text
169
+ const text = await this.text();
170
+ // Then parse
171
+ return JSON.parse(text);
172
+ };
173
+
174
+ // Patch formData() to cache and capture
175
+ Request.prototype.formData = async function() {
176
+ const text = await this.text();
177
+ const params = new URLSearchParams(text);
178
+ return params;
179
+ };
180
+
181
+ console.log('[securenow] Auto-capture: Patched Next.js Request for automatic body capture');
182
+ }
183
+
184
+ // Auto-patch when module is imported
185
+ if (isBodyCaptureEnabled()) {
186
+ try {
187
+ patchNextRequest();
188
+ console.log('[securenow] 📝 Automatic body capture: ENABLED');
189
+ console.log('[securenow] 💡 No code changes needed - bodies captured automatically!');
190
+ } catch (error) {
191
+ console.warn('[securenow] ⚠️ Auto-capture patch failed:', error.message);
192
+ console.warn('[securenow] 💡 Body capture disabled. Use manual approach if needed.');
193
+ }
194
+ } else {
195
+ console.log('[securenow] 📝 Automatic body capture: DISABLED (set SECURENOW_CAPTURE_BODY=1 to enable)');
196
+ }
197
+
198
+ module.exports = {
199
+ patchNextRequest,
200
+ safeBodyCapture,
201
+ redactSensitiveData,
202
+ isBodyCaptureEnabled,
203
+ };
204
+
205
+
206
+
207
+