securenow 4.0.3 → 4.0.5

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.
@@ -0,0 +1,178 @@
1
+ /**
2
+ * SecureNow Next.js Middleware for Body Capture
3
+ *
4
+ * OPTIONAL: Import this in your Next.js app to enable automatic body capture
5
+ *
6
+ * Usage:
7
+ *
8
+ * Create middleware.ts in your Next.js app root:
9
+ *
10
+ * export { middleware } from 'securenow/nextjs-middleware';
11
+ * export const config = {
12
+ * matcher: '/api/:path*', // Apply to API routes only
13
+ * };
14
+ *
15
+ * That's it! Bodies are now captured with sensitive data redacted.
16
+ */
17
+
18
+ const { trace, context, SpanStatusCode } = require('@opentelemetry/api');
19
+
20
+ // Default sensitive fields to redact
21
+ const DEFAULT_SENSITIVE_FIELDS = [
22
+ 'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
23
+ 'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
24
+ 'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
25
+ ];
26
+
27
+ /**
28
+ * Redact sensitive fields from an object
29
+ */
30
+ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
31
+ if (!obj || typeof obj !== 'object') return obj;
32
+
33
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
34
+
35
+ for (const key in redacted) {
36
+ const lowerKey = key.toLowerCase();
37
+
38
+ if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
39
+ redacted[key] = '[REDACTED]';
40
+ } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
41
+ redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
42
+ }
43
+ }
44
+
45
+ return redacted;
46
+ }
47
+
48
+ /**
49
+ * Redact sensitive data from GraphQL query strings
50
+ */
51
+ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
52
+ if (!query || typeof query !== 'string') return query;
53
+
54
+ let redacted = query;
55
+
56
+ sensitiveFields.forEach(field => {
57
+ const patterns = [
58
+ new RegExp(`(${field}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
59
+ new RegExp(`(${field}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
60
+ ];
61
+
62
+ patterns.forEach(pattern => {
63
+ redacted = redacted.replace(pattern, (match, prefix, value, suffix) => {
64
+ return suffix ? `${prefix}[REDACTED]${suffix}` : `${prefix}[REDACTED]`;
65
+ });
66
+ });
67
+ });
68
+
69
+ return redacted;
70
+ }
71
+
72
+ /**
73
+ * Next.js Middleware for Body Capture
74
+ */
75
+ async function middleware(request) {
76
+ const { NextResponse } = require('next/server');
77
+
78
+ // Only capture for POST/PUT/PATCH
79
+ if (!['POST', 'PUT', 'PATCH'].includes(request.method)) {
80
+ return NextResponse.next();
81
+ }
82
+
83
+ // Get or create a tracer
84
+ const tracer = trace.getTracer('securenow-middleware');
85
+ let span = trace.getActiveSpan();
86
+ let createdSpan = false;
87
+
88
+ // If no active span, create one for this middleware
89
+ if (!span) {
90
+ const url = new URL(request.url);
91
+ span = tracer.startSpan(`middleware ${request.method} ${url.pathname}`);
92
+ createdSpan = true;
93
+ }
94
+
95
+ try {
96
+ const contentType = request.headers.get('content-type') || '';
97
+ const maxBodySize = parseInt(process.env.SECURENOW_MAX_BODY_SIZE || '10240');
98
+ const customSensitiveFields = (process.env.SECURENOW_SENSITIVE_FIELDS || '').split(',').map(s => s.trim()).filter(Boolean);
99
+ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
100
+
101
+ // Only capture supported types
102
+ if (contentType.includes('application/json') ||
103
+ contentType.includes('application/graphql')) {
104
+
105
+ // Clone the request to read body without consuming the original
106
+ const clonedRequest = request.clone();
107
+ const bodyText = await clonedRequest.text();
108
+
109
+ if (bodyText.length <= maxBodySize) {
110
+ let redactedBody;
111
+
112
+ if (contentType.includes('application/graphql')) {
113
+ // GraphQL: redact query string
114
+ redactedBody = redactGraphQLQuery(bodyText, allSensitiveFields);
115
+ } else {
116
+ // JSON: parse and redact
117
+ try {
118
+ const parsed = JSON.parse(bodyText);
119
+ const redacted = redactSensitiveData(parsed, allSensitiveFields);
120
+ redactedBody = JSON.stringify(redacted);
121
+ } catch (e) {
122
+ redactedBody = bodyText; // Keep as-is if parse fails
123
+ }
124
+ }
125
+
126
+ span.setAttributes({
127
+ 'http.request.body': redactedBody.substring(0, maxBodySize),
128
+ 'http.request.body.type': contentType.includes('graphql') ? 'graphql' : 'json',
129
+ 'http.request.body.size': bodyText.length,
130
+ });
131
+ } else {
132
+ span.setAttribute('http.request.body', `[TOO LARGE: ${bodyText.length} bytes]`);
133
+ }
134
+ } else if (contentType.includes('application/x-www-form-urlencoded')) {
135
+ const clonedRequest = request.clone();
136
+ const formData = await clonedRequest.formData();
137
+ const parsed = Object.fromEntries(formData);
138
+ const redacted = redactSensitiveData(parsed, allSensitiveFields);
139
+
140
+ span.setAttributes({
141
+ 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
142
+ 'http.request.body.type': 'form',
143
+ 'http.request.body.size': JSON.stringify(parsed).length,
144
+ });
145
+ } else if (contentType.includes('multipart/form-data')) {
146
+ span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
147
+ span.setAttribute('http.request.body.type', 'multipart');
148
+ }
149
+
150
+ // End span if we created it
151
+ if (createdSpan) {
152
+ span.setStatus({ code: SpanStatusCode.OK });
153
+ span.end();
154
+ }
155
+ } catch (error) {
156
+ // Silently fail - don't break the request
157
+ console.debug('[securenow] Body capture failed:', error.message);
158
+
159
+ // End span with error if we created it
160
+ if (createdSpan && span) {
161
+ span.setStatus({
162
+ code: SpanStatusCode.ERROR,
163
+ message: error.message
164
+ });
165
+ span.end();
166
+ }
167
+ }
168
+
169
+ return NextResponse.next();
170
+ }
171
+
172
+ module.exports = {
173
+ middleware,
174
+ redactSensitiveData,
175
+ redactGraphQLQuery,
176
+ DEFAULT_SENSITIVE_FIELDS,
177
+ };
178
+
@@ -0,0 +1,155 @@
1
+ /**
2
+ * SecureNow Next.js API Route Wrapper for Body Capture
3
+ *
4
+ * This approach is NON-INVASIVE and runs INSIDE your handler,
5
+ * so it never blocks or interferes with middleware or routing.
6
+ *
7
+ * Usage:
8
+ *
9
+ * import { withSecureNow } from 'securenow/nextjs-wrapper';
10
+ *
11
+ * export const POST = withSecureNow(async (request) => {
12
+ * // Your handler code - request.body is available as parsed JSON
13
+ * const data = await request.json();
14
+ * return Response.json({ success: true });
15
+ * });
16
+ */
17
+
18
+ const { trace } = require('@opentelemetry/api');
19
+
20
+ // Default sensitive fields to redact
21
+ const DEFAULT_SENSITIVE_FIELDS = [
22
+ 'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
23
+ 'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
24
+ 'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
25
+ ];
26
+
27
+ /**
28
+ * Redact sensitive fields from an object
29
+ */
30
+ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
31
+ if (!obj || typeof obj !== 'object') return obj;
32
+
33
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
34
+
35
+ for (const key in redacted) {
36
+ const lowerKey = key.toLowerCase();
37
+
38
+ if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
39
+ redacted[key] = '[REDACTED]';
40
+ } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
41
+ redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
42
+ }
43
+ }
44
+
45
+ return redacted;
46
+ }
47
+
48
+ /**
49
+ * Capture body from Request object (clone to avoid consuming)
50
+ */
51
+ async function captureRequestBody(request) {
52
+ const captureBody = String(process.env.SECURENOW_CAPTURE_BODY) === '1' ||
53
+ String(process.env.SECURENOW_CAPTURE_BODY).toLowerCase() === 'true';
54
+
55
+ if (!captureBody) return;
56
+ if (!['POST', 'PUT', 'PATCH'].includes(request.method)) return;
57
+
58
+ const span = trace.getActiveSpan();
59
+ if (!span) return;
60
+
61
+ try {
62
+ const contentType = request.headers.get('content-type') || '';
63
+ const maxBodySize = parseInt(process.env.SECURENOW_MAX_BODY_SIZE || '10240');
64
+ const customSensitiveFields = (process.env.SECURENOW_SENSITIVE_FIELDS || '').split(',').map(s => s.trim()).filter(Boolean);
65
+ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
66
+
67
+ // Only for supported types
68
+ if (!contentType.includes('application/json') &&
69
+ !contentType.includes('application/graphql') &&
70
+ !contentType.includes('application/x-www-form-urlencoded')) {
71
+ return;
72
+ }
73
+
74
+ // Clone to avoid consuming the original
75
+ const cloned = request.clone();
76
+ const bodyText = await cloned.text();
77
+
78
+ if (bodyText.length > maxBodySize) {
79
+ span.setAttribute('http.request.body', `[TOO LARGE: ${bodyText.length} bytes]`);
80
+ span.setAttribute('http.request.body.size', bodyText.length);
81
+ return;
82
+ }
83
+
84
+ // Parse and redact based on type
85
+ let redacted;
86
+ if (contentType.includes('application/json') || contentType.includes('application/graphql')) {
87
+ try {
88
+ const parsed = JSON.parse(bodyText);
89
+ redacted = redactSensitiveData(parsed, allSensitiveFields);
90
+ span.setAttributes({
91
+ 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
92
+ 'http.request.body.type': contentType.includes('graphql') ? 'graphql' : 'json',
93
+ 'http.request.body.size': bodyText.length,
94
+ });
95
+ } catch (e) {
96
+ span.setAttribute('http.request.body', '[INVALID JSON]');
97
+ }
98
+ } else if (contentType.includes('application/x-www-form-urlencoded')) {
99
+ const params = new URLSearchParams(bodyText);
100
+ const parsed = Object.fromEntries(params);
101
+ redacted = redactSensitiveData(parsed, allSensitiveFields);
102
+ span.setAttributes({
103
+ 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
104
+ 'http.request.body.type': 'form',
105
+ 'http.request.body.size': bodyText.length,
106
+ });
107
+ }
108
+ } catch (error) {
109
+ // Silently fail - never block the request
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Wrap a Next.js API route handler to capture body
115
+ * This is OPTIONAL and NON-INVASIVE - only use on routes where you want body capture
116
+ */
117
+ function withSecureNow(handler) {
118
+ return async function wrappedHandler(request, context) {
119
+ // Capture body asynchronously (doesn't block handler)
120
+ captureRequestBody(request).catch(() => {
121
+ // Ignore errors silently
122
+ });
123
+
124
+ // Call original handler immediately - no blocking!
125
+ return handler(request, context);
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Alternative: Auto-capture wrapper that tries to capture AFTER handler runs
131
+ * This is even safer as it never interferes with the handler logic
132
+ */
133
+ function withSecureNowAsync(handler) {
134
+ return async function wrappedHandler(request, context) {
135
+ // Try to capture body in background (non-blocking)
136
+ const capturePromise = captureRequestBody(request);
137
+
138
+ // Run handler
139
+ const response = await handler(request, context);
140
+
141
+ // Wait for capture to finish (but don't fail if it doesn't)
142
+ await capturePromise.catch(() => {});
143
+
144
+ return response;
145
+ };
146
+ }
147
+
148
+ module.exports = {
149
+ withSecureNow,
150
+ withSecureNowAsync,
151
+ captureRequestBody,
152
+ redactSensitiveData,
153
+ DEFAULT_SENSITIVE_FIELDS,
154
+ };
155
+
package/nextjs.js CHANGED
@@ -276,11 +276,18 @@ function registerSecureNow(options = {}) {
276
276
  'vercel.region': process.env.VERCEL_REGION || undefined,
277
277
  },
278
278
  instrumentations: [
279
- // Add HTTP instrumentation with request hooks to capture IP, headers, and body
279
+ // Add HTTP instrumentation with request hooks to capture IP, headers
280
+ // NOTE: Body capture is DISABLED at this level for Next.js to prevent conflicts
280
281
  new HttpInstrumentation({
281
282
  requireParentforOutgoingSpans: false,
282
283
  requireParentforIncomingSpans: false,
283
- requestHook: async (span, request) => {
284
+ // Ignore request/response bodies to prevent Next.js conflicts
285
+ ignoreIncomingRequestHook: (request) => {
286
+ // Never ignore - we want to trace all requests
287
+ return false;
288
+ },
289
+ requestHook: (span, request) => {
290
+ // SYNCHRONOUS ONLY - no async operations to avoid timing issues
284
291
  try {
285
292
  // Capture client IP from various headers
286
293
  const headers = request.headers || {};
@@ -294,7 +301,7 @@ function registerSecureNow(options = {}) {
294
301
  request.socket?.remoteAddress ||
295
302
  'unknown';
296
303
 
297
- // Add IP and request metadata to span
304
+ // Add IP and request metadata to span (synchronously)
298
305
  span.setAttributes({
299
306
  'http.client_ip': clientIp,
300
307
  'http.user_agent': headers['user-agent'] || 'unknown',
@@ -319,64 +326,20 @@ function registerSecureNow(options = {}) {
319
326
  span.setAttribute('http.geo.country', headers['cf-ipcountry']);
320
327
  }
321
328
 
322
- // -------- Capture Request Body --------
323
- if (captureBody && request.method && ['POST', 'PUT', 'PATCH'].includes(request.method)) {
324
- const contentType = headers['content-type'] || '';
325
-
326
- // Only capture JSON, GraphQL, and form data (not large files)
327
- if (contentType.includes('application/json') ||
328
- contentType.includes('application/graphql') ||
329
- contentType.includes('application/x-www-form-urlencoded')) {
330
-
331
- const bodyResult = await captureRequestBody(request, maxBodySize);
332
-
333
- if (bodyResult.captured) {
334
- let redactedBody;
335
-
336
- // Redact based on type
337
- if (bodyResult.type === 'graphql') {
338
- // GraphQL: redact query string
339
- redactedBody = redactGraphQLQuery(bodyResult.body, allSensitiveFields);
340
- } else if (typeof bodyResult.body === 'object') {
341
- // JSON/Form: redact object properties
342
- redactedBody = redactSensitiveData(bodyResult.body, allSensitiveFields);
343
- } else {
344
- // Plain text: basic redaction
345
- redactedBody = bodyResult.body;
346
- }
347
-
348
- span.setAttributes({
349
- 'http.request.body': typeof redactedBody === 'string'
350
- ? redactedBody.substring(0, maxBodySize)
351
- : JSON.stringify(redactedBody).substring(0, maxBodySize),
352
- 'http.request.body.type': bodyResult.type,
353
- 'http.request.body.size': bodyResult.size,
354
- });
355
- } else {
356
- span.setAttribute('http.request.body.capture_failed', bodyResult.reason || 'unknown');
357
- }
358
- } else if (contentType.includes('multipart/form-data')) {
359
- // Multipart is NOT captured at all
360
- span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
361
- span.setAttribute('http.request.body.type', 'multipart');
362
- span.setAttribute('http.request.body.note', 'File uploads not captured by design');
363
- }
364
- }
329
+ // -------- Request Body NOT captured at HTTP instrumentation level --------
330
+ // IMPORTANT: Do NOT attempt to read request.body or listen to 'data' events
331
+ // Next.js manages request streams internally and reading them here causes:
332
+ // - "Response body object should not be disturbed or locked" errors
333
+ // - Hanging requests that never complete
334
+ // - Body data unavailable to Next.js route handlers
335
+ //
336
+ // Body capture must be done in Next.js middleware using request.clone()
337
+
365
338
  } catch (error) {
366
339
  // Silently fail to not break the request
367
- console.debug('[securenow] Failed to capture request metadata:', error.message);
340
+ // Do not log in production to avoid noise
368
341
  }
369
- },
370
- responseHook: (span, response) => {
371
- try {
372
- // Add response metadata
373
- if (response.statusCode) {
374
- span.setAttribute('http.status_code', response.statusCode);
375
- }
376
- } catch (error) {
377
- console.debug('[securenow] Failed to capture response metadata:', error.message);
378
- }
379
- },
342
+ }
380
343
  }),
381
344
  ],
382
345
  instrumentationConfig: {
@@ -402,10 +365,10 @@ function registerSecureNow(options = {}) {
402
365
  isRegistered = true;
403
366
  console.log('[securenow] ✅ OpenTelemetry started for Next.js → %s', tracesUrl);
404
367
  console.log('[securenow] 📊 Auto-capturing: IP, User-Agent, Headers, Geographic data');
368
+ console.log('[securenow] ⚠️ Body capture DISABLED at HTTP instrumentation level (prevents Next.js conflicts)');
405
369
  if (captureBody) {
406
- console.log('[securenow] 📝 Request body capture: ENABLED (max: %d bytes, redacting %d sensitive fields)', maxBodySize, allSensitiveFields.length);
407
- } else {
408
- console.log('[securenow] 📝 Request body capture: DISABLED (set SECURENOW_CAPTURE_BODY=1 to enable)');
370
+ console.log('[securenow] 💡 SECURENOW_CAPTURE_BODY is set but has no effect in Next.js HTTP instrumentation');
371
+ console.log('[securenow] 💡 Body capture must be implemented differently for Next.js (coming soon)');
409
372
  }
410
373
 
411
374
  // Optional test span
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "OpenTelemetry instrumentation for Node.js and Next.js - Send traces to SigNoz or any OTLP backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",
@@ -33,6 +33,9 @@
33
33
  "./register": "./register.js",
34
34
  "./tracing": "./tracing.js",
35
35
  "./nextjs": "./nextjs.js",
36
+ "./nextjs-auto-capture": "./nextjs-auto-capture.js",
37
+ "./nextjs-middleware": "./nextjs-middleware.js",
38
+ "./nextjs-wrapper": "./nextjs-wrapper.js",
36
39
  "./nextjs-webpack-config": "./nextjs-webpack-config.js",
37
40
  "./register-vite": "./register-vite.js",
38
41
  "./web-vite": {
@@ -44,6 +47,9 @@
44
47
  "register.js",
45
48
  "tracing.js",
46
49
  "nextjs.js",
50
+ "nextjs-auto-capture.js",
51
+ "nextjs-middleware.js",
52
+ "nextjs-wrapper.js",
47
53
  "cli.js",
48
54
  "postinstall.js",
49
55
  "register-vite.js",
@@ -57,7 +63,16 @@
57
63
  "AUTOMATIC-IP-CAPTURE.md",
58
64
  "REQUEST-BODY-CAPTURE.md",
59
65
  "BODY-CAPTURE-QUICKSTART.md",
60
- "REDACTION-EXAMPLES.md"
66
+ "REDACTION-EXAMPLES.md",
67
+ "NEXTJS-BODY-CAPTURE.md",
68
+ "NEXTJS-BODY-CAPTURE-COMPARISON.md",
69
+ "NEXTJS-WRAPPER-APPROACH.md",
70
+ "QUICKSTART-BODY-CAPTURE.md",
71
+ "AUTO-BODY-CAPTURE.md",
72
+ "EASIEST-SETUP.md",
73
+ "SOLUTION-SUMMARY.md",
74
+ "BODY-CAPTURE-FIX.md",
75
+ "FINAL-SOLUTION.md"
61
76
  ],
62
77
  "dependencies": {
63
78
  "@opentelemetry/api": "1.7.0",
package/postinstall.js CHANGED
@@ -80,6 +80,64 @@ export function register() {
80
80
  * SECURENOW_INSTANCE=http://your-signoz-server:4318
81
81
  * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
82
82
  * OTEL_LOG_LEVEL=info
83
+ *
84
+ * Optional: Enable request body capture
85
+ * SECURENOW_CAPTURE_BODY=1
86
+ * (Also create middleware.ts to activate - run: npx securenow init)
87
+ */
88
+ `;
89
+
90
+ fs.writeFileSync(targetPath, content, 'utf8');
91
+ }
92
+
93
+ // Create TypeScript middleware file
94
+ function createTsMiddleware(targetPath) {
95
+ const content = `// SecureNow Middleware - Automatic Request Body Capture
96
+ // This enables capturing JSON, GraphQL, and Form request bodies
97
+ // with automatic sensitive field redaction
98
+
99
+ export { middleware } from 'securenow/nextjs-middleware';
100
+
101
+ export const config = {
102
+ matcher: '/api/:path*', // Apply to all API routes
103
+ };
104
+
105
+ /**
106
+ * Bodies are captured with:
107
+ * - Automatic redaction of passwords, tokens, cards, etc.
108
+ * - Size limits (configurable via SECURENOW_MAX_BODY_SIZE)
109
+ * - JSON, GraphQL, Form data support
110
+ *
111
+ * Configure in .env.local:
112
+ * SECURENOW_MAX_BODY_SIZE=20480
113
+ * SECURENOW_SENSITIVE_FIELDS=email,phone
114
+ */
115
+ `;
116
+
117
+ fs.writeFileSync(targetPath, content, 'utf8');
118
+ }
119
+
120
+ // Create JavaScript middleware file
121
+ function createJsMiddleware(targetPath) {
122
+ const content = `// SecureNow Middleware - Automatic Request Body Capture
123
+ // This enables capturing JSON, GraphQL, and Form request bodies
124
+ // with automatic sensitive field redaction
125
+
126
+ export { middleware } from 'securenow/nextjs-middleware';
127
+
128
+ export const config = {
129
+ matcher: '/api/:path*', // Apply to all API routes
130
+ };
131
+
132
+ /**
133
+ * Bodies are captured with:
134
+ * - Automatic redaction of passwords, tokens, cards, etc.
135
+ * - Size limits (configurable via SECURENOW_MAX_BODY_SIZE)
136
+ * - JSON, GraphQL, Form data support
137
+ *
138
+ * Configure in .env.local:
139
+ * SECURENOW_MAX_BODY_SIZE=20480
140
+ * SECURENOW_SENSITIVE_FIELDS=email,phone
83
141
  */
84
142
  `;
85
143
 
@@ -101,6 +159,11 @@ SECURENOW_INSTANCE=http://your-signoz-server:4318
101
159
 
102
160
  # Optional: Log level (debug|info|warn|error)
103
161
  # OTEL_LOG_LEVEL=info
162
+
163
+ # Optional: Enable request body capture (requires middleware.ts)
164
+ # SECURENOW_CAPTURE_BODY=1
165
+ # SECURENOW_MAX_BODY_SIZE=10240
166
+ # SECURENOW_SENSITIVE_FIELDS=email,phone
104
167
  `;
105
168
 
106
169
  fs.writeFileSync(targetPath, content, 'utf8');
@@ -171,33 +234,65 @@ async function setup() {
171
234
 
172
235
  console.log(`\n✅ Created ${srcExists ? 'src/' : ''}${fileName}`);
173
236
 
174
- // Create .env.local if it doesn't exist
175
- const envPath = path.join(process.cwd(), '.env.local');
176
- if (!fs.existsSync(envPath)) {
177
- createEnvTemplate(envPath);
178
- console.log('✅ Created .env.local template');
179
- }
180
-
181
- console.log('\n┌─────────────────────────────────────────────────┐');
182
- console.log('│ 🚀 Next Steps: │');
183
- console.log('│ │');
184
- console.log('│ 1. Edit .env.local and set: │');
185
- console.log('│ SECURENOW_APPID=your-app-name │');
186
- console.log('│ SECURENOW_INSTANCE=http://signoz:4318 │');
187
- console.log('│ │');
188
- console.log('│ 2. Run your app: npm run dev │');
189
- console.log('│ │');
190
- console.log('│ 3. Check SigNoz for traces! │');
191
- console.log('│ │');
192
- console.log('│ 📚 Full guide: npm docs securenow │');
193
- console.log('└─────────────────────────────────────────────────┘\n');
237
+ // Ask about middleware for body capture
238
+ rl.question('\nWould you like to enable request body capture? (y/N) ', (middlewareAnswer) => {
239
+ const shouldCreateMiddleware = middlewareAnswer && (middlewareAnswer.toLowerCase() === 'y' || middlewareAnswer.toLowerCase() === 'yes');
240
+
241
+ if (shouldCreateMiddleware) {
242
+ try {
243
+ const middlewareName = useTypeScript ? 'middleware.ts' : 'middleware.js';
244
+ const middlewarePath = srcExists
245
+ ? path.join(process.cwd(), 'src', middlewareName)
246
+ : path.join(process.cwd(), middlewareName);
247
+
248
+ if (useTypeScript) {
249
+ createTsMiddleware(middlewarePath);
250
+ } else {
251
+ createJsMiddleware(middlewarePath);
252
+ }
253
+
254
+ console.log(`✅ Created ${srcExists ? 'src/' : ''}${middlewareName}`);
255
+ console.log(' Captures JSON, GraphQL, Form bodies with auto-redaction');
256
+ } catch (error) {
257
+ console.warn(`⚠️ Could not create middleware: ${error.message}`);
258
+ }
259
+ }
260
+
261
+ // Create .env.local if it doesn't exist
262
+ const envPath = path.join(process.cwd(), '.env.local');
263
+ if (!fs.existsSync(envPath)) {
264
+ createEnvTemplate(envPath);
265
+ console.log('✅ Created .env.local template');
266
+ }
267
+
268
+ console.log('\n┌─────────────────────────────────────────────────┐');
269
+ console.log('│ 🚀 Next Steps: │');
270
+ console.log('│ │');
271
+ console.log('│ 1. Edit .env.local and set: │');
272
+ console.log('│ SECURENOW_APPID=your-app-name │');
273
+ console.log('│ SECURENOW_INSTANCE=http://signoz:4318 │');
274
+ if (shouldCreateMiddleware) {
275
+ console.log('│ SECURENOW_CAPTURE_BODY=1 │');
276
+ }
277
+ console.log('│ │');
278
+ console.log('│ 2. Run your app: npm run dev │');
279
+ console.log('│ │');
280
+ console.log('│ 3. Check SigNoz for traces! │');
281
+ console.log('│ │');
282
+ if (shouldCreateMiddleware) {
283
+ console.log('│ 📝 Body capture enabled with auto-redaction │');
284
+ }
285
+ console.log('│ 📚 Full guide: npm docs securenow │');
286
+ console.log('└─────────────────────────────────────────────────┘\n');
287
+
288
+ rl.close();
289
+ });
194
290
 
195
291
  } catch (error) {
196
292
  console.error('\n❌ Failed to create instrumentation file:', error.message);
197
293
  console.log('💡 You can create it manually or run: npx securenow init');
294
+ rl.close();
198
295
  }
199
-
200
- rl.close();
201
296
  });
202
297
  }
203
298