securenow 5.18.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +40 -239
  3. package/cli.js +455 -415
  4. package/console-instrumentation.js +136 -147
  5. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
  6. package/docs/ARCHITECTURE.md +3 -3
  7. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  8. package/docs/AUTO-SETUP.md +4 -4
  9. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  10. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  11. package/docs/CHANGELOG-NEXTJS.md +35 -1
  12. package/docs/CUSTOMER-GUIDE.md +16 -16
  13. package/docs/EASIEST-SETUP.md +5 -5
  14. package/docs/ENVIRONMENT-VARIABLES.md +652 -880
  15. package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
  16. package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
  17. package/docs/INDEX.md +4 -22
  18. package/docs/LOGGING-GUIDE.md +708 -701
  19. package/docs/LOGGING-QUICKSTART.md +255 -234
  20. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  21. package/docs/NEXTJS-GUIDE.md +14 -14
  22. package/docs/NEXTJS-QUICKSTART.md +1 -1
  23. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  24. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  25. package/docs/REDACTION-EXAMPLES.md +1 -1
  26. package/docs/REQUEST-BODY-CAPTURE.md +10 -19
  27. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  28. package/examples/README.md +6 -6
  29. package/examples/instrumentation-with-auto-capture.ts +1 -1
  30. package/examples/nextjs-env-example.txt +2 -2
  31. package/examples/nextjs-instrumentation.js +1 -1
  32. package/examples/nextjs-instrumentation.ts +1 -1
  33. package/examples/nextjs-with-logging-example.md +6 -6
  34. package/examples/nextjs-with-options.ts +1 -1
  35. package/examples/test-nextjs-setup.js +1 -1
  36. package/nextjs-auto-capture.js +207 -199
  37. package/nextjs-middleware.js +181 -186
  38. package/nextjs-webpack-config.js +53 -88
  39. package/nextjs-wrapper.js +158 -158
  40. package/nextjs.d.ts +1 -1
  41. package/nextjs.js +198 -186
  42. package/package.json +45 -67
  43. package/postinstall.js +6 -6
  44. package/register.d.ts +1 -1
  45. package/register.js +4 -39
  46. package/tracing.d.ts +1 -2
  47. package/tracing.js +26 -286
  48. package/web-vite.mjs +156 -239
  49. package/CONSUMING-APPS-GUIDE.md +0 -455
  50. package/NPM_README.md +0 -1933
  51. package/SKILL-API.md +0 -600
  52. package/SKILL-CLI.md +0 -409
  53. package/cidr.js +0 -83
  54. package/cli/apps.js +0 -585
  55. package/cli/auth.js +0 -280
  56. package/cli/client.js +0 -115
  57. package/cli/config.js +0 -173
  58. package/cli/firewall.js +0 -100
  59. package/cli/fp.js +0 -638
  60. package/cli/init.js +0 -201
  61. package/cli/monitor.js +0 -440
  62. package/cli/run.js +0 -133
  63. package/cli/security.js +0 -1064
  64. package/cli/ui.js +0 -386
  65. package/docs/API-KEYS-GUIDE.md +0 -233
  66. package/docs/AUTO-SETUP-SUMMARY.md +0 -331
  67. package/docs/BODY-CAPTURE-FIX.md +0 -261
  68. package/docs/COMPLETION-REPORT.md +0 -408
  69. package/docs/FINAL-SOLUTION.md +0 -335
  70. package/docs/FIREWALL-GUIDE.md +0 -426
  71. package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
  72. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
  73. package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
  74. package/docs/NUXT-GUIDE.md +0 -166
  75. package/docs/SOLUTION-SUMMARY.md +0 -312
  76. package/firewall-cloud.js +0 -212
  77. package/firewall-iptables.js +0 -139
  78. package/firewall-only.js +0 -38
  79. package/firewall-tcp.js +0 -74
  80. package/firewall.js +0 -720
  81. package/free-trial-banner.js +0 -174
  82. package/nuxt-server-plugin.mjs +0 -423
  83. package/nuxt.d.ts +0 -60
  84. package/nuxt.mjs +0 -75
  85. package/resolve-ip.js +0 -77
package/postinstall.js CHANGED
@@ -53,7 +53,7 @@ export function register() {
53
53
  * SECURENOW_APPID=my-nextjs-app
54
54
  *
55
55
  * Optional:
56
- * SECURENOW_INSTANCE=http://your-otlp-backend:4318
56
+ * SECURENOW_INSTANCE=http://your-signoz-server:4318
57
57
  * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
58
58
  * OTEL_LOG_LEVEL=info
59
59
  */
@@ -77,7 +77,7 @@ export function register() {
77
77
  * SECURENOW_APPID=my-nextjs-app
78
78
  *
79
79
  * Optional:
80
- * SECURENOW_INSTANCE=http://your-otlp-backend:4318
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
83
  *
@@ -150,9 +150,9 @@ function createEnvTemplate(targetPath) {
150
150
  # Required: Your application identifier
151
151
  SECURENOW_APPID=my-nextjs-app
152
152
 
153
- # Optional: Your OTLP-compatible backend / collector endpoint
153
+ # Optional: Your SigNoz/OpenTelemetry collector endpoint
154
154
  # Default: https://freetrial.securenow.ai:4318
155
- SECURENOW_INSTANCE=http://your-otlp-backend:4318
155
+ SECURENOW_INSTANCE=http://your-signoz-server:4318
156
156
 
157
157
  # Optional: API key or authentication headers
158
158
  # OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-api-key-here"
@@ -270,14 +270,14 @@ async function setup() {
270
270
  console.log('│ │');
271
271
  console.log('│ 1. Edit .env.local and set: │');
272
272
  console.log('│ SECURENOW_APPID=your-app-name │');
273
- console.log('│ SECURENOW_INSTANCE=http://your-otlp-backend:4318 │');
273
+ console.log('│ SECURENOW_INSTANCE=http://signoz:4318 │');
274
274
  if (shouldCreateMiddleware) {
275
275
  console.log('│ SECURENOW_CAPTURE_BODY=1 │');
276
276
  }
277
277
  console.log('│ │');
278
278
  console.log('│ 2. Run your app: npm run dev │');
279
279
  console.log('│ │');
280
- console.log('│ 3. Check SecureNow for traces! │');
280
+ console.log('│ 3. Check SigNoz for traces! │');
281
281
  console.log('│ │');
282
282
  if (shouldCreateMiddleware) {
283
283
  console.log('│ 📝 Body capture enabled with auto-redaction │');
package/register.d.ts CHANGED
@@ -56,7 +56,7 @@ export {};
56
56
  * node_args: '-r securenow/register',
57
57
  * env: {
58
58
  * SECURENOW_APPID: 'my-app',
59
- * SECURENOW_INSTANCE: 'http://your-otlp-backend:4318',
59
+ * SECURENOW_INSTANCE: 'http://signoz:4318',
60
60
  * SECURENOW_CAPTURE_BODY: '1',
61
61
  * }
62
62
  * }]
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,15 +18,15 @@
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');
31
25
  const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
26
+ const { logs: apiLogs } = require('@opentelemetry/api-logs');
32
27
  const { Resource } = require('@opentelemetry/resources');
33
28
  const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
34
29
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
35
- const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb');
36
30
  const { v4: uuidv4 } = require('uuid');
37
31
 
38
32
  const env = k => process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
@@ -53,10 +47,6 @@ const DEFAULT_SENSITIVE_FIELDS = [
53
47
  'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
54
48
  ];
55
49
 
56
- function escapeRegex(str) {
57
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
58
- }
59
-
60
50
  /**
61
51
  * Redact sensitive fields from an object
62
52
  */
@@ -65,7 +55,7 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
65
55
 
66
56
  const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
67
57
 
68
- for (const key of Object.keys(redacted)) {
58
+ for (const key in redacted) {
69
59
  const lowerKey = key.toLowerCase();
70
60
 
71
61
  if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
@@ -88,10 +78,10 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
88
78
 
89
79
  // Redact sensitive fields in GraphQL arguments and variables
90
80
  sensitiveFields.forEach(field => {
91
- const escaped = escapeRegex(field);
81
+ // Match patterns: field: "value" or field: 'value' or field:"value"
92
82
  const patterns = [
93
- new RegExp(`(${escaped}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
94
- new RegExp(`(${escaped}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
83
+ new RegExp(`(${field}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
84
+ new RegExp(`(${field}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
95
85
  ];
96
86
 
97
87
  patterns.forEach(pattern => {
@@ -108,155 +98,6 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
108
98
  return redacted;
109
99
  }
110
100
 
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
101
  // -------- diagnostics --------
261
102
  (() => {
262
103
  const L = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
@@ -320,44 +161,21 @@ for (const n of (env('SECURENOW_DISABLE_INSTRUMENTATIONS') || '').split(',').map
320
161
 
321
162
  // -------- Body Capture Configuration --------
322
163
  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);
164
+ const maxBodySize = parseInt(env('SECURENOW_MAX_BODY_SIZE') || '10240'); // 10KB default
324
165
  const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
325
166
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
326
167
 
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
168
  // Configure HTTP instrumentation with body capture
333
169
  const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
334
170
  const httpInstrumentation = new HttpInstrumentation({
335
171
  requestHook: (span, request) => {
336
172
  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)) {
173
+ if (captureBody && request.method && ['POST', 'PUT', 'PATCH'].includes(request.method)) {
356
174
  const contentType = request.headers['content-type'] || '';
357
175
 
358
- if (captureBody && (contentType.includes('application/json') ||
176
+ if (contentType.includes('application/json') ||
359
177
  contentType.includes('application/graphql') ||
360
- contentType.includes('application/x-www-form-urlencoded'))) {
178
+ contentType.includes('application/x-www-form-urlencoded')) {
361
179
 
362
180
  let body = '';
363
181
  const chunks = [];
@@ -405,9 +223,9 @@ const httpInstrumentation = new HttpInstrumentation({
405
223
  });
406
224
  }
407
225
  } catch (e) {
408
- span.setAttribute('http.request.body', '[UNPARSEABLE - REDACTED FOR SAFETY]');
226
+ // Parse error: capture as-is (truncated)
227
+ span.setAttribute('http.request.body', body.substring(0, 1000));
409
228
  span.setAttribute('http.request.body.parse_error', true);
410
- span.setAttribute('http.request.body.size', size);
411
229
  }
412
230
  } else if (size > maxBodySize) {
413
231
  span.setAttribute('http.request.body', `[TOO LARGE: ${size} bytes]`);
@@ -415,38 +233,10 @@ const httpInstrumentation = new HttpInstrumentation({
415
233
  }
416
234
  });
417
235
  } 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
- }
236
+ // Multipart is NOT captured
237
+ span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
238
+ span.setAttribute('http.request.body.type', 'multipart');
239
+ span.setAttribute('http.request.body.note', 'File uploads not captured by design');
450
240
  }
451
241
  }
452
242
  } catch (error) {
@@ -456,7 +246,7 @@ const httpInstrumentation = new HttpInstrumentation({
456
246
  });
457
247
 
458
248
  // -------- Logging Configuration --------
459
- const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) === '1' || String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true';
249
+ const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) !== '0' && String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() !== 'false';
460
250
 
461
251
  // Create shared resource for both traces and logs
462
252
  const sharedResource = new Resource({
@@ -468,6 +258,7 @@ const sharedResource = new Resource({
468
258
 
469
259
  // Initialize LoggerProvider if logging is enabled
470
260
  let loggerProvider = null;
261
+ let globalLogger = null;
471
262
 
472
263
  if (loggingEnabled) {
473
264
  const logExporter = new OTLPLogExporter({
@@ -475,35 +266,16 @@ if (loggingEnabled) {
475
266
  headers
476
267
  });
477
268
 
478
- const batchLogProcessor = new BatchLogRecordProcessor(logExporter);
479
269
  loggerProvider = new LoggerProvider({
480
270
  resource: sharedResource,
481
271
  });
482
- loggerProvider.addLogRecordProcessor(batchLogProcessor);
272
+ // sdk-logs 0.47.x ignores the `processors` constructor option (added in 0.52),
273
+ // so the provider would silently keep a NoopLogRecordProcessor and drop every
274
+ // emit(). Register the processor explicitly instead.
275
+ loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
276
+ apiLogs.setGlobalLoggerProvider(loggerProvider);
483
277
 
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;
278
+ globalLogger = loggerProvider.getLogger('securenow', '1.0.0');
507
279
  }
508
280
 
509
281
  // -------- SDK --------
@@ -512,11 +284,9 @@ const sdk = new NodeSDK({
512
284
  traceExporter,
513
285
  instrumentations: [
514
286
  httpInstrumentation,
515
- ...(disabledMap['@opentelemetry/instrumentation-mongodb'] ? [] : [new MongoDBInstrumentation()]),
516
287
  ...getNodeAutoInstrumentations({
517
288
  ...disabledMap,
518
- '@opentelemetry/instrumentation-http': { enabled: false },
519
- '@opentelemetry/instrumentation-mongodb': { enabled: false },
289
+ '@opentelemetry/instrumentation-http': { enabled: false }, // We use our custom one above
520
290
  }),
521
291
  ],
522
292
  resource: sharedResource,
@@ -535,52 +305,22 @@ const sdk = new NodeSDK({
535
305
  if (captureBody) {
536
306
  console.log('[securenow] 📝 Request body capture: ENABLED (max: %d bytes, redacting %d sensitive fields)', maxBodySize, allSensitiveFields.length);
537
307
  }
538
- if (captureMultipart) {
539
- console.log('[securenow] 📎 Multipart body capture: ENABLED (streaming — file content not buffered)');
540
- }
541
308
  if (String(env('SECURENOW_TEST_SPAN')) === '1') {
542
309
  const api = require('@opentelemetry/api');
543
310
  const tracer = api.trace.getTracer('securenow-smoke');
544
311
  const span = tracer.startSpan('securenow.startup.smoke'); span.end();
545
312
  }
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
313
  } catch (e) {
570
314
  console.error('[securenow] OTel start failed:', e && e.stack || e);
571
315
  }
572
316
  })();
573
317
 
574
- let shuttingDown = false;
575
318
  async function safeShutdown(sig) {
576
- if (shuttingDown) return;
577
- shuttingDown = true;
578
319
  try {
579
320
  await Promise.resolve(sdk.shutdown?.());
580
321
  if (loggerProvider) {
581
322
  await Promise.resolve(loggerProvider.shutdown?.());
582
323
  }
583
- try { require('./firewall').shutdown(); } catch (_) {}
584
324
  console.log(`[securenow] Tracing and logging terminated on ${sig}`);
585
325
  }
586
326
  catch (e) { console.error('[securenow] Shutdown error:', e); }