securenow 7.6.9 → 7.7.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/NPM_README.md +12 -1
- package/SKILL-CLI.md +29 -16
- package/cli/automation.js +275 -0
- package/cli/firewall.js +29 -12
- package/cli/human.js +96 -2
- package/cli/security.js +171 -42
- package/cli.js +71 -28
- package/mcp/catalog.js +327 -15
- package/nextjs.js +22 -23
- package/nuxt-server-plugin.mjs +13 -8
- package/package.json +1 -1
- package/resolve-ip.js +135 -60
- package/tracing.js +25 -4
package/resolve-ip.js
CHANGED
|
@@ -2,75 +2,150 @@
|
|
|
2
2
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const appConfig = require('./app-config');
|
|
5
|
-
|
|
6
|
-
const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
|
|
7
|
-
const PRIVATE_IP_RE = /^(127\.|::1
|
|
8
|
-
|
|
5
|
+
|
|
6
|
+
const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
|
|
7
|
+
const PRIVATE_IP_RE = /^(127\.|::1$|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.|f[cd][0-9a-f]{2}:)/i;
|
|
8
|
+
|
|
9
9
|
const trustedProxies = appConfig.listEnv('SECURENOW_TRUSTED_PROXIES');
|
|
10
|
-
const trustedProxySet = trustedProxies.length ? new Set(trustedProxies) : null;
|
|
11
|
-
|
|
12
|
-
let
|
|
13
|
-
function
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
10
|
+
const trustedProxySet = trustedProxies.length ? new Set(trustedProxies.map(normalizeIp).filter(Boolean)) : null;
|
|
11
|
+
|
|
12
|
+
let _hostIps = null;
|
|
13
|
+
function normalizeIp(value) {
|
|
14
|
+
if (Array.isArray(value)) value = value[0];
|
|
15
|
+
if (!value) return '';
|
|
16
|
+
let ip = String(value).trim();
|
|
17
|
+
if (!ip) return '';
|
|
18
|
+
ip = ip.replace(/^::ffff:/i, '');
|
|
19
|
+
if (ip.startsWith('[')) {
|
|
20
|
+
const end = ip.indexOf(']');
|
|
21
|
+
if (end !== -1) ip = ip.slice(1, end);
|
|
22
|
+
} else if (/^\d{1,3}(?:\.\d{1,3}){3}:\d+$/.test(ip)) {
|
|
23
|
+
ip = ip.replace(/:\d+$/, '');
|
|
24
|
+
}
|
|
25
|
+
return ip;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getHostIps() {
|
|
29
|
+
if (_hostIps !== null) return _hostIps;
|
|
30
|
+
const ips = new Set();
|
|
31
|
+
try {
|
|
32
|
+
const ifaces = os.networkInterfaces();
|
|
33
|
+
for (const name of Object.keys(ifaces)) {
|
|
34
|
+
for (const iface of ifaces[name]) {
|
|
35
|
+
if (!iface.internal && iface.address) {
|
|
36
|
+
const normalized = normalizeIp(iface.address);
|
|
37
|
+
if (normalized) ips.add(normalized);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (_) {}
|
|
42
|
+
_hostIps = ips;
|
|
43
|
+
return _hostIps;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isFromTrustedProxy(socketIp) {
|
|
47
|
+
if (!socketIp) return false;
|
|
48
|
+
const normalized = normalizeIp(socketIp);
|
|
49
|
+
if (!normalized) return false;
|
|
50
|
+
if (trustedProxySet && trustedProxySet.has(normalized)) return true;
|
|
51
|
+
if (getHostIps().has(normalized)) return true;
|
|
52
|
+
return PRIVATE_IP_RE.test(normalized);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function firstHeaderValue(value) {
|
|
56
|
+
if (Array.isArray(value)) return value.find(Boolean) || '';
|
|
57
|
+
return value || '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function splitForwardedChain(value) {
|
|
61
|
+
return String(firstHeaderValue(value) || '')
|
|
62
|
+
.split(',')
|
|
63
|
+
.map((s) => normalizeIp(s))
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getHeader(headers, name) {
|
|
68
|
+
if (!headers) return '';
|
|
69
|
+
return firstHeaderValue(headers[name] || headers[name.toLowerCase()] || headers[name.toUpperCase()]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveClientIpWithDetails(request) {
|
|
73
|
+
const socketIp = normalizeIp(request.socket?.remoteAddress || '');
|
|
74
|
+
const headers = request.headers || {};
|
|
75
|
+
const trustedProxy = isFromTrustedProxy(socketIp);
|
|
76
|
+
|
|
77
|
+
const details = {
|
|
78
|
+
ip: socketIp,
|
|
79
|
+
source: socketIp ? 'socket' : 'unknown',
|
|
80
|
+
socketIp,
|
|
81
|
+
trustedProxy,
|
|
82
|
+
forwardedFor: String(getHeader(headers, 'x-forwarded-for') || ''),
|
|
83
|
+
realIp: String(getHeader(headers, 'x-real-ip') || ''),
|
|
84
|
+
cfConnectingIp: String(getHeader(headers, 'cf-connecting-ip') || ''),
|
|
85
|
+
trueClientIp: String(getHeader(headers, 'true-client-ip') || ''),
|
|
86
|
+
clientIp: String(getHeader(headers, 'x-client-ip') || ''),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (!trustedProxy) return details;
|
|
90
|
+
|
|
91
|
+
const chain = splitForwardedChain(details.forwardedFor);
|
|
92
|
+
if (chain.length) {
|
|
93
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
94
|
+
if (!isFromTrustedProxy(chain[i])) {
|
|
95
|
+
return { ...details, ip: chain[i], source: 'x-forwarded-for' };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return details;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const headerCandidates = [
|
|
102
|
+
['cf-connecting-ip', details.cfConnectingIp],
|
|
103
|
+
['true-client-ip', details.trueClientIp],
|
|
104
|
+
['x-real-ip', details.realIp],
|
|
105
|
+
['x-client-ip', details.clientIp],
|
|
106
|
+
];
|
|
107
|
+
for (const [source, raw] of headerCandidates) {
|
|
108
|
+
const ip = normalizeIp(raw);
|
|
109
|
+
if (ip) return { ...details, ip, source };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (LOOPBACK_RE.test(socketIp)) {
|
|
113
|
+
const hostIp = [...getHostIps()][0] || '';
|
|
114
|
+
if (hostIp) return { ...details, ip: hostIp, source: 'host-network' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (trustedProxy && getHostIps().has(socketIp)) {
|
|
118
|
+
return { ...details, source: 'trusted-proxy-socket' };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return details;
|
|
122
|
+
}
|
|
33
123
|
|
|
34
124
|
/**
|
|
35
125
|
* Resolve the real client IP from an HTTP request, respecting trusted proxies.
|
|
36
126
|
* Reads X-Forwarded-For / X-Real-IP only when the direct connection comes
|
|
37
127
|
* from a private/trusted proxy IP. Prevents client-side IP spoofing.
|
|
38
128
|
*/
|
|
39
|
-
function resolveClientIp(request) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const fwd = request.headers['x-forwarded-for'];
|
|
44
|
-
if (fwd) {
|
|
45
|
-
const chain = String(fwd).split(',').map(s => s.trim()).filter(Boolean);
|
|
46
|
-
for (let i = chain.length - 1; i >= 0; i--) {
|
|
47
|
-
if (!isFromTrustedProxy(chain[i])) return chain[i];
|
|
48
|
-
}
|
|
49
|
-
return socketIp;
|
|
50
|
-
}
|
|
51
|
-
const headerIp = request.headers['x-real-ip'];
|
|
52
|
-
if (headerIp) return headerIp;
|
|
53
|
-
|
|
54
|
-
if (LOOPBACK_RE.test(socketIp)) {
|
|
55
|
-
const hostIp = getHostIp();
|
|
56
|
-
if (hostIp) return hostIp;
|
|
57
|
-
}
|
|
58
|
-
return socketIp;
|
|
59
|
-
}
|
|
129
|
+
function resolveClientIp(request) {
|
|
130
|
+
return resolveClientIpWithDetails(request).ip || '';
|
|
131
|
+
}
|
|
60
132
|
|
|
61
133
|
/**
|
|
62
134
|
* Resolve IP from a raw TCP socket (no HTTP headers available).
|
|
63
135
|
* Normalizes IPv6-mapped IPv4 addresses.
|
|
64
136
|
*/
|
|
65
|
-
function resolveSocketIp(socket) {
|
|
66
|
-
const raw = socket?.remoteAddress || '';
|
|
67
|
-
return raw
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
module.exports = {
|
|
71
|
-
resolveClientIp,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
137
|
+
function resolveSocketIp(socket) {
|
|
138
|
+
const raw = socket?.remoteAddress || '';
|
|
139
|
+
return normalizeIp(raw);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
resolveClientIp,
|
|
144
|
+
resolveClientIpWithDetails,
|
|
145
|
+
resolveSocketIp,
|
|
146
|
+
isFromTrustedProxy,
|
|
147
|
+
normalizeIp,
|
|
148
|
+
getHostIps,
|
|
149
|
+
LOOPBACK_RE,
|
|
150
|
+
PRIVATE_IP_RE,
|
|
151
|
+
};
|
package/tracing.js
CHANGED
|
@@ -343,16 +343,37 @@ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveField
|
|
|
343
343
|
const captureMultipart = !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_MULTIPART') ?? ''));
|
|
344
344
|
|
|
345
345
|
// -------- Trusted proxy IP resolution --------
|
|
346
|
-
const {
|
|
346
|
+
const { resolveClientIpWithDetails } = require('./resolve-ip');
|
|
347
347
|
|
|
348
348
|
// Configure HTTP instrumentation with body capture
|
|
349
349
|
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
|
|
350
350
|
const httpInstrumentation = new HttpInstrumentation({
|
|
351
351
|
requestHook: (span, request) => {
|
|
352
352
|
try {
|
|
353
|
-
const
|
|
354
|
-
if (
|
|
355
|
-
span.setAttribute('http.client_ip',
|
|
353
|
+
const ipDetails = resolveClientIpWithDetails(request);
|
|
354
|
+
if (ipDetails.ip) {
|
|
355
|
+
span.setAttribute('http.client_ip', ipDetails.ip);
|
|
356
|
+
span.setAttribute('http.client_ip.source', ipDetails.source);
|
|
357
|
+
span.setAttribute('http.socket_ip', ipDetails.socketIp || '');
|
|
358
|
+
span.setAttribute('http.proxy.trusted', String(!!ipDetails.trustedProxy));
|
|
359
|
+
if (ipDetails.forwardedFor) {
|
|
360
|
+
span.setAttribute('http.forwarded_for', ipDetails.forwardedFor);
|
|
361
|
+
span.setAttribute('http.request.header.x_forwarded_for', ipDetails.forwardedFor);
|
|
362
|
+
}
|
|
363
|
+
if (ipDetails.realIp) {
|
|
364
|
+
span.setAttribute('http.real_ip', ipDetails.realIp);
|
|
365
|
+
span.setAttribute('http.request.header.x_real_ip', ipDetails.realIp);
|
|
366
|
+
}
|
|
367
|
+
if (ipDetails.cfConnectingIp) {
|
|
368
|
+
span.setAttribute('http.cf.connecting_ip', ipDetails.cfConnectingIp);
|
|
369
|
+
span.setAttribute('http.request.header.cf_connecting_ip', ipDetails.cfConnectingIp);
|
|
370
|
+
}
|
|
371
|
+
if (ipDetails.trueClientIp) {
|
|
372
|
+
span.setAttribute('http.request.header.true_client_ip', ipDetails.trueClientIp);
|
|
373
|
+
}
|
|
374
|
+
if (ipDetails.clientIp) {
|
|
375
|
+
span.setAttribute('http.request.header.x_client_ip', ipDetails.clientIp);
|
|
376
|
+
}
|
|
356
377
|
}
|
|
357
378
|
|
|
358
379
|
if (request.headers) {
|