securenow 5.8.1 → 5.9.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.
package/cli/auth.js CHANGED
@@ -10,7 +10,7 @@ function openBrowser(url) {
10
10
  try {
11
11
  const platform = process.platform;
12
12
  if (platform === 'darwin') execFileSync('open', [url], { stdio: 'ignore' });
13
- else if (platform === 'win32') execFileSync('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
13
+ else if (platform === 'win32') execFileSync('rundll32', ['url.dll,FileProtocolHandler', url], { stdio: 'ignore' });
14
14
  else execFileSync('xdg-open', [url], { stdio: 'ignore' });
15
15
  return true;
16
16
  } catch {
package/cli/config.js CHANGED
@@ -32,6 +32,12 @@ function loadJSON(filepath) {
32
32
  function saveJSON(filepath, data) {
33
33
  ensureDir();
34
34
  fs.writeFileSync(filepath, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 });
35
+ if (process.platform === 'win32') {
36
+ try {
37
+ const { execFileSync } = require('child_process');
38
+ execFileSync('icacls', [filepath, '/inheritance:r', '/grant:r', `${process.env.USERNAME}:F`], { stdio: 'ignore' });
39
+ } catch (_) {}
40
+ }
35
41
  }
36
42
 
37
43
  function loadConfig() {
@@ -107,7 +107,8 @@ function patchHttpForBanner() {
107
107
  if (res._snIsHtml === undefined) {
108
108
  var ct = res.getHeader('content-type');
109
109
  var ce = res.getHeader('content-encoding');
110
- res._snIsHtml = !!(ct && String(ct).includes('text/html') && !ce);
110
+ var csp = res.getHeader('content-security-policy');
111
+ res._snIsHtml = !!(ct && String(ct).includes('text/html') && !ce && !csp);
111
112
  }
112
113
  if (!res._snIsHtml) {
113
114
  res._snBannerDone = true;
@@ -136,6 +137,11 @@ function patchHttpForBanner() {
136
137
  if (modified !== chunk) {
137
138
  var enc = typeof encoding === 'function' ? 'utf8' : encoding;
138
139
  var callback = typeof encoding === 'function' ? encoding : cb;
140
+ try {
141
+ if (this.getHeader('content-length')) {
142
+ this.setHeader('content-length', Buffer.byteLength(modified));
143
+ }
144
+ } catch (_) { /* headers already sent */ }
139
145
  return _origWrite.call(this, modified, enc, callback);
140
146
  }
141
147
  } catch (_) { /* never break the app */ }
@@ -33,7 +33,7 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
33
33
 
34
34
  const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
35
35
 
36
- for (const key in redacted) {
36
+ for (const key of Object.keys(redacted)) {
37
37
  const lowerKey = key.toLowerCase();
38
38
 
39
39
  if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
@@ -27,12 +27,16 @@ const DEFAULT_SENSITIVE_FIELDS = [
27
27
  /**
28
28
  * Redact sensitive fields from an object
29
29
  */
30
+ function escapeRegex(str) {
31
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
32
+ }
33
+
30
34
  function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
31
35
  if (!obj || typeof obj !== 'object') return obj;
32
36
 
33
37
  const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
34
38
 
35
- for (const key in redacted) {
39
+ for (const key of Object.keys(redacted)) {
36
40
  const lowerKey = key.toLowerCase();
37
41
 
38
42
  if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
@@ -54,9 +58,10 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
54
58
  let redacted = query;
55
59
 
56
60
  sensitiveFields.forEach(field => {
61
+ const escaped = escapeRegex(field);
57
62
  const patterns = [
58
- new RegExp(`(${field}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
59
- new RegExp(`(${field}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
63
+ new RegExp(`(${escaped}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
64
+ new RegExp(`(${escaped}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
60
65
  ];
61
66
 
62
67
  patterns.forEach(pattern => {
package/nextjs-wrapper.js CHANGED
@@ -32,7 +32,7 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
32
32
 
33
33
  const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
34
34
 
35
- for (const key in redacted) {
35
+ for (const key of Object.keys(redacted)) {
36
36
  const lowerKey = key.toLowerCase();
37
37
 
38
38
  if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "5.8.1",
3
+ "version": "5.9.0",
4
4
  "description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",
package/tracing.js CHANGED
@@ -53,6 +53,10 @@ const DEFAULT_SENSITIVE_FIELDS = [
53
53
  'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
54
54
  ];
55
55
 
56
+ function escapeRegex(str) {
57
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
58
+ }
59
+
56
60
  /**
57
61
  * Redact sensitive fields from an object
58
62
  */
@@ -61,7 +65,7 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
61
65
 
62
66
  const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
63
67
 
64
- for (const key in redacted) {
68
+ for (const key of Object.keys(redacted)) {
65
69
  const lowerKey = key.toLowerCase();
66
70
 
67
71
  if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
@@ -84,10 +88,10 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
84
88
 
85
89
  // Redact sensitive fields in GraphQL arguments and variables
86
90
  sensitiveFields.forEach(field => {
87
- // Match patterns: field: "value" or field: 'value' or field:"value"
91
+ const escaped = escapeRegex(field);
88
92
  const patterns = [
89
- new RegExp(`(${field}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
90
- new RegExp(`(${field}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
93
+ new RegExp(`(${escaped}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
94
+ new RegExp(`(${escaped}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
91
95
  ];
92
96
 
93
97
  patterns.forEach(pattern => {
@@ -118,7 +122,7 @@ function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFiel
118
122
  const boundary = extractBoundary(contentType);
119
123
  if (!boundary) { onComplete({ error: 'BOUNDARY_NOT_FOUND' }); return; }
120
124
 
121
- const result = { fields: {}, files: [] };
125
+ const result = { fields: Object.create(null), files: [] };
122
126
  let totalSize = 0;
123
127
  let buf = Buffer.alloc(0);
124
128
 
@@ -139,7 +143,7 @@ function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFiel
139
143
  let textVal = '';
140
144
 
141
145
  function flushPart() {
142
- if (!fldName) return;
146
+ if (!fldName || fldName === '__proto__' || fldName === 'constructor' || fldName === 'prototype') return;
143
147
  if (isFile) {
144
148
  result.files.push({ field: fldName, filename: fName, contentType: pCT || 'unknown', size: bodyBytes });
145
149
  } else {
@@ -316,7 +320,7 @@ for (const n of (env('SECURENOW_DISABLE_INSTRUMENTATIONS') || '').split(',').map
316
320
 
317
321
  // -------- Body Capture Configuration --------
318
322
  const captureBody = String(env('SECURENOW_CAPTURE_BODY')) === '1' || String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true';
319
- const maxBodySize = parseInt(env('SECURENOW_MAX_BODY_SIZE') || '10240'); // 10KB default
323
+ const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
320
324
  const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
321
325
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
322
326
 
@@ -328,7 +332,7 @@ const captureMultipart = String(env('SECURENOW_CAPTURE_MULTIPART')) === '1' || S
328
332
  // This prevents end-users from spoofing their IP via custom headers.
329
333
  const os = require('os');
330
334
  const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
331
- const PRIVATE_IP_RE = /^(127\.|::1|::ffff:127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|fc|fd)/;
335
+ const PRIVATE_IP_RE = /^(127\.|::1$|::ffff:127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|f[cd][0-9a-f]{2}:)/;
332
336
  const trustedProxyCsv = (env('SECURENOW_TRUSTED_PROXIES') || '').trim();
333
337
  const trustedProxySet = trustedProxyCsv ? new Set(trustedProxyCsv.split(',').map(s => s.trim()).filter(Boolean)) : null;
334
338
 
@@ -443,9 +447,9 @@ const httpInstrumentation = new HttpInstrumentation({
443
447
  });
444
448
  }
445
449
  } catch (e) {
446
- // Parse error: capture as-is (truncated)
447
- span.setAttribute('http.request.body', body.substring(0, 1000));
450
+ span.setAttribute('http.request.body', '[UNPARSEABLE - REDACTED FOR SAFETY]');
448
451
  span.setAttribute('http.request.body.parse_error', true);
452
+ span.setAttribute('http.request.body.size', size);
449
453
  }
450
454
  } else if (size > maxBodySize) {
451
455
  span.setAttribute('http.request.body', `[TOO LARGE: ${size} bytes]`);
package/web-vite.mjs CHANGED
@@ -55,10 +55,20 @@ const baseName = rawBase || null;
55
55
  const noUuid = String(env('SECURENOW_NO_UUID')) === '1' || String(env('SECURENOW_NO_UUID')).toLowerCase() === 'true';
56
56
  const strict = String(env('SECURENOW_STRICT')) === '1' || String(env('SECURENOW_STRICT')).toLowerCase() === 'true';
57
57
 
58
- // Simple UUID v4 (no crypto dependency needed)
59
58
  function uuidv4(): string {
60
- const rnd = (n = 16) => Array.from({ length: n }, () => Math.floor(Math.random() * 16).toString(16)).join('');
61
- return `${rnd(8)}-${rnd(4)}-4${rnd(3)}-${((8 + Math.random()*4)|0).toString(16)}${rnd(3)}-${rnd(12)}`;
59
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
60
+ return crypto.randomUUID();
61
+ }
62
+ const bytes = new Uint8Array(16);
63
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
64
+ crypto.getRandomValues(bytes);
65
+ } else {
66
+ for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
67
+ }
68
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
69
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
70
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
71
+ return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`;
62
72
  }
63
73
 
64
74
  let serviceName: string;
@@ -124,11 +134,11 @@ export function startSecurenowWeb() {
124
134
  new DocumentLoadInstrumentation(),
125
135
  new UserInteractionInstrumentation(),
126
136
  new FetchInstrumentation({
127
- propagateTraceHeaderCorsUrls: [/.*/],
137
+ propagateTraceHeaderCorsUrls: [new RegExp(`^${location.origin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)],
128
138
  ignoreUrls: [/\/vite\/hmr/, /^chrome-extension:\/\//, /sockjs/],
129
139
  }),
130
140
  new XMLHttpRequestInstrumentation({
131
- propagateTraceHeaderCorsUrls: [/.*/],
141
+ propagateTraceHeaderCorsUrls: [new RegExp(`^${location.origin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)],
132
142
  }),
133
143
  ],
134
144
  });