securenow 5.18.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +1 -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 +239 -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 +135 -190
  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 +22 -287
  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
package/register.js CHANGED
@@ -1,49 +1,14 @@
1
- // securenow/register.js — the only preload customers need:
2
- // node --require securenow/register app.js
3
- //
4
- // For ESM apps ("type": "module"), this file auto-registers the
5
- // OpenTelemetry ESM loader hook via module.register() (Node >=20.6).
6
- // On older Node versions it falls back to a warning.
1
+ // securenow/preload.js
7
2
  'use strict';
8
3
 
9
- // 1. load .env before anything else
4
+ // load .env into process.env before anything else
10
5
  try {
11
6
  require('dotenv').config();
12
7
  console.log('[securenow] dotenv loaded from', process.env.DOTENV_CONFIG_PATH || '.env');
13
8
  } catch (e) {
9
+ // dotenv is optional — only warn if it’s missing
14
10
  console.warn('[securenow] dotenv not found or failed to load');
15
11
  }
16
12
 
17
- // 2. Auto-register the ESM loader hook so customers never need --import
18
- (() => {
19
- try {
20
- const fs = require('fs');
21
- const path = require('path');
22
- const pkgPath = path.resolve(process.cwd(), 'package.json');
23
- if (!fs.existsSync(pkgPath)) return;
24
-
25
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
26
- if (pkg.type !== 'module') return;
27
-
28
- // Already registered via --import?
29
- const execArgv = process.execArgv.join(' ');
30
- if (execArgv.includes('hook.mjs') || execArgv.includes('import-in-the-middle')) return;
31
-
32
- // Node >=20.6 exposes module.register() for programmatic ESM hooks
33
- const mod = require('node:module');
34
- if (typeof mod.register !== 'function') {
35
- console.warn('[securenow] ESM app detected but Node %s lacks module.register().', process.version);
36
- console.warn('[securenow] Upgrade to Node >=20.6 or add: --import @opentelemetry/instrumentation/hook.mjs');
37
- return;
38
- }
39
-
40
- const { pathToFileURL } = require('node:url');
41
- mod.register('@opentelemetry/instrumentation/hook.mjs', pathToFileURL(__filename));
42
- console.log('[securenow] ESM loader hook auto-registered (module.register)');
43
- } catch (_) {
44
- // Non-fatal — tracing.js will show its own ESM warning if the hook is missing
45
- }
46
- })();
47
-
48
- // 3. Run the OTel SDK setup
13
+ // then run the real tracer preload
49
14
  require('./tracing');
package/tracing.d.ts CHANGED
@@ -112,7 +112,7 @@ export interface LoggerProvider {
112
112
  }
113
113
 
114
114
  /**
115
- * Get a logger instance for sending structured logs to any OTLP-compatible backend
115
+ * Get a logger instance for sending structured logs to SigNoz
116
116
  *
117
117
  * @param name - Logger name (e.g., 'my-service', 'auth-module')
118
118
  * @param version - Logger version (optional, defaults to '1.0.0')
@@ -178,6 +178,5 @@ export const loggerProvider: LoggerProvider | null;
178
178
  * - SECURENOW_DISABLE_INSTRUMENTATIONS=pkg1,pkg2
179
179
  * - OTEL_LOG_LEVEL=info|debug
180
180
  * - SECURENOW_TEST_SPAN=1
181
- * - SECURENOW_TRUSTED_PROXIES=ip1,ip2 # Additional trusted proxy IPs for X-Forwarded-For
182
181
  * - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=... # Override logs endpoint
183
182
  */
package/tracing.js CHANGED
@@ -1,12 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Preload with: node --require securenow/register app.js
5
- *
6
- * Works for both CJS and ESM apps. On Node >=20.6 the ESM loader hook is
7
- * auto-registered via module.register() — no --import flag needed.
8
- * On Node 18 with "type": "module", add the hook manually:
9
- * node --import @opentelemetry/instrumentation/hook.mjs --require securenow/register app.js
4
+ * Preload with: NODE_OPTIONS="-r securenow/register"
10
5
  *
11
6
  * Env:
12
7
  * SECURENOW_APPID=logical-name # or OTEL_SERVICE_NAME=logical-name
@@ -16,7 +11,6 @@
16
11
  * OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=... # full traces URL
17
12
  * OTEL_EXPORTER_OTLP_HEADERS="k=v,k2=v2"
18
13
  * SECURENOW_DISABLE_INSTRUMENTATIONS="pkg1,pkg2"
19
- * SECURENOW_CAPTURE_MULTIPART=1 # capture multipart/form-data fields & file metadata (streaming, no file content buffered)
20
14
  * OTEL_LOG_LEVEL=info|debug
21
15
  * SECURENOW_TEST_SPAN=1
22
16
  *
@@ -24,7 +18,7 @@
24
18
  * SECURENOW_STRICT=1 -> if no appid/name is provided in cluster, exit(1) so PM2 restarts the worker
25
19
  */
26
20
 
27
- const { diag, DiagConsoleLogger, DiagLogLevel, context, trace } = require('@opentelemetry/api');
21
+ const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');
28
22
  const { NodeSDK } = require('@opentelemetry/sdk-node');
29
23
  const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
30
24
  const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
@@ -32,7 +26,6 @@ const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-
32
26
  const { Resource } = require('@opentelemetry/resources');
33
27
  const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
34
28
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
35
- const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb');
36
29
  const { v4: uuidv4 } = require('uuid');
37
30
 
38
31
  const env = k => process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
@@ -53,10 +46,6 @@ const DEFAULT_SENSITIVE_FIELDS = [
53
46
  'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
54
47
  ];
55
48
 
56
- function escapeRegex(str) {
57
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
58
- }
59
-
60
49
  /**
61
50
  * Redact sensitive fields from an object
62
51
  */
@@ -65,7 +54,7 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
65
54
 
66
55
  const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
67
56
 
68
- for (const key of Object.keys(redacted)) {
57
+ for (const key in redacted) {
69
58
  const lowerKey = key.toLowerCase();
70
59
 
71
60
  if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
@@ -88,10 +77,10 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
88
77
 
89
78
  // Redact sensitive fields in GraphQL arguments and variables
90
79
  sensitiveFields.forEach(field => {
91
- const escaped = escapeRegex(field);
80
+ // Match patterns: field: "value" or field: 'value' or field:"value"
92
81
  const patterns = [
93
- new RegExp(`(${escaped}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
94
- new RegExp(`(${escaped}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
82
+ new RegExp(`(${field}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
83
+ new RegExp(`(${field}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
95
84
  ];
96
85
 
97
86
  patterns.forEach(pattern => {
@@ -108,155 +97,6 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
108
97
  return redacted;
109
98
  }
110
99
 
111
- // -------- Multipart streaming parser --------
112
- // Streams through the request without buffering file content.
113
- // Only part headers and text-field values are kept in memory,
114
- // so memory stays bounded (~few KB) regardless of upload size.
115
-
116
- function extractBoundary(contentType) {
117
- const match = contentType.match(/boundary=(?:"([^"]+)"|([^\s;]+))/i);
118
- return match ? (match[1] || match[2]) : null;
119
- }
120
-
121
- function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFieldSize, onComplete) {
122
- const boundary = extractBoundary(contentType);
123
- if (!boundary) { onComplete({ error: 'BOUNDARY_NOT_FOUND' }); return; }
124
-
125
- const result = { fields: Object.create(null), files: [] };
126
- let totalSize = 0;
127
- let buf = Buffer.alloc(0);
128
-
129
- const MAX_PARTS = 100;
130
- let partCount = 0;
131
-
132
- const FIRST_DELIM = Buffer.from('--' + boundary);
133
- const DELIM = Buffer.from('\r\n--' + boundary);
134
- const HDR_END = Buffer.from('\r\n\r\n');
135
-
136
- let initialized = false;
137
- let inHeaders = true;
138
- let isFile = false;
139
- let fldName = '';
140
- let fName = '';
141
- let pCT = '';
142
- let bodyBytes = 0;
143
- let textVal = '';
144
-
145
- function flushPart() {
146
- if (!fldName || fldName === '__proto__' || fldName === 'constructor' || fldName === 'prototype') return;
147
- if (isFile) {
148
- result.files.push({ field: fldName, filename: fName, contentType: pCT || 'unknown', size: bodyBytes });
149
- } else {
150
- const lower = fldName.toLowerCase();
151
- const redact = sensitiveFields.some(f => lower.includes(f.toLowerCase()));
152
- result.fields[fldName] = redact ? '[REDACTED]' : textVal.substring(0, maxTextFieldSize);
153
- }
154
- fldName = ''; bodyBytes = 0; textVal = ''; partCount++;
155
- }
156
-
157
- function drain() {
158
- if (!initialized) {
159
- const i = buf.indexOf(FIRST_DELIM);
160
- if (i === -1) {
161
- if (buf.length > FIRST_DELIM.length + 4) buf = buf.slice(buf.length - FIRST_DELIM.length - 4);
162
- return;
163
- }
164
- buf = buf.slice(i + FIRST_DELIM.length);
165
- initialized = true;
166
- inHeaders = true;
167
- }
168
-
169
- let guard = 200;
170
- while (buf.length > 0 && guard-- > 0 && partCount < MAX_PARTS) {
171
- if (inHeaders) {
172
- if (buf.length >= 2 && buf[0] === 0x2D && buf[1] === 0x2D) { buf = Buffer.alloc(0); return; }
173
- if (buf.length >= 2 && buf[0] === 0x0D && buf[1] === 0x0A) { buf = buf.slice(2); continue; }
174
-
175
- const hi = buf.indexOf(HDR_END);
176
- if (hi === -1) return;
177
-
178
- const hdr = buf.slice(0, hi).toString('latin1');
179
- buf = buf.slice(hi + 4);
180
-
181
- const nm = hdr.match(/name="([^"]+)"/);
182
- const fn = hdr.match(/filename="([^"]*)"/);
183
- const ct = hdr.match(/Content-Type:\s*(.+)/i);
184
- fldName = nm ? nm[1] : '';
185
- fName = fn ? fn[1] : '';
186
- pCT = ct ? ct[1].trim() : '';
187
- isFile = !!fn;
188
- bodyBytes = 0;
189
- textVal = '';
190
- inHeaders = false;
191
- }
192
-
193
- const di = buf.indexOf(DELIM);
194
- if (di === -1) {
195
- const safe = Math.max(0, buf.length - DELIM.length - 2);
196
- if (safe > 0) {
197
- bodyBytes += safe;
198
- if (!isFile && textVal.length < maxTextFieldSize) {
199
- textVal += buf.slice(0, safe).toString('utf8').substring(0, maxTextFieldSize - textVal.length);
200
- }
201
- buf = buf.slice(safe);
202
- }
203
- return;
204
- }
205
-
206
- bodyBytes += di;
207
- if (!isFile && textVal.length < maxTextFieldSize) {
208
- textVal += buf.slice(0, di).toString('utf8').substring(0, maxTextFieldSize - textVal.length);
209
- }
210
- flushPart();
211
- buf = buf.slice(di + DELIM.length);
212
- inHeaders = true;
213
- }
214
- }
215
-
216
- request.on('data', (chunk) => {
217
- totalSize += chunk.length;
218
- buf = Buffer.concat([buf, chunk]);
219
- drain();
220
- });
221
-
222
- request.on('end', () => {
223
- try {
224
- if (!inHeaders && fldName) {
225
- bodyBytes += buf.length;
226
- if (!isFile) textVal += buf.toString('utf8').substring(0, maxTextFieldSize - textVal.length);
227
- flushPart();
228
- }
229
- onComplete({ parsed: result, totalSize });
230
- } catch (e) {
231
- onComplete({ error: 'PARSE_ERROR' });
232
- }
233
- });
234
- }
235
-
236
- // -------- ESM detection --------
237
- // register.js auto-registers the hook via module.register() on Node >=20.6.
238
- // This warning only fires if BOTH --import AND module.register() were skipped
239
- // (e.g. Node 18, or require('securenow/tracing') called directly without register.js).
240
- (() => {
241
- try {
242
- const fs = require('fs');
243
- const path = require('path');
244
- const pkgPath = path.resolve(process.cwd(), 'package.json');
245
- if (fs.existsSync(pkgPath)) {
246
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
247
- if (pkg.type === 'module') {
248
- const execArgv = process.execArgv.join(' ');
249
- const hasCliHook = execArgv.includes('hook.mjs') || execArgv.includes('import-in-the-middle');
250
- const hasModuleRegister = typeof require('node:module').register === 'function';
251
- if (!hasCliHook && !hasModuleRegister) {
252
- console.warn('[securenow] ⚠️ ESM app detected ("type": "module") but no ESM loader hook available.');
253
- console.warn('[securenow] Upgrade to Node >=20.6 (recommended) or add: --import @opentelemetry/instrumentation/hook.mjs');
254
- }
255
- }
256
- }
257
- } catch (_) {}
258
- })();
259
-
260
100
  // -------- diagnostics --------
261
101
  (() => {
262
102
  const L = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
@@ -320,44 +160,21 @@ for (const n of (env('SECURENOW_DISABLE_INSTRUMENTATIONS') || '').split(',').map
320
160
 
321
161
  // -------- Body Capture Configuration --------
322
162
  const captureBody = String(env('SECURENOW_CAPTURE_BODY')) === '1' || String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true';
323
- const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
163
+ const maxBodySize = parseInt(env('SECURENOW_MAX_BODY_SIZE') || '10240'); // 10KB default
324
164
  const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
325
165
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
326
166
 
327
- const captureMultipart = String(env('SECURENOW_CAPTURE_MULTIPART')) === '1' || String(env('SECURENOW_CAPTURE_MULTIPART')).toLowerCase() === 'true';
328
-
329
- // -------- Trusted proxy IP resolution --------
330
- const { resolveClientIp, isFromTrustedProxy, LOOPBACK_RE } = require('./resolve-ip');
331
-
332
167
  // Configure HTTP instrumentation with body capture
333
168
  const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
334
169
  const httpInstrumentation = new HttpInstrumentation({
335
170
  requestHook: (span, request) => {
336
171
  try {
337
- const clientIp = resolveClientIp(request);
338
- if (clientIp) {
339
- span.setAttribute('http.client_ip', clientIp);
340
- }
341
-
342
- if (request.headers) {
343
- const SKIP_HEADERS = new Set(['cookie', 'authorization', 'proxy-authorization', 'set-cookie', 'x-api-key', 'x-auth-token']);
344
- const safe = {};
345
- for (const [k, v] of Object.entries(request.headers)) {
346
- if (SKIP_HEADERS.has(k.toLowerCase())) { safe[k] = '[REDACTED]'; continue; }
347
- safe[k] = typeof v === 'string' ? v.substring(0, 500) : String(v);
348
- }
349
- const serialized = JSON.stringify(safe);
350
- if (serialized.length <= 8192) {
351
- span.setAttribute('http.request.headers', serialized);
352
- }
353
- }
354
-
355
- if ((captureBody || captureMultipart) && request.method && ['POST', 'PUT', 'PATCH'].includes(request.method)) {
172
+ if (captureBody && request.method && ['POST', 'PUT', 'PATCH'].includes(request.method)) {
356
173
  const contentType = request.headers['content-type'] || '';
357
174
 
358
- if (captureBody && (contentType.includes('application/json') ||
175
+ if (contentType.includes('application/json') ||
359
176
  contentType.includes('application/graphql') ||
360
- contentType.includes('application/x-www-form-urlencoded'))) {
177
+ contentType.includes('application/x-www-form-urlencoded')) {
361
178
 
362
179
  let body = '';
363
180
  const chunks = [];
@@ -405,9 +222,9 @@ const httpInstrumentation = new HttpInstrumentation({
405
222
  });
406
223
  }
407
224
  } catch (e) {
408
- span.setAttribute('http.request.body', '[UNPARSEABLE - REDACTED FOR SAFETY]');
225
+ // Parse error: capture as-is (truncated)
226
+ span.setAttribute('http.request.body', body.substring(0, 1000));
409
227
  span.setAttribute('http.request.body.parse_error', true);
410
- span.setAttribute('http.request.body.size', size);
411
228
  }
412
229
  } else if (size > maxBodySize) {
413
230
  span.setAttribute('http.request.body', `[TOO LARGE: ${size} bytes]`);
@@ -415,38 +232,10 @@ const httpInstrumentation = new HttpInstrumentation({
415
232
  }
416
233
  });
417
234
  } else if (contentType.includes('multipart/form-data')) {
418
- if (captureMultipart) {
419
- collectMultipartMeta(request, contentType, allSensitiveFields, 1000, ({ error, parsed, totalSize }) => {
420
- try {
421
- if (error === 'BOUNDARY_NOT_FOUND') {
422
- span.setAttribute('http.request.body', '[MULTIPART - BOUNDARY NOT FOUND]');
423
- span.setAttribute('http.request.body.type', 'multipart');
424
- return;
425
- }
426
- if (error) {
427
- span.setAttribute('http.request.body', '[MULTIPART - PARSE ERROR]');
428
- span.setAttribute('http.request.body.type', 'multipart');
429
- span.setAttribute('http.request.body.parse_error', true);
430
- return;
431
- }
432
- span.setAttributes({
433
- 'http.request.body': JSON.stringify(parsed).substring(0, maxBodySize),
434
- 'http.request.body.type': 'multipart',
435
- 'http.request.body.size': totalSize,
436
- 'http.request.body.fields_count': Object.keys(parsed.fields).length,
437
- 'http.request.body.files_count': parsed.files.length,
438
- });
439
- } catch (e) {
440
- span.setAttribute('http.request.body', '[MULTIPART - PARSE ERROR]');
441
- span.setAttribute('http.request.body.type', 'multipart');
442
- span.setAttribute('http.request.body.parse_error', true);
443
- }
444
- });
445
- } else {
446
- span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
447
- span.setAttribute('http.request.body.type', 'multipart');
448
- span.setAttribute('http.request.body.note', 'Set SECURENOW_CAPTURE_MULTIPART=1 to enable');
449
- }
235
+ // Multipart is NOT captured
236
+ span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
237
+ span.setAttribute('http.request.body.type', 'multipart');
238
+ span.setAttribute('http.request.body.note', 'File uploads not captured by design');
450
239
  }
451
240
  }
452
241
  } catch (error) {
@@ -456,7 +245,7 @@ const httpInstrumentation = new HttpInstrumentation({
456
245
  });
457
246
 
458
247
  // -------- Logging Configuration --------
459
- const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) === '1' || String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true';
248
+ const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) !== '0' && String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() !== 'false';
460
249
 
461
250
  // Create shared resource for both traces and logs
462
251
  const sharedResource = new Resource({
@@ -468,6 +257,7 @@ const sharedResource = new Resource({
468
257
 
469
258
  // Initialize LoggerProvider if logging is enabled
470
259
  let loggerProvider = null;
260
+ let globalLogger = null;
471
261
 
472
262
  if (loggingEnabled) {
473
263
  const logExporter = new OTLPLogExporter({
@@ -475,35 +265,12 @@ if (loggingEnabled) {
475
265
  headers
476
266
  });
477
267
 
478
- const batchLogProcessor = new BatchLogRecordProcessor(logExporter);
479
268
  loggerProvider = new LoggerProvider({
480
269
  resource: sharedResource,
270
+ processors: [new BatchLogRecordProcessor(logExporter)],
481
271
  });
482
- loggerProvider.addLogRecordProcessor(batchLogProcessor);
483
-
484
- // Auto-patch console.* so every log/warn/error becomes an OTel log record
485
- const _logger = loggerProvider.getLogger('console', '1.0.0');
486
- const _orig = { log: console.log, info: console.info, warn: console.warn, error: console.error, debug: console.debug };
487
- const SEV = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
488
- function _emit(sn, st, args) {
489
- try {
490
- const activeCtx = context.active();
491
- const spanCtx = trace.getSpanContext(activeCtx);
492
- _logger.emit({
493
- severityNumber: sn,
494
- severityText: st,
495
- body: args.map(a => (typeof a === 'object' && a !== null) ? JSON.stringify(a) : String(a)).join(' '),
496
- attributes: { 'log.source': 'console', 'log.method': st.toLowerCase() },
497
- ...(spanCtx && { context: activeCtx }),
498
- });
499
- } catch (_) {}
500
- }
501
- console.log = function (...a) { _emit(SEV.INFO, 'INFO', a); _orig.log.apply(console, a); };
502
- console.info = function (...a) { _emit(SEV.INFO, 'INFO', a); _orig.info.apply(console, a); };
503
- console.warn = function (...a) { _emit(SEV.WARN, 'WARN', a); _orig.warn.apply(console, a); };
504
- console.error = function (...a) { _emit(SEV.ERROR, 'ERROR', a); _orig.error.apply(console, a); };
505
- console.debug = function (...a) { _emit(SEV.DEBUG, 'DEBUG', a); _orig.debug.apply(console, a); };
506
- console.__securenow_patched = true;
272
+
273
+ globalLogger = loggerProvider.getLogger('securenow', '1.0.0');
507
274
  }
508
275
 
509
276
  // -------- SDK --------
@@ -512,11 +279,9 @@ const sdk = new NodeSDK({
512
279
  traceExporter,
513
280
  instrumentations: [
514
281
  httpInstrumentation,
515
- ...(disabledMap['@opentelemetry/instrumentation-mongodb'] ? [] : [new MongoDBInstrumentation()]),
516
282
  ...getNodeAutoInstrumentations({
517
283
  ...disabledMap,
518
- '@opentelemetry/instrumentation-http': { enabled: false },
519
- '@opentelemetry/instrumentation-mongodb': { enabled: false },
284
+ '@opentelemetry/instrumentation-http': { enabled: false }, // We use our custom one above
520
285
  }),
521
286
  ],
522
287
  resource: sharedResource,
@@ -535,52 +300,22 @@ const sdk = new NodeSDK({
535
300
  if (captureBody) {
536
301
  console.log('[securenow] 📝 Request body capture: ENABLED (max: %d bytes, redacting %d sensitive fields)', maxBodySize, allSensitiveFields.length);
537
302
  }
538
- if (captureMultipart) {
539
- console.log('[securenow] 📎 Multipart body capture: ENABLED (streaming — file content not buffered)');
540
- }
541
303
  if (String(env('SECURENOW_TEST_SPAN')) === '1') {
542
304
  const api = require('@opentelemetry/api');
543
305
  const tracer = api.trace.getTracer('securenow-smoke');
544
306
  const span = tracer.startSpan('securenow.startup.smoke'); span.end();
545
307
  }
546
-
547
- // Free trial banner
548
- const { isFreeTrial, patchHttpForBanner } = require('./free-trial-banner');
549
- if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
550
- patchHttpForBanner();
551
- }
552
-
553
- // Firewall — auto-activates when SECURENOW_API_KEY is set
554
- const firewallApiKey = env('SECURENOW_API_KEY');
555
- if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
556
- require('./firewall').init({
557
- apiKey: firewallApiKey,
558
- apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
559
- versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
560
- syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
561
- failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
562
- statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
563
- log: env('SECURENOW_FIREWALL_LOG') !== '0',
564
- tcp: env('SECURENOW_FIREWALL_TCP') === '1',
565
- iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
566
- cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
567
- });
568
- }
569
308
  } catch (e) {
570
309
  console.error('[securenow] OTel start failed:', e && e.stack || e);
571
310
  }
572
311
  })();
573
312
 
574
- let shuttingDown = false;
575
313
  async function safeShutdown(sig) {
576
- if (shuttingDown) return;
577
- shuttingDown = true;
578
314
  try {
579
315
  await Promise.resolve(sdk.shutdown?.());
580
316
  if (loggerProvider) {
581
317
  await Promise.resolve(loggerProvider.shutdown?.());
582
318
  }
583
- try { require('./firewall').shutdown(); } catch (_) {}
584
319
  console.log(`[securenow] Tracing and logging terminated on ${sig}`);
585
320
  }
586
321
  catch (e) { console.error('[securenow] Shutdown error:', e); }