securenow 5.10.2 → 5.11.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/CONSUMING-APPS-GUIDE.md +30 -0
- package/NPM_README.md +65 -0
- package/README.md +13 -0
- package/cidr.js +83 -0
- package/cli/auth.js +208 -208
- package/cli/config.js +117 -117
- package/cli/firewall.js +81 -0
- package/cli/fp.js +638 -0
- package/cli/security.js +4 -8
- package/cli.js +28 -1
- package/console-instrumentation.js +147 -147
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +40 -1
- package/docs/API-KEYS-GUIDE.md +215 -0
- package/docs/ENVIRONMENT-VARIABLES.md +880 -697
- package/docs/FIREWALL-GUIDE.md +388 -0
- package/docs/INDEX.md +8 -1
- package/firewall-cloud.js +212 -0
- package/firewall-iptables.js +139 -0
- package/firewall-tcp.js +58 -0
- package/firewall.js +235 -0
- package/free-trial-banner.js +174 -174
- package/nextjs-auto-capture.js +199 -199
- package/nextjs-middleware.js +186 -186
- package/nextjs-wrapper.js +158 -158
- package/nuxt-server-plugin.mjs +400 -400
- package/package.json +30 -3
- package/resolve-ip.js +77 -0
- package/tracing.js +31 -56
- package/web-vite.mjs +239 -239
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.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",
|
|
@@ -33,7 +33,11 @@
|
|
|
33
33
|
"nuxt",
|
|
34
34
|
"nuxt3",
|
|
35
35
|
"nitro",
|
|
36
|
-
"vue"
|
|
36
|
+
"vue",
|
|
37
|
+
"firewall",
|
|
38
|
+
"ip-blocking",
|
|
39
|
+
"waf",
|
|
40
|
+
"security"
|
|
37
41
|
],
|
|
38
42
|
"exports": {
|
|
39
43
|
".": {
|
|
@@ -74,6 +78,15 @@
|
|
|
74
78
|
"import": "./nuxt.mjs",
|
|
75
79
|
"default": "./nuxt.mjs"
|
|
76
80
|
},
|
|
81
|
+
"./firewall": {
|
|
82
|
+
"default": "./firewall.js"
|
|
83
|
+
},
|
|
84
|
+
"./cidr": {
|
|
85
|
+
"default": "./cidr.js"
|
|
86
|
+
},
|
|
87
|
+
"./resolve-ip": {
|
|
88
|
+
"default": "./resolve-ip.js"
|
|
89
|
+
},
|
|
77
90
|
"./register-vite": "./register-vite.js",
|
|
78
91
|
"./web-vite": {
|
|
79
92
|
"import": "./web-vite.mjs",
|
|
@@ -101,6 +114,12 @@
|
|
|
101
114
|
"cli.js",
|
|
102
115
|
"cli/",
|
|
103
116
|
"free-trial-banner.js",
|
|
117
|
+
"resolve-ip.js",
|
|
118
|
+
"cidr.js",
|
|
119
|
+
"firewall.js",
|
|
120
|
+
"firewall-tcp.js",
|
|
121
|
+
"firewall-iptables.js",
|
|
122
|
+
"firewall-cloud.js",
|
|
104
123
|
"postinstall.js",
|
|
105
124
|
"register-vite.js",
|
|
106
125
|
"web-vite.mjs",
|
|
@@ -134,7 +153,9 @@
|
|
|
134
153
|
},
|
|
135
154
|
"peerDependencies": {
|
|
136
155
|
"next": ">=13.0.0",
|
|
137
|
-
"nuxt": ">=3.0.0"
|
|
156
|
+
"nuxt": ">=3.0.0",
|
|
157
|
+
"@aws-sdk/client-wafv2": ">=3.0.0",
|
|
158
|
+
"@google-cloud/compute": ">=4.0.0"
|
|
138
159
|
},
|
|
139
160
|
"peerDependenciesMeta": {
|
|
140
161
|
"next": {
|
|
@@ -142,6 +163,12 @@
|
|
|
142
163
|
},
|
|
143
164
|
"nuxt": {
|
|
144
165
|
"optional": true
|
|
166
|
+
},
|
|
167
|
+
"@aws-sdk/client-wafv2": {
|
|
168
|
+
"optional": true
|
|
169
|
+
},
|
|
170
|
+
"@google-cloud/compute": {
|
|
171
|
+
"optional": true
|
|
145
172
|
}
|
|
146
173
|
},
|
|
147
174
|
"overrides": {
|
package/resolve-ip.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
|
|
6
|
+
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}:)/;
|
|
7
|
+
|
|
8
|
+
const trustedProxyCsv = (process.env.SECURENOW_TRUSTED_PROXIES || '').trim();
|
|
9
|
+
const trustedProxySet = trustedProxyCsv
|
|
10
|
+
? new Set(trustedProxyCsv.split(',').map(s => s.trim()).filter(Boolean))
|
|
11
|
+
: null;
|
|
12
|
+
|
|
13
|
+
let _hostIp = null;
|
|
14
|
+
function getHostIp() {
|
|
15
|
+
if (_hostIp !== null) return _hostIp;
|
|
16
|
+
try {
|
|
17
|
+
const ifaces = os.networkInterfaces();
|
|
18
|
+
for (const name of Object.keys(ifaces)) {
|
|
19
|
+
for (const iface of ifaces[name]) {
|
|
20
|
+
if (!iface.internal && iface.family === 'IPv4') { _hostIp = iface.address; return _hostIp; }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} catch (_) {}
|
|
24
|
+
_hostIp = '';
|
|
25
|
+
return _hostIp;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isFromTrustedProxy(socketIp) {
|
|
29
|
+
if (!socketIp) return false;
|
|
30
|
+
const normalized = socketIp.replace(/^::ffff:/, '');
|
|
31
|
+
if (trustedProxySet && trustedProxySet.has(normalized)) return true;
|
|
32
|
+
return PRIVATE_IP_RE.test(socketIp);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the real client IP from an HTTP request, respecting trusted proxies.
|
|
37
|
+
* Reads X-Forwarded-For / X-Real-IP only when the direct connection comes
|
|
38
|
+
* from a private/trusted proxy IP. Prevents client-side IP spoofing.
|
|
39
|
+
*/
|
|
40
|
+
function resolveClientIp(request) {
|
|
41
|
+
const socketIp = request.socket?.remoteAddress || '';
|
|
42
|
+
if (!isFromTrustedProxy(socketIp)) return socketIp;
|
|
43
|
+
|
|
44
|
+
const fwd = request.headers['x-forwarded-for'];
|
|
45
|
+
if (fwd) {
|
|
46
|
+
const chain = String(fwd).split(',').map(s => s.trim()).filter(Boolean);
|
|
47
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
48
|
+
if (!isFromTrustedProxy(chain[i])) return chain[i];
|
|
49
|
+
}
|
|
50
|
+
return socketIp;
|
|
51
|
+
}
|
|
52
|
+
const headerIp = request.headers['x-real-ip'];
|
|
53
|
+
if (headerIp) return headerIp;
|
|
54
|
+
|
|
55
|
+
if (LOOPBACK_RE.test(socketIp)) {
|
|
56
|
+
const hostIp = getHostIp();
|
|
57
|
+
if (hostIp) return hostIp;
|
|
58
|
+
}
|
|
59
|
+
return socketIp;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve IP from a raw TCP socket (no HTTP headers available).
|
|
64
|
+
* Normalizes IPv6-mapped IPv4 addresses.
|
|
65
|
+
*/
|
|
66
|
+
function resolveSocketIp(socket) {
|
|
67
|
+
const raw = socket?.remoteAddress || '';
|
|
68
|
+
return raw.replace(/^::ffff:/, '');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
resolveClientIp,
|
|
73
|
+
resolveSocketIp,
|
|
74
|
+
isFromTrustedProxy,
|
|
75
|
+
LOOPBACK_RE,
|
|
76
|
+
PRIVATE_IP_RE,
|
|
77
|
+
};
|
package/tracing.js
CHANGED
|
@@ -327,62 +327,7 @@ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveField
|
|
|
327
327
|
const captureMultipart = String(env('SECURENOW_CAPTURE_MULTIPART')) === '1' || String(env('SECURENOW_CAPTURE_MULTIPART')).toLowerCase() === 'true';
|
|
328
328
|
|
|
329
329
|
// -------- Trusted proxy IP resolution --------
|
|
330
|
-
|
|
331
|
-
// a known proxy (loopback, private RFC-1918/RFC-4193, or an explicit allowlist).
|
|
332
|
-
// This prevents end-users from spoofing their IP via custom headers.
|
|
333
|
-
const os = require('os');
|
|
334
|
-
const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
|
|
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}:)/;
|
|
336
|
-
const trustedProxyCsv = (env('SECURENOW_TRUSTED_PROXIES') || '').trim();
|
|
337
|
-
const trustedProxySet = trustedProxyCsv ? new Set(trustedProxyCsv.split(',').map(s => s.trim()).filter(Boolean)) : null;
|
|
338
|
-
|
|
339
|
-
// Resolve the host's actual network IP once at startup (used when socket is loopback)
|
|
340
|
-
let _hostIp = null;
|
|
341
|
-
function getHostIp() {
|
|
342
|
-
if (_hostIp !== null) return _hostIp;
|
|
343
|
-
try {
|
|
344
|
-
const ifaces = os.networkInterfaces();
|
|
345
|
-
for (const name of Object.keys(ifaces)) {
|
|
346
|
-
for (const iface of ifaces[name]) {
|
|
347
|
-
if (!iface.internal && iface.family === 'IPv4') { _hostIp = iface.address; return _hostIp; }
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
} catch (_) {}
|
|
351
|
-
_hostIp = '';
|
|
352
|
-
return _hostIp;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function isFromTrustedProxy(socketIp) {
|
|
356
|
-
if (!socketIp) return false;
|
|
357
|
-
const normalized = socketIp.replace(/^::ffff:/, '');
|
|
358
|
-
if (trustedProxySet && trustedProxySet.has(normalized)) return true;
|
|
359
|
-
return PRIVATE_IP_RE.test(socketIp);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function resolveClientIp(request) {
|
|
363
|
-
const socketIp = request.socket?.remoteAddress || '';
|
|
364
|
-
if (!isFromTrustedProxy(socketIp)) return socketIp;
|
|
365
|
-
|
|
366
|
-
// Connection is from a trusted proxy — read the leftmost untrusted IP
|
|
367
|
-
const fwd = request.headers['x-forwarded-for'];
|
|
368
|
-
if (fwd) {
|
|
369
|
-
const chain = String(fwd).split(',').map(s => s.trim()).filter(Boolean);
|
|
370
|
-
for (let i = chain.length - 1; i >= 0; i--) {
|
|
371
|
-
if (!isFromTrustedProxy(chain[i])) return chain[i];
|
|
372
|
-
}
|
|
373
|
-
return socketIp;
|
|
374
|
-
}
|
|
375
|
-
const headerIp = request.headers['x-real-ip'];
|
|
376
|
-
if (headerIp) return headerIp;
|
|
377
|
-
|
|
378
|
-
// Loopback means the client is on this machine — use the host's network IP
|
|
379
|
-
// so traces are attributed to the actual machine, not a useless ::1 / 127.0.0.1
|
|
380
|
-
if (LOOPBACK_RE.test(socketIp)) {
|
|
381
|
-
const hostIp = getHostIp();
|
|
382
|
-
if (hostIp) return hostIp;
|
|
383
|
-
}
|
|
384
|
-
return socketIp;
|
|
385
|
-
}
|
|
330
|
+
const { resolveClientIp, isFromTrustedProxy, LOOPBACK_RE } = require('./resolve-ip');
|
|
386
331
|
|
|
387
332
|
// Configure HTTP instrumentation with body capture
|
|
388
333
|
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
|
|
@@ -394,6 +339,19 @@ const httpInstrumentation = new HttpInstrumentation({
|
|
|
394
339
|
span.setAttribute('http.client_ip', clientIp);
|
|
395
340
|
}
|
|
396
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
|
+
|
|
397
355
|
if ((captureBody || captureMultipart) && request.method && ['POST', 'PUT', 'PATCH'].includes(request.method)) {
|
|
398
356
|
const contentType = request.headers['content-type'] || '';
|
|
399
357
|
|
|
@@ -591,6 +549,22 @@ const sdk = new NodeSDK({
|
|
|
591
549
|
if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
|
|
592
550
|
patchHttpForBanner();
|
|
593
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
|
+
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 60,
|
|
560
|
+
failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
|
|
561
|
+
statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
|
|
562
|
+
log: env('SECURENOW_FIREWALL_LOG') !== '0',
|
|
563
|
+
tcp: env('SECURENOW_FIREWALL_TCP') === '1',
|
|
564
|
+
iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
|
|
565
|
+
cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
594
568
|
} catch (e) {
|
|
595
569
|
console.error('[securenow] OTel start failed:', e && e.stack || e);
|
|
596
570
|
}
|
|
@@ -605,6 +579,7 @@ async function safeShutdown(sig) {
|
|
|
605
579
|
if (loggerProvider) {
|
|
606
580
|
await Promise.resolve(loggerProvider.shutdown?.());
|
|
607
581
|
}
|
|
582
|
+
try { require('./firewall').shutdown(); } catch (_) {}
|
|
608
583
|
console.log(`[securenow] Tracing and logging terminated on ${sig}`);
|
|
609
584
|
}
|
|
610
585
|
catch (e) { console.error('[securenow] Shutdown error:', e); }
|