securenow 4.0.2 → 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
@@ -25,6 +25,176 @@ const env = k => process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k
25
25
 
26
26
  let isRegistered = false;
27
27
 
28
+ // Default sensitive fields to redact from request bodies
29
+ const DEFAULT_SENSITIVE_FIELDS = [
30
+ 'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
31
+ 'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
32
+ 'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
33
+ ];
34
+
35
+ /**
36
+ * Redact sensitive fields from an object
37
+ */
38
+ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
39
+ if (!obj || typeof obj !== 'object') return obj;
40
+
41
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
42
+
43
+ for (const key in redacted) {
44
+ const lowerKey = key.toLowerCase();
45
+
46
+ // Check if field is sensitive
47
+ if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
48
+ redacted[key] = '[REDACTED]';
49
+ } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
50
+ // Recursively redact nested objects
51
+ redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
52
+ }
53
+ }
54
+
55
+ return redacted;
56
+ }
57
+
58
+ /**
59
+ * Redact sensitive data from GraphQL query strings
60
+ */
61
+ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
62
+ if (!query || typeof query !== 'string') return query;
63
+
64
+ let redacted = query;
65
+
66
+ // Redact sensitive fields in GraphQL arguments and variables
67
+ // Matches patterns like: password: "value" or password:"value" or password:'value'
68
+ sensitiveFields.forEach(field => {
69
+ // Match field: "value" or field: 'value' or field:"value" (with optional spaces)
70
+ const patterns = [
71
+ new RegExp(`(${field}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
72
+ new RegExp(`(${field}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
73
+ ];
74
+
75
+ patterns.forEach(pattern => {
76
+ redacted = redacted.replace(pattern, (match, prefix, value, suffix) => {
77
+ if (suffix) {
78
+ return `${prefix}[REDACTED]${suffix}`;
79
+ } else {
80
+ return `${prefix}[REDACTED]`;
81
+ }
82
+ });
83
+ });
84
+ });
85
+
86
+ return redacted;
87
+ }
88
+
89
+ /**
90
+ * Parse and capture request body safely
91
+ */
92
+ async function captureRequestBody(request, maxSize = 10240) {
93
+ try {
94
+ const contentType = request.headers['content-type'] || '';
95
+ let body = '';
96
+
97
+ // Collect body chunks
98
+ const chunks = [];
99
+ let size = 0;
100
+
101
+ return new Promise((resolve) => {
102
+ request.on('data', (chunk) => {
103
+ size += chunk.length;
104
+ if (size <= maxSize) {
105
+ chunks.push(chunk);
106
+ }
107
+ });
108
+
109
+ request.on('end', () => {
110
+ if (size > maxSize) {
111
+ resolve({
112
+ captured: false,
113
+ reason: `Body too large (${size} bytes > ${maxSize} bytes)`,
114
+ size
115
+ });
116
+ return;
117
+ }
118
+
119
+ body = Buffer.concat(chunks).toString('utf8');
120
+
121
+ // Parse based on content type
122
+ if (contentType.includes('application/json')) {
123
+ try {
124
+ const parsed = JSON.parse(body);
125
+ resolve({
126
+ captured: true,
127
+ type: 'json',
128
+ body: parsed,
129
+ size
130
+ });
131
+ } catch (e) {
132
+ resolve({
133
+ captured: true,
134
+ type: 'json',
135
+ body: body.substring(0, 1000),
136
+ parseError: true,
137
+ size
138
+ });
139
+ }
140
+ } else if (contentType.includes('application/graphql')) {
141
+ // GraphQL queries need redaction too!
142
+ resolve({
143
+ captured: true,
144
+ type: 'graphql',
145
+ body: body, // Will be redacted later
146
+ size
147
+ });
148
+ } else if (contentType.includes('multipart/form-data')) {
149
+ // Multipart is NOT captured (files can be huge)
150
+ resolve({
151
+ captured: false,
152
+ type: 'multipart',
153
+ reason: 'Multipart data not captured (file uploads)',
154
+ size
155
+ });
156
+ } else if (contentType.includes('application/x-www-form-urlencoded')) {
157
+ try {
158
+ const params = new URLSearchParams(body);
159
+ const parsed = Object.fromEntries(params);
160
+ resolve({
161
+ captured: true,
162
+ type: 'form',
163
+ body: parsed,
164
+ size
165
+ });
166
+ } catch (e) {
167
+ resolve({
168
+ captured: true,
169
+ type: 'form',
170
+ body: body.substring(0, 1000),
171
+ size
172
+ });
173
+ }
174
+ } else {
175
+ resolve({
176
+ captured: true,
177
+ type: 'text',
178
+ body: body.substring(0, 1000),
179
+ size
180
+ });
181
+ }
182
+ });
183
+
184
+ request.on('error', () => {
185
+ resolve({ captured: false, reason: 'Stream error' });
186
+ });
187
+
188
+ // Timeout after 100ms
189
+ setTimeout(() => {
190
+ resolve({ captured: false, reason: 'Timeout' });
191
+ }, 100);
192
+ });
193
+ } catch (error) {
194
+ return { captured: false, reason: error.message };
195
+ }
196
+ }
197
+
28
198
  /**
29
199
  * Register SecureNow OpenTelemetry for Next.js using @vercel/otel
30
200
  * @param {Object} options - Optional configuration
@@ -86,6 +256,14 @@ function registerSecureNow(options = {}) {
86
256
 
87
257
  console.log('[securenow] 🚀 Next.js App → service.name=%s', serviceName);
88
258
 
259
+ // -------- Body Capture Configuration --------
260
+ const captureBody = String(env('SECURENOW_CAPTURE_BODY')) === '1' ||
261
+ String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true' ||
262
+ options.captureBody === true;
263
+ const maxBodySize = parseInt(env('SECURENOW_MAX_BODY_SIZE') || '10240'); // 10KB default
264
+ const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
265
+ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
266
+
89
267
  // -------- Use @vercel/otel with enhanced configuration --------
90
268
  const { registerOTel } = require('@vercel/otel');
91
269
  const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
@@ -98,11 +276,18 @@ function registerSecureNow(options = {}) {
98
276
  'vercel.region': process.env.VERCEL_REGION || undefined,
99
277
  },
100
278
  instrumentations: [
101
- // Add HTTP instrumentation with request hooks to capture IP and headers
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
102
281
  new HttpInstrumentation({
103
282
  requireParentforOutgoingSpans: false,
104
283
  requireParentforIncomingSpans: false,
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
+ },
105
289
  requestHook: (span, request) => {
290
+ // SYNCHRONOUS ONLY - no async operations to avoid timing issues
106
291
  try {
107
292
  // Capture client IP from various headers
108
293
  const headers = request.headers || {};
@@ -116,7 +301,7 @@ function registerSecureNow(options = {}) {
116
301
  request.socket?.remoteAddress ||
117
302
  'unknown';
118
303
 
119
- // Add IP and request metadata to span
304
+ // Add IP and request metadata to span (synchronously)
120
305
  span.setAttributes({
121
306
  'http.client_ip': clientIp,
122
307
  'http.user_agent': headers['user-agent'] || 'unknown',
@@ -140,21 +325,21 @@ function registerSecureNow(options = {}) {
140
325
  if (headers['cf-ipcountry']) {
141
326
  span.setAttribute('http.geo.country', headers['cf-ipcountry']);
142
327
  }
328
+
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
+
143
338
  } catch (error) {
144
339
  // Silently fail to not break the request
145
- console.debug('[securenow] Failed to capture request metadata:', error.message);
146
- }
147
- },
148
- responseHook: (span, response) => {
149
- try {
150
- // Add response metadata
151
- if (response.statusCode) {
152
- span.setAttribute('http.status_code', response.statusCode);
153
- }
154
- } catch (error) {
155
- console.debug('[securenow] Failed to capture response metadata:', error.message);
340
+ // Do not log in production to avoid noise
156
341
  }
157
- },
342
+ }
158
343
  }),
159
344
  ],
160
345
  instrumentationConfig: {
@@ -180,6 +365,11 @@ function registerSecureNow(options = {}) {
180
365
  isRegistered = true;
181
366
  console.log('[securenow] ✅ OpenTelemetry started for Next.js → %s', tracesUrl);
182
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)');
369
+ if (captureBody) {
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)');
372
+ }
183
373
 
184
374
  // Optional test span
185
375
  if (String(env('SECURENOW_TEST_SPAN')) === '1') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "4.0.2",
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",
@@ -54,7 +60,19 @@
54
60
  "NEXTJS-QUICKSTART.md",
55
61
  "CUSTOMER-GUIDE.md",
56
62
  "AUTO-SETUP.md",
57
- "AUTOMATIC-IP-CAPTURE.md"
63
+ "AUTOMATIC-IP-CAPTURE.md",
64
+ "REQUEST-BODY-CAPTURE.md",
65
+ "BODY-CAPTURE-QUICKSTART.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"
58
76
  ],
59
77
  "dependencies": {
60
78
  "@opentelemetry/api": "1.7.0",