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.
- package/LICENSE +15 -0
- package/README.md +40 -239
- package/cli.js +455 -415
- package/console-instrumentation.js +136 -147
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-BODY-CAPTURE.md +1 -1
- package/docs/AUTO-SETUP.md +4 -4
- package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
- package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
- package/docs/CHANGELOG-NEXTJS.md +35 -1
- package/docs/CUSTOMER-GUIDE.md +16 -16
- package/docs/EASIEST-SETUP.md +5 -5
- package/docs/ENVIRONMENT-VARIABLES.md +652 -880
- package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
- package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
- package/docs/INDEX.md +4 -22
- package/docs/LOGGING-GUIDE.md +708 -701
- package/docs/LOGGING-QUICKSTART.md +255 -234
- 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-WRAPPER-APPROACH.md +1 -1
- package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
- package/docs/REDACTION-EXAMPLES.md +1 -1
- package/docs/REQUEST-BODY-CAPTURE.md +10 -19
- 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/nextjs-auto-capture.js +207 -199
- package/nextjs-middleware.js +181 -186
- package/nextjs-webpack-config.js +53 -88
- package/nextjs-wrapper.js +158 -158
- package/nextjs.d.ts +1 -1
- package/nextjs.js +198 -186
- package/package.json +45 -67
- package/postinstall.js +6 -6
- package/register.d.ts +1 -1
- package/register.js +4 -39
- package/tracing.d.ts +1 -2
- package/tracing.js +26 -286
- package/web-vite.mjs +156 -239
- package/CONSUMING-APPS-GUIDE.md +0 -455
- package/NPM_README.md +0 -1933
- package/SKILL-API.md +0 -600
- package/SKILL-CLI.md +0 -409
- package/cidr.js +0 -83
- package/cli/apps.js +0 -585
- package/cli/auth.js +0 -280
- package/cli/client.js +0 -115
- package/cli/config.js +0 -173
- package/cli/firewall.js +0 -100
- package/cli/fp.js +0 -638
- package/cli/init.js +0 -201
- package/cli/monitor.js +0 -440
- package/cli/run.js +0 -133
- package/cli/security.js +0 -1064
- package/cli/ui.js +0 -386
- package/docs/API-KEYS-GUIDE.md +0 -233
- package/docs/AUTO-SETUP-SUMMARY.md +0 -331
- package/docs/BODY-CAPTURE-FIX.md +0 -261
- package/docs/COMPLETION-REPORT.md +0 -408
- package/docs/FINAL-SOLUTION.md +0 -335
- package/docs/FIREWALL-GUIDE.md +0 -426
- package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
- package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
- package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
- package/docs/NUXT-GUIDE.md +0 -166
- package/docs/SOLUTION-SUMMARY.md +0 -312
- package/firewall-cloud.js +0 -212
- package/firewall-iptables.js +0 -139
- package/firewall-only.js +0 -38
- package/firewall-tcp.js +0 -74
- package/firewall.js +0 -720
- package/free-trial-banner.js +0 -174
- package/nuxt-server-plugin.mjs +0 -423
- package/nuxt.d.ts +0 -60
- package/nuxt.mjs +0 -75
- package/resolve-ip.js +0 -77
package/nextjs.js
CHANGED
|
@@ -5,39 +5,21 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage in Next.js app:
|
|
7
7
|
*
|
|
8
|
-
* 1.
|
|
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:
|
|
8
|
+
* 1. Create instrumentation.ts (or .js) in your project root:
|
|
29
9
|
*
|
|
30
10
|
* import { registerSecureNow } from 'securenow/nextjs';
|
|
31
11
|
* export function register() {
|
|
32
12
|
* registerSecureNow();
|
|
33
13
|
* }
|
|
34
14
|
*
|
|
35
|
-
*
|
|
15
|
+
* 2. Set environment variables:
|
|
36
16
|
* SECURENOW_APPID=my-nextjs-app
|
|
37
|
-
* SECURENOW_INSTANCE=http://your-
|
|
17
|
+
* SECURENOW_INSTANCE=http://your-signoz-host:4318
|
|
18
|
+
*
|
|
19
|
+
* That's it! 🎉 No webpack warnings!
|
|
38
20
|
*/
|
|
39
21
|
|
|
40
|
-
const {
|
|
22
|
+
const { v4: uuidv4 } = require('uuid');
|
|
41
23
|
|
|
42
24
|
const env = k => process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
|
|
43
25
|
|
|
@@ -68,9 +50,10 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
68
50
|
|
|
69
51
|
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
70
52
|
|
|
71
|
-
for (const key
|
|
53
|
+
for (const key in redacted) {
|
|
72
54
|
const lowerKey = key.toLowerCase();
|
|
73
55
|
|
|
56
|
+
// Check if field is sensitive
|
|
74
57
|
if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
|
|
75
58
|
redacted[key] = '[REDACTED]';
|
|
76
59
|
} else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
|
|
@@ -82,10 +65,6 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
82
65
|
return redacted;
|
|
83
66
|
}
|
|
84
67
|
|
|
85
|
-
function escapeRegex(str) {
|
|
86
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
68
|
/**
|
|
90
69
|
* Redact sensitive data from GraphQL query strings
|
|
91
70
|
*/
|
|
@@ -97,10 +76,10 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
97
76
|
// Redact sensitive fields in GraphQL arguments and variables
|
|
98
77
|
// Matches patterns like: password: "value" or password:"value" or password:'value'
|
|
99
78
|
sensitiveFields.forEach(field => {
|
|
100
|
-
|
|
79
|
+
// Match field: "value" or field: 'value' or field:"value" (with optional spaces)
|
|
101
80
|
const patterns = [
|
|
102
|
-
new RegExp(`(${
|
|
103
|
-
new RegExp(`(${
|
|
81
|
+
new RegExp(`(${field}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
|
|
82
|
+
new RegExp(`(${field}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
|
|
104
83
|
];
|
|
105
84
|
|
|
106
85
|
patterns.forEach(pattern => {
|
|
@@ -117,6 +96,115 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
117
96
|
return redacted;
|
|
118
97
|
}
|
|
119
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Parse and capture request body safely
|
|
101
|
+
*/
|
|
102
|
+
async function captureRequestBody(request, maxSize = 10240) {
|
|
103
|
+
try {
|
|
104
|
+
const contentType = request.headers['content-type'] || '';
|
|
105
|
+
let body = '';
|
|
106
|
+
|
|
107
|
+
// Collect body chunks
|
|
108
|
+
const chunks = [];
|
|
109
|
+
let size = 0;
|
|
110
|
+
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
request.on('data', (chunk) => {
|
|
113
|
+
size += chunk.length;
|
|
114
|
+
if (size <= maxSize) {
|
|
115
|
+
chunks.push(chunk);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
request.on('end', () => {
|
|
120
|
+
if (size > maxSize) {
|
|
121
|
+
resolve({
|
|
122
|
+
captured: false,
|
|
123
|
+
reason: `Body too large (${size} bytes > ${maxSize} bytes)`,
|
|
124
|
+
size
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
body = Buffer.concat(chunks).toString('utf8');
|
|
130
|
+
|
|
131
|
+
// Parse based on content type
|
|
132
|
+
if (contentType.includes('application/json')) {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(body);
|
|
135
|
+
resolve({
|
|
136
|
+
captured: true,
|
|
137
|
+
type: 'json',
|
|
138
|
+
body: parsed,
|
|
139
|
+
size
|
|
140
|
+
});
|
|
141
|
+
} catch (e) {
|
|
142
|
+
resolve({
|
|
143
|
+
captured: true,
|
|
144
|
+
type: 'json',
|
|
145
|
+
body: body.substring(0, 1000),
|
|
146
|
+
parseError: true,
|
|
147
|
+
size
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} else if (contentType.includes('application/graphql')) {
|
|
151
|
+
// GraphQL queries need redaction too!
|
|
152
|
+
resolve({
|
|
153
|
+
captured: true,
|
|
154
|
+
type: 'graphql',
|
|
155
|
+
body: body, // Will be redacted later
|
|
156
|
+
size
|
|
157
|
+
});
|
|
158
|
+
} else if (contentType.includes('multipart/form-data')) {
|
|
159
|
+
// Multipart is NOT captured (files can be huge)
|
|
160
|
+
resolve({
|
|
161
|
+
captured: false,
|
|
162
|
+
type: 'multipart',
|
|
163
|
+
reason: 'Multipart data not captured (file uploads)',
|
|
164
|
+
size
|
|
165
|
+
});
|
|
166
|
+
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
167
|
+
try {
|
|
168
|
+
const params = new URLSearchParams(body);
|
|
169
|
+
const parsed = Object.fromEntries(params);
|
|
170
|
+
resolve({
|
|
171
|
+
captured: true,
|
|
172
|
+
type: 'form',
|
|
173
|
+
body: parsed,
|
|
174
|
+
size
|
|
175
|
+
});
|
|
176
|
+
} catch (e) {
|
|
177
|
+
resolve({
|
|
178
|
+
captured: true,
|
|
179
|
+
type: 'form',
|
|
180
|
+
body: body.substring(0, 1000),
|
|
181
|
+
size
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
resolve({
|
|
186
|
+
captured: true,
|
|
187
|
+
type: 'text',
|
|
188
|
+
body: body.substring(0, 1000),
|
|
189
|
+
size
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
request.on('error', () => {
|
|
195
|
+
resolve({ captured: false, reason: 'Stream error' });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Timeout after 100ms
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
resolve({ captured: false, reason: 'Timeout' });
|
|
201
|
+
}, 100);
|
|
202
|
+
});
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return { captured: false, reason: error.message };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
120
208
|
/**
|
|
121
209
|
* Register SecureNow OpenTelemetry for Next.js using @vercel/otel
|
|
122
210
|
* @param {Object} options - Optional configuration
|
|
@@ -157,9 +245,9 @@ function registerSecureNow(options = {}) {
|
|
|
157
245
|
// service.name
|
|
158
246
|
let serviceName;
|
|
159
247
|
if (baseName) {
|
|
160
|
-
serviceName = noUuid ? baseName : `${baseName}-${
|
|
248
|
+
serviceName = noUuid ? baseName : `${baseName}-${uuidv4()}`;
|
|
161
249
|
} else {
|
|
162
|
-
serviceName = `nextjs-app-${
|
|
250
|
+
serviceName = `nextjs-app-${uuidv4()}`;
|
|
163
251
|
console.warn('[securenow] ⚠️ No SECURENOW_APPID or OTEL_SERVICE_NAME provided. Using fallback: %s', serviceName);
|
|
164
252
|
console.warn('[securenow] 💡 Set SECURENOW_APPID=your-app-name in .env.local for better tracking');
|
|
165
253
|
}
|
|
@@ -173,11 +261,17 @@ function registerSecureNow(options = {}) {
|
|
|
173
261
|
).replace(/\/$/, '');
|
|
174
262
|
|
|
175
263
|
const tracesUrl = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
|
|
176
|
-
const logsUrl
|
|
264
|
+
const logsUrl = env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
|
|
177
265
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
266
|
+
// Set environment variables for @vercel/otel to pick up
|
|
267
|
+
process.env.OTEL_SERVICE_NAME = serviceName;
|
|
268
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = endpointBase;
|
|
269
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = tracesUrl;
|
|
270
|
+
|
|
271
|
+
// -------- Logging Configuration --------
|
|
272
|
+
// Opt-in: SECURENOW_LOGGING_ENABLED=1 (or "true").
|
|
273
|
+
const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) === '1' ||
|
|
274
|
+
String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true';
|
|
181
275
|
|
|
182
276
|
console.log('[securenow] 🚀 Next.js App → service.name=%s', serviceName);
|
|
183
277
|
|
|
@@ -185,7 +279,7 @@ function registerSecureNow(options = {}) {
|
|
|
185
279
|
const captureBody = String(env('SECURENOW_CAPTURE_BODY')) === '1' ||
|
|
186
280
|
String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true' ||
|
|
187
281
|
options.captureBody === true;
|
|
188
|
-
const maxBodySize =
|
|
282
|
+
const maxBodySize = parseInt(env('SECURENOW_MAX_BODY_SIZE') || '10240'); // 10KB default
|
|
189
283
|
const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
190
284
|
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
191
285
|
|
|
@@ -219,19 +313,14 @@ function registerSecureNow(options = {}) {
|
|
|
219
313
|
const clientIp = headers['x-client-ip'];
|
|
220
314
|
const socketIp = request.socket?.remoteAddress;
|
|
221
315
|
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
primaryIp = realIp || cfConnectingIp || clientIp || primaryIp;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
316
|
+
// Primary IP (first in chain is the real client)
|
|
317
|
+
const primaryIp =
|
|
318
|
+
(forwardedFor ? forwardedFor.split(',')[0]?.trim() : null) ||
|
|
319
|
+
realIp ||
|
|
320
|
+
cfConnectingIp ||
|
|
321
|
+
clientIp ||
|
|
322
|
+
socketIp ||
|
|
323
|
+
'unknown';
|
|
235
324
|
|
|
236
325
|
// ======== PROTOCOL & CONNECTION ========
|
|
237
326
|
const scheme = headers['x-forwarded-proto'] ||
|
|
@@ -421,126 +510,70 @@ function registerSecureNow(options = {}) {
|
|
|
421
510
|
|
|
422
511
|
sdk.start();
|
|
423
512
|
console.log('[securenow] 🎯 Vanilla SDK initialized for self-hosted environment');
|
|
513
|
+
}
|
|
424
514
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
515
|
+
// -------- Logging pipeline (both Vercel and self-hosted) --------
|
|
516
|
+
// Neither @vercel/otel nor NodeSDK 0.47.x wires OTLP logs for us, so we
|
|
517
|
+
// create the LoggerProvider ourselves, register a BatchLogRecordProcessor
|
|
518
|
+
// (addLogRecordProcessor — the `processors` constructor option was only
|
|
519
|
+
// added in sdk-logs 0.52 and is silently ignored in 0.47), publish it as
|
|
520
|
+
// the global logger provider, and auto-patch console.* to emit records.
|
|
521
|
+
if (loggingEnabled) {
|
|
522
|
+
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
|
|
523
|
+
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
|
|
524
|
+
const { logs } = require('@opentelemetry/api-logs');
|
|
525
|
+
const { Resource } = require('@opentelemetry/resources');
|
|
526
|
+
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
|
|
431
527
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
528
|
+
const logResource = new Resource({
|
|
529
|
+
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
530
|
+
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env('NODE_ENV') || env('VERCEL_ENV') || 'production',
|
|
531
|
+
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version || process.env.VERCEL_GIT_COMMIT_SHA || undefined,
|
|
532
|
+
});
|
|
436
533
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
534
|
+
const logExporter = new OTLPLogExporter({
|
|
535
|
+
url: logsUrl,
|
|
536
|
+
headers: parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS')),
|
|
537
|
+
});
|
|
538
|
+
const loggerProvider = new LoggerProvider({ resource: logResource });
|
|
539
|
+
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
|
|
540
|
+
logs.setGlobalLoggerProvider(loggerProvider);
|
|
541
|
+
|
|
542
|
+
const _logger = loggerProvider.getLogger('console', '1.0.0');
|
|
543
|
+
const _orig = { log: console.log, info: console.info, warn: console.warn, error: console.error, debug: console.debug };
|
|
544
|
+
const SEV = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
|
|
545
|
+
const _emit = (sn, st, args) => {
|
|
546
|
+
try {
|
|
547
|
+
_logger.emit({
|
|
548
|
+
severityNumber: sn,
|
|
549
|
+
severityText: st,
|
|
550
|
+
body: args.map(a => (typeof a === 'object' && a !== null)
|
|
551
|
+
? (() => { try { return JSON.stringify(a); } catch { return String(a); } })()
|
|
552
|
+
: String(a)).join(' '),
|
|
553
|
+
attributes: { 'log.source': 'console', 'log.method': st.toLowerCase() },
|
|
442
554
|
});
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
});
|
|
463
|
-
} catch (_) {}
|
|
464
|
-
}
|
|
465
|
-
console.log = (...args) => {
|
|
466
|
-
origLog.apply(console, args);
|
|
467
|
-
_emitLog(SeverityNumber.INFO, 'INFO', args);
|
|
468
|
-
};
|
|
469
|
-
console.warn = (...args) => {
|
|
470
|
-
origWarn.apply(console, args);
|
|
471
|
-
_emitLog(SeverityNumber.WARN, 'WARN', args);
|
|
472
|
-
};
|
|
473
|
-
console.error = (...args) => {
|
|
474
|
-
origError.apply(console, args);
|
|
475
|
-
_emitLog(SeverityNumber.ERROR, 'ERROR', args);
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
|
|
479
|
-
|
|
480
|
-
// Auto-log every incoming HTTP request/response
|
|
481
|
-
try {
|
|
482
|
-
const http = require('http');
|
|
483
|
-
const originalEmit = http.Server.prototype.emit;
|
|
484
|
-
http.Server.prototype.emit = function (event, req, res) {
|
|
485
|
-
if (event === 'request' && req && res) {
|
|
486
|
-
const start = Date.now();
|
|
487
|
-
const method = req.method;
|
|
488
|
-
const url = req.url;
|
|
489
|
-
res.on('finish', () => {
|
|
490
|
-
const reqCtx = otelContext.active();
|
|
491
|
-
const reqSpanCtx = otelTrace.getSpanContext(reqCtx);
|
|
492
|
-
const duration = Date.now() - start;
|
|
493
|
-
const status = res.statusCode;
|
|
494
|
-
const ip = req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.socket?.remoteAddress || '-';
|
|
495
|
-
const ua = req.headers['user-agent'] || '-';
|
|
496
|
-
const body = `${method} ${url} ${status} ${duration}ms ip=${ip} ua=${ua}`;
|
|
497
|
-
const severity = status >= 500 ? SeverityNumber.ERROR : status >= 400 ? SeverityNumber.WARN : SeverityNumber.INFO;
|
|
498
|
-
const severityText = status >= 500 ? 'ERROR' : status >= 400 ? 'WARN' : 'INFO';
|
|
499
|
-
origLog.call(console, '[securenow] %s %s %d %dms', method, url, status, duration);
|
|
500
|
-
try {
|
|
501
|
-
logger.emit({
|
|
502
|
-
severityNumber: severity,
|
|
503
|
-
severityText,
|
|
504
|
-
body,
|
|
505
|
-
attributes: {
|
|
506
|
-
'http.method': method,
|
|
507
|
-
'http.url': url,
|
|
508
|
-
'http.status_code': status,
|
|
509
|
-
'http.duration_ms': duration,
|
|
510
|
-
'http.client_ip': String(ip).split(',')[0].trim(),
|
|
511
|
-
'http.user_agent': ua,
|
|
512
|
-
},
|
|
513
|
-
...(reqSpanCtx && { context: reqCtx }),
|
|
514
|
-
});
|
|
515
|
-
} catch (_) {}
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
return originalEmit.apply(this, arguments);
|
|
519
|
-
};
|
|
520
|
-
console.log('[securenow] 📋 HTTP request logging: ENABLED');
|
|
521
|
-
} catch (_) {}
|
|
522
|
-
|
|
523
|
-
// Graceful shutdown for logs
|
|
524
|
-
process.on('SIGTERM', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { require('./firewall').shutdown(); } catch (_) {} });
|
|
525
|
-
process.on('SIGINT', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { require('./firewall').shutdown(); } catch (_) {} });
|
|
526
|
-
} catch (e) {
|
|
527
|
-
console.warn('[securenow] ⚠️ Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
console.log('[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)');
|
|
531
|
-
}
|
|
555
|
+
} catch (_) {}
|
|
556
|
+
};
|
|
557
|
+
console.log = function (...a) { _emit(SEV.INFO, 'INFO', a); _orig.log.apply(console, a); };
|
|
558
|
+
console.info = function (...a) { _emit(SEV.INFO, 'INFO', a); _orig.info.apply(console, a); };
|
|
559
|
+
console.warn = function (...a) { _emit(SEV.WARN, 'WARN', a); _orig.warn.apply(console, a); };
|
|
560
|
+
console.error = function (...a) { _emit(SEV.ERROR, 'ERROR', a); _orig.error.apply(console, a); };
|
|
561
|
+
console.debug = function (...a) { _emit(SEV.DEBUG, 'DEBUG', a); _orig.debug.apply(console, a); };
|
|
562
|
+
|
|
563
|
+
const _shutdownLogs = async () => {
|
|
564
|
+
try { await Promise.resolve(loggerProvider.forceFlush?.()); } catch (_) {}
|
|
565
|
+
try { await Promise.resolve(loggerProvider.shutdown?.()); } catch (_) {}
|
|
566
|
+
};
|
|
567
|
+
process.on('SIGINT', _shutdownLogs);
|
|
568
|
+
process.on('SIGTERM', _shutdownLogs);
|
|
569
|
+
process.on('beforeExit', _shutdownLogs);
|
|
570
|
+
|
|
571
|
+
console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
|
|
572
|
+
} else {
|
|
573
|
+
console.log('[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)');
|
|
532
574
|
}
|
|
533
575
|
|
|
534
576
|
isRegistered = true;
|
|
535
|
-
|
|
536
|
-
// Free trial banner (optional — may not be bundled in standalone builds)
|
|
537
|
-
try {
|
|
538
|
-
const { isFreeTrial, patchHttpForBanner } = require('./free-trial-banner');
|
|
539
|
-
if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
|
|
540
|
-
patchHttpForBanner();
|
|
541
|
-
}
|
|
542
|
-
} catch (_) {}
|
|
543
|
-
|
|
544
577
|
console.log('[securenow] ✅ OpenTelemetry started for Next.js → %s', tracesUrl);
|
|
545
578
|
console.log('[securenow] 📊 Auto-capturing comprehensive request metadata:');
|
|
546
579
|
console.log('[securenow] • IP addresses (x-forwarded-for, x-real-ip, socket)');
|
|
@@ -572,27 +605,6 @@ function registerSecureNow(options = {}) {
|
|
|
572
605
|
console.error('[securenow] Make sure OpenTelemetry dependencies are installed');
|
|
573
606
|
}
|
|
574
607
|
}
|
|
575
|
-
|
|
576
|
-
// Firewall — runs independently from OTel so it works even if tracing fails
|
|
577
|
-
const firewallApiKey = env('SECURENOW_API_KEY');
|
|
578
|
-
if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
|
|
579
|
-
try {
|
|
580
|
-
require('./firewall').init({
|
|
581
|
-
apiKey: firewallApiKey,
|
|
582
|
-
apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
|
|
583
|
-
versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
|
|
584
|
-
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
|
|
585
|
-
failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
|
|
586
|
-
statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
|
|
587
|
-
log: env('SECURENOW_FIREWALL_LOG') !== '0',
|
|
588
|
-
tcp: env('SECURENOW_FIREWALL_TCP') === '1',
|
|
589
|
-
iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
|
|
590
|
-
cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
|
|
591
|
-
});
|
|
592
|
-
} catch (e) {
|
|
593
|
-
console.warn('[securenow] Firewall init failed:', e.message);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
608
|
}
|
|
597
609
|
|
|
598
610
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "OpenTelemetry instrumentation for Node.js
|
|
3
|
+
"version": "6.0.1",
|
|
4
|
+
"description": "OpenTelemetry instrumentation for Node.js and Next.js - Send traces and logs to SigNoz or any OTLP backend",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "register.js",
|
|
7
7
|
"types": "register.d.ts",
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"postinstall": "node postinstall.js || exit 0"
|
|
13
13
|
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/securenow-ai/securenow-npm.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://securenow.ai",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/securenow-ai/securenow-npm/issues",
|
|
21
|
+
"email": "support@securenow.ai"
|
|
22
|
+
},
|
|
23
|
+
"author": "SecureNow <support@securenow.ai> (https://securenow.ai)",
|
|
14
24
|
"keywords": [
|
|
15
25
|
"opentelemetry",
|
|
16
26
|
"otel",
|
|
@@ -20,24 +30,16 @@
|
|
|
20
30
|
"observability",
|
|
21
31
|
"apm",
|
|
22
32
|
"monitoring",
|
|
23
|
-
"cli",
|
|
24
33
|
"nextjs",
|
|
25
34
|
"next.js",
|
|
35
|
+
"signoz",
|
|
26
36
|
"instrumentation",
|
|
27
37
|
"telemetry",
|
|
28
38
|
"distributed-tracing",
|
|
29
39
|
"node",
|
|
30
40
|
"express",
|
|
31
41
|
"fastify",
|
|
32
|
-
"nestjs"
|
|
33
|
-
"nuxt",
|
|
34
|
-
"nuxt3",
|
|
35
|
-
"nitro",
|
|
36
|
-
"vue",
|
|
37
|
-
"firewall",
|
|
38
|
-
"ip-blocking",
|
|
39
|
-
"waf",
|
|
40
|
-
"security"
|
|
42
|
+
"nestjs"
|
|
41
43
|
],
|
|
42
44
|
"exports": {
|
|
43
45
|
".": {
|
|
@@ -72,24 +74,6 @@
|
|
|
72
74
|
"default": "./nextjs-wrapper.js"
|
|
73
75
|
},
|
|
74
76
|
"./nextjs-webpack-config": "./nextjs-webpack-config.js",
|
|
75
|
-
"./package.json": "./package.json",
|
|
76
|
-
"./nuxt": {
|
|
77
|
-
"types": "./nuxt.d.ts",
|
|
78
|
-
"import": "./nuxt.mjs",
|
|
79
|
-
"default": "./nuxt.mjs"
|
|
80
|
-
},
|
|
81
|
-
"./firewall": {
|
|
82
|
-
"default": "./firewall.js"
|
|
83
|
-
},
|
|
84
|
-
"./firewall-only": {
|
|
85
|
-
"default": "./firewall-only.js"
|
|
86
|
-
},
|
|
87
|
-
"./cidr": {
|
|
88
|
-
"default": "./cidr.js"
|
|
89
|
-
},
|
|
90
|
-
"./resolve-ip": {
|
|
91
|
-
"default": "./resolve-ip.js"
|
|
92
|
-
},
|
|
93
77
|
"./register-vite": "./register-vite.js",
|
|
94
78
|
"./web-vite": {
|
|
95
79
|
"import": "./web-vite.mjs",
|
|
@@ -111,45 +95,52 @@
|
|
|
111
95
|
"nextjs-wrapper.js",
|
|
112
96
|
"nextjs-wrapper.d.ts",
|
|
113
97
|
"nextjs-webpack-config.js",
|
|
114
|
-
"nuxt.mjs",
|
|
115
|
-
"nuxt.d.ts",
|
|
116
|
-
"nuxt-server-plugin.mjs",
|
|
117
98
|
"cli.js",
|
|
118
|
-
"cli/",
|
|
119
|
-
"free-trial-banner.js",
|
|
120
|
-
"resolve-ip.js",
|
|
121
|
-
"cidr.js",
|
|
122
|
-
"firewall.js",
|
|
123
|
-
"firewall-only.js",
|
|
124
|
-
"firewall-tcp.js",
|
|
125
|
-
"firewall-iptables.js",
|
|
126
|
-
"firewall-cloud.js",
|
|
127
99
|
"postinstall.js",
|
|
128
100
|
"register-vite.js",
|
|
129
101
|
"web-vite.mjs",
|
|
130
102
|
"examples/",
|
|
131
|
-
"docs/",
|
|
103
|
+
"docs/ALL-FRAMEWORKS-QUICKSTART.md",
|
|
104
|
+
"docs/ARCHITECTURE.md",
|
|
105
|
+
"docs/AUTO-BODY-CAPTURE.md",
|
|
106
|
+
"docs/CHANGELOG-NEXTJS.md",
|
|
107
|
+
"docs/NEXTJS-WEBPACK-WARNINGS.md",
|
|
108
|
+
"docs/AUTO-SETUP.md",
|
|
109
|
+
"docs/AUTOMATIC-IP-CAPTURE.md",
|
|
110
|
+
"docs/BODY-CAPTURE-QUICKSTART.md",
|
|
111
|
+
"docs/CUSTOMER-GUIDE.md",
|
|
112
|
+
"docs/EASIEST-SETUP.md",
|
|
113
|
+
"docs/ENVIRONMENT-VARIABLES.md",
|
|
114
|
+
"docs/EXPRESS-BODY-CAPTURE.md",
|
|
115
|
+
"docs/EXPRESS-SETUP-GUIDE.md",
|
|
116
|
+
"docs/INDEX.md",
|
|
117
|
+
"docs/LOGGING-GUIDE.md",
|
|
118
|
+
"docs/LOGGING-QUICKSTART.md",
|
|
119
|
+
"docs/NEXTJS-BODY-CAPTURE.md",
|
|
120
|
+
"docs/NEXTJS-GUIDE.md",
|
|
121
|
+
"docs/NEXTJS-QUICKSTART.md",
|
|
122
|
+
"docs/NEXTJS-WRAPPER-APPROACH.md",
|
|
123
|
+
"docs/QUICKSTART-BODY-CAPTURE.md",
|
|
124
|
+
"docs/REDACTION-EXAMPLES.md",
|
|
125
|
+
"docs/REQUEST-BODY-CAPTURE.md",
|
|
126
|
+
"docs/VERCEL-OTEL-MIGRATION.md",
|
|
132
127
|
"README.md",
|
|
133
|
-
"
|
|
134
|
-
"CONSUMING-APPS-GUIDE.md",
|
|
135
|
-
"SKILL-CLI.md",
|
|
136
|
-
"SKILL-API.md"
|
|
128
|
+
"LICENSE"
|
|
137
129
|
],
|
|
138
130
|
"dependencies": {
|
|
139
131
|
"@opentelemetry/api": "1.7.0",
|
|
140
|
-
"@opentelemetry/api-logs": "0.47.0",
|
|
132
|
+
"@opentelemetry/api-logs": "^0.47.0",
|
|
141
133
|
"@opentelemetry/auto-instrumentations-node": "0.47.0",
|
|
142
|
-
"@opentelemetry/exporter-logs-otlp-http": "0.47.0",
|
|
134
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.47.0",
|
|
143
135
|
"@opentelemetry/exporter-trace-otlp-http": "0.47.0",
|
|
144
136
|
"@opentelemetry/instrumentation": "0.47.0",
|
|
145
137
|
"@opentelemetry/instrumentation-document-load": "0.47.0",
|
|
146
138
|
"@opentelemetry/instrumentation-fetch": "0.47.0",
|
|
147
|
-
"@opentelemetry/instrumentation-http": "0.
|
|
148
|
-
"@opentelemetry/instrumentation-mongodb": "0.46.0",
|
|
139
|
+
"@opentelemetry/instrumentation-http": "^0.208.0",
|
|
149
140
|
"@opentelemetry/instrumentation-user-interaction": "0.47.0",
|
|
150
141
|
"@opentelemetry/instrumentation-xml-http-request": "0.47.0",
|
|
151
142
|
"@opentelemetry/resources": "1.20.0",
|
|
152
|
-
"@opentelemetry/sdk-logs": "0.47.0",
|
|
143
|
+
"@opentelemetry/sdk-logs": "^0.47.0",
|
|
153
144
|
"@opentelemetry/sdk-node": "0.47.0",
|
|
154
145
|
"@opentelemetry/sdk-trace-web": "1.20.0",
|
|
155
146
|
"@opentelemetry/semantic-conventions": "1.20.0",
|
|
@@ -158,28 +149,15 @@
|
|
|
158
149
|
"uuid": "^9.0.0"
|
|
159
150
|
},
|
|
160
151
|
"peerDependencies": {
|
|
161
|
-
"next": ">=13.0.0"
|
|
162
|
-
"nuxt": ">=3.0.0",
|
|
163
|
-
"@aws-sdk/client-wafv2": ">=3.0.0",
|
|
164
|
-
"@google-cloud/compute": ">=4.0.0"
|
|
152
|
+
"next": ">=13.0.0"
|
|
165
153
|
},
|
|
166
154
|
"peerDependenciesMeta": {
|
|
167
155
|
"next": {
|
|
168
156
|
"optional": true
|
|
169
|
-
},
|
|
170
|
-
"nuxt": {
|
|
171
|
-
"optional": true
|
|
172
|
-
},
|
|
173
|
-
"@aws-sdk/client-wafv2": {
|
|
174
|
-
"optional": true
|
|
175
|
-
},
|
|
176
|
-
"@google-cloud/compute": {
|
|
177
|
-
"optional": true
|
|
178
157
|
}
|
|
179
158
|
},
|
|
180
159
|
"overrides": {
|
|
181
|
-
"@opentelemetry/api": "1.7.0"
|
|
182
|
-
"@opentelemetry/api-logs": "0.47.0"
|
|
160
|
+
"@opentelemetry/api": "1.7.0"
|
|
183
161
|
},
|
|
184
162
|
"sideEffects": true,
|
|
185
163
|
"license": "ISC"
|