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 +1 -1
- package/cli/config.js +6 -0
- package/free-trial-banner.js +7 -1
- package/nextjs-auto-capture.js +1 -1
- package/nextjs-middleware.js +8 -3
- package/nextjs-wrapper.js +1 -1
- package/package.json +1 -1
- package/tracing.js +14 -10
- package/web-vite.mjs +15 -5
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('
|
|
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() {
|
package/free-trial-banner.js
CHANGED
|
@@ -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
|
-
|
|
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 */ }
|
package/nextjs-auto-capture.js
CHANGED
|
@@ -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
|
|
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()))) {
|
package/nextjs-middleware.js
CHANGED
|
@@ -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
|
|
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(`(${
|
|
59
|
-
new RegExp(`(${
|
|
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
|
|
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
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
|
|
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
|
-
|
|
91
|
+
const escaped = escapeRegex(field);
|
|
88
92
|
const patterns = [
|
|
89
|
-
new RegExp(`(${
|
|
90
|
-
new RegExp(`(${
|
|
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:
|
|
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') ||
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
});
|