securenow 8.11.1 → 8.11.3
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 +3 -1
- package/README.md +2 -0
- package/SKILL-API.md +3 -1
- package/SKILL-CLI.md +625 -632
- package/TELEMETRY-SQL.md +6 -1
- package/cidr.js +83 -83
- package/cli/client.js +102 -102
- package/cli/diagnostics.js +4 -0
- package/cli/firewall.js +141 -141
- package/cli/fp.js +638 -638
- package/cli/monitor.js +398 -398
- package/cli/run.js +132 -132
- package/cli/security.js +1866 -1856
- package/cli/ui.js +374 -374
- package/cli.js +923 -922
- package/firewall-cloud.js +202 -202
- package/firewall-iptables.js +139 -139
- package/firewall-only.js +46 -46
- package/firewall-tcp.js +74 -74
- package/firewall.js +1470 -1470
- package/legacy-semconv.js +4 -3
- package/mcp/catalog.js +2358 -2346
- package/nextjs-auto-capture.d.ts +29 -29
- package/nextjs-auto-capture.js +280 -274
- package/nextjs-middleware.d.ts +57 -57
- package/nextjs-middleware.js +274 -268
- package/nextjs-webpack-config.js +79 -79
- package/nextjs-wrapper.d.ts +95 -95
- package/nextjs-wrapper.js +240 -234
- package/nextjs.js +19 -12
- package/nuxt-server-plugin.mjs +11 -5
- package/nuxt.mjs +74 -74
- package/otp-redaction.js +99 -0
- package/package.json +3 -2
- package/register.d.ts +42 -42
- package/register.js +44 -44
- package/resolve-ip.js +13 -13
- package/telemetry-schema.json +3 -1
- package/tracing.js +860 -855
package/nuxt-server-plugin.mjs
CHANGED
|
@@ -42,14 +42,18 @@ function createResource(attributes) {
|
|
|
42
42
|
throw new Error('Unsupported @opentelemetry/resources version');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
const { OTP_SENSITIVE_FIELDS, matchesSensitiveField, redactOtpQuery, withOtpRedactionExporter, redactOtpGraphQL } = nodeRequire('./otp-redaction');
|
|
46
|
+
|
|
45
47
|
// Default sensitive fields to redact from request bodies.
|
|
46
|
-
//
|
|
48
|
+
// OTP/code aliases use exact normalized matching; other fields are matched
|
|
49
|
+
// substring-wise against lowercased keys (see redactSensitiveData),
|
|
47
50
|
// so e.g. 'card' also catches 'creditCard' and 'account' also catches
|
|
48
51
|
// 'accountNumber'. Over-redaction here is intentional: a falsely-redacted
|
|
49
52
|
// telemetry value is always safer than a leaked secret. Entries are kept
|
|
50
53
|
// specific enough to avoid nuking broad benign keys (e.g. we use
|
|
51
54
|
// 'firstname'/'lastname'/'fullname', never bare 'name').
|
|
52
55
|
const DEFAULT_SENSITIVE_FIELDS = [
|
|
56
|
+
...OTP_SENSITIVE_FIELDS,
|
|
53
57
|
// credentials / auth
|
|
54
58
|
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
55
59
|
'access_token', 'auth', 'authorization', 'bearer', 'credentials',
|
|
@@ -107,7 +111,10 @@ function luhnValid(digits) {
|
|
|
107
111
|
*/
|
|
108
112
|
function redactSensitiveValue(value) {
|
|
109
113
|
if (typeof value !== 'string' || value.length === 0) return value;
|
|
110
|
-
|
|
114
|
+
// Credential assignments in GraphQL/query strings need field-aware
|
|
115
|
+
// redaction even though a short numeric OTP has no unique value shape.
|
|
116
|
+
value = redactOtpQuery(redactOtpGraphQL(value));
|
|
117
|
+
if (value.length > MAX_VALUE_SCAN_LENGTH) return value; // bound shape scans
|
|
111
118
|
let out = value;
|
|
112
119
|
for (const r of VALUE_REDACTORS) {
|
|
113
120
|
r.re.lastIndex = 0;
|
|
@@ -128,8 +135,7 @@ function redactSensitiveData(obj, fields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
128
135
|
if (!obj || typeof obj !== 'object') return obj;
|
|
129
136
|
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
130
137
|
for (const key of Object.keys(redacted)) {
|
|
131
|
-
|
|
132
|
-
if (fields.some((f) => lower.includes(f.toLowerCase()))) {
|
|
138
|
+
if (matchesSensitiveField(key, fields)) {
|
|
133
139
|
redacted[key] = '[REDACTED]';
|
|
134
140
|
} else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
|
|
135
141
|
redacted[key] = redactSensitiveData(redacted[key], fields);
|
|
@@ -299,7 +305,7 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
299
305
|
}));
|
|
300
306
|
|
|
301
307
|
// ── Trace exporter + SDK ──
|
|
302
|
-
const traceExporter = new OTLPTraceExporter({ url: tracesUrl, headers });
|
|
308
|
+
const traceExporter = withOtpRedactionExporter(new OTLPTraceExporter({ url: tracesUrl, headers }));
|
|
303
309
|
|
|
304
310
|
const sdk = new NodeSDK({
|
|
305
311
|
...nodeSdkTelemetryOptions,
|
package/nuxt.mjs
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SecureNow Nuxt 3 Module
|
|
3
|
-
*
|
|
4
|
-
* Usage in nuxt.config.ts:
|
|
5
|
-
*
|
|
6
|
-
* export default defineNuxtConfig({
|
|
7
|
-
* modules: ['securenow/nuxt'],
|
|
8
|
-
* securenow: { // optional overrides
|
|
9
|
-
* serviceName: 'my-nuxt-app',
|
|
10
|
-
* },
|
|
11
|
-
* });
|
|
12
|
-
*
|
|
13
|
-
* Environment variables (same as all SecureNow integrations):
|
|
1
|
+
/**
|
|
2
|
+
* SecureNow Nuxt 3 Module
|
|
3
|
+
*
|
|
4
|
+
* Usage in nuxt.config.ts:
|
|
5
|
+
*
|
|
6
|
+
* export default defineNuxtConfig({
|
|
7
|
+
* modules: ['securenow/nuxt'],
|
|
8
|
+
* securenow: { // optional overrides
|
|
9
|
+
* serviceName: 'my-nuxt-app',
|
|
10
|
+
* },
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* Environment variables (same as all SecureNow integrations):
|
|
14
14
|
* Configuration is read from .securenow/credentials.json.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { defineNuxtModule, createResolver, addServerPlugin } from '@nuxt/kit';
|
|
18
|
-
|
|
19
|
-
const OTEL_EXTERNALS = [
|
|
20
|
-
'securenow',
|
|
21
|
-
'@opentelemetry/api',
|
|
22
|
-
'@opentelemetry/api-logs',
|
|
23
|
-
'@opentelemetry/sdk-node',
|
|
24
|
-
'@opentelemetry/sdk-logs',
|
|
25
|
-
'@opentelemetry/auto-instrumentations-node',
|
|
26
|
-
'@opentelemetry/instrumentation',
|
|
27
|
-
'@opentelemetry/instrumentation-http',
|
|
28
|
-
'@opentelemetry/exporter-trace-otlp-http',
|
|
29
|
-
'@opentelemetry/exporter-logs-otlp-http',
|
|
30
|
-
'@opentelemetry/resources',
|
|
31
|
-
'@opentelemetry/semantic-conventions',
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
export default defineNuxtModule({
|
|
35
|
-
meta: {
|
|
36
|
-
name: 'securenow',
|
|
37
|
-
configKey: 'securenow',
|
|
38
|
-
compatibility: { nuxt: '>=3.0.0' },
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
defaults: {
|
|
42
|
-
serviceName: undefined,
|
|
43
|
-
endpoint: undefined,
|
|
44
|
-
noUuid: undefined,
|
|
45
|
-
captureBody: undefined,
|
|
46
|
-
logging: undefined,
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
setup(options, nuxt) {
|
|
50
|
-
const { resolve } = createResolver(import.meta.url);
|
|
51
|
-
|
|
52
|
-
// ── Externalize OTel packages so Nitro doesn't bundle them ──
|
|
53
|
-
nuxt.hook('nitro:config', (nitroConfig) => {
|
|
54
|
-
nitroConfig.externals = nitroConfig.externals || {};
|
|
55
|
-
nitroConfig.externals.external = [
|
|
56
|
-
...(nitroConfig.externals.external || []),
|
|
57
|
-
...OTEL_EXTERNALS,
|
|
58
|
-
];
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// ── Pass module options to the server plugin via runtimeConfig ──
|
|
62
|
-
nuxt.options.runtimeConfig.securenow = {
|
|
63
|
-
serviceName: options.serviceName,
|
|
64
|
-
endpoint: options.endpoint,
|
|
65
|
-
noUuid: options.noUuid,
|
|
66
|
-
captureBody: options.captureBody,
|
|
67
|
-
logging: options.logging,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// ── Register Nitro server plugin ──
|
|
71
|
-
addServerPlugin(resolve('./nuxt-server-plugin'));
|
|
72
|
-
|
|
73
|
-
console.log('[securenow] Nuxt module loaded — server plugin registered');
|
|
74
|
-
},
|
|
75
|
-
});
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { defineNuxtModule, createResolver, addServerPlugin } from '@nuxt/kit';
|
|
18
|
+
|
|
19
|
+
const OTEL_EXTERNALS = [
|
|
20
|
+
'securenow',
|
|
21
|
+
'@opentelemetry/api',
|
|
22
|
+
'@opentelemetry/api-logs',
|
|
23
|
+
'@opentelemetry/sdk-node',
|
|
24
|
+
'@opentelemetry/sdk-logs',
|
|
25
|
+
'@opentelemetry/auto-instrumentations-node',
|
|
26
|
+
'@opentelemetry/instrumentation',
|
|
27
|
+
'@opentelemetry/instrumentation-http',
|
|
28
|
+
'@opentelemetry/exporter-trace-otlp-http',
|
|
29
|
+
'@opentelemetry/exporter-logs-otlp-http',
|
|
30
|
+
'@opentelemetry/resources',
|
|
31
|
+
'@opentelemetry/semantic-conventions',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export default defineNuxtModule({
|
|
35
|
+
meta: {
|
|
36
|
+
name: 'securenow',
|
|
37
|
+
configKey: 'securenow',
|
|
38
|
+
compatibility: { nuxt: '>=3.0.0' },
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
defaults: {
|
|
42
|
+
serviceName: undefined,
|
|
43
|
+
endpoint: undefined,
|
|
44
|
+
noUuid: undefined,
|
|
45
|
+
captureBody: undefined,
|
|
46
|
+
logging: undefined,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
setup(options, nuxt) {
|
|
50
|
+
const { resolve } = createResolver(import.meta.url);
|
|
51
|
+
|
|
52
|
+
// ── Externalize OTel packages so Nitro doesn't bundle them ──
|
|
53
|
+
nuxt.hook('nitro:config', (nitroConfig) => {
|
|
54
|
+
nitroConfig.externals = nitroConfig.externals || {};
|
|
55
|
+
nitroConfig.externals.external = [
|
|
56
|
+
...(nitroConfig.externals.external || []),
|
|
57
|
+
...OTEL_EXTERNALS,
|
|
58
|
+
];
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ── Pass module options to the server plugin via runtimeConfig ──
|
|
62
|
+
nuxt.options.runtimeConfig.securenow = {
|
|
63
|
+
serviceName: options.serviceName,
|
|
64
|
+
endpoint: options.endpoint,
|
|
65
|
+
noUuid: options.noUuid,
|
|
66
|
+
captureBody: options.captureBody,
|
|
67
|
+
logging: options.logging,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ── Register Nitro server plugin ──
|
|
71
|
+
addServerPlugin(resolve('./nuxt-server-plugin'));
|
|
72
|
+
|
|
73
|
+
console.log('[securenow] Nuxt module loaded — server plugin registered');
|
|
74
|
+
},
|
|
75
|
+
});
|
package/otp-redaction.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Match credential names exactly after case/separator normalization. Treating
|
|
4
|
+
// "code" as a substring would also hide status_code/error_code forensic data.
|
|
5
|
+
const OTP_SENSITIVE_FIELDS = Object.freeze([
|
|
6
|
+
'code', 'otp', 'otpCode', 'oneTimeCode', 'oneTimePassword', 'passcode',
|
|
7
|
+
'verificationCode', 'confirmationCode', 'emailCode', 'smsCode', 'loginCode',
|
|
8
|
+
'signInCode', 'authCode', 'authenticationCode', 'mfaCode', 'totpCode', '2faCode',
|
|
9
|
+
'securityCode', 'recoveryCode', 'backupCode',
|
|
10
|
+
]);
|
|
11
|
+
const canonical = (key) => String(key).replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
12
|
+
const OTP_NAMES = new Set(OTP_SENSITIVE_FIELDS.map(canonical));
|
|
13
|
+
|
|
14
|
+
function isOtpCodeField(key) {
|
|
15
|
+
let name = String(key);
|
|
16
|
+
// Support individually captured header attributes and nested form keys.
|
|
17
|
+
name = name.replace(/^http\.(?:request|response)\.header[s]?\./i, '');
|
|
18
|
+
const nested = name.match(/(?:\[|\.)([^\[\].]+)\]?$/);
|
|
19
|
+
if (nested) name = nested[1];
|
|
20
|
+
name = name.replace(/^x[-_]/i, '');
|
|
21
|
+
return OTP_NAMES.has(canonical(name));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function matchesSensitiveField(key, fields) {
|
|
25
|
+
if (isOtpCodeField(key)) return true;
|
|
26
|
+
const lower = String(key).toLowerCase();
|
|
27
|
+
return fields.some((field) => (canonical(field) === 'otp' || !isOtpCodeField(field)) && lower.includes(String(field).toLowerCase()));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function redactOtpData(value) {
|
|
31
|
+
if (typeof value === 'string') return redactOtpQuery(redactOtpGraphQL(value));
|
|
32
|
+
if (!value || typeof value !== 'object') return value;
|
|
33
|
+
const out = Array.isArray(value) ? [...value] : { ...value };
|
|
34
|
+
for (const key of Object.keys(out)) {
|
|
35
|
+
out[key] = isOtpCodeField(key) ? '[REDACTED]' : redactOtpData(out[key]);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function redactOtpGraphQL(query) {
|
|
41
|
+
return query.replace(/\b([A-Za-z_][\w]*)(\s*:\s*)("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,})]+)/g,
|
|
42
|
+
(match, key, separator) => isOtpCodeField(key) ? `${key}${separator}"[REDACTED]"` : match);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Preserve the exact bytes of harmless query parameters, including attack
|
|
46
|
+
// payloads. Also covers duplicate and percent-encoded credential keys.
|
|
47
|
+
function redactOtpQuery(value, marker = 'REDACTED') {
|
|
48
|
+
if (typeof value !== 'string' || !value.includes('?')) return value;
|
|
49
|
+
return value.replace(/([?&])([^?&#=\s]+)=([^&#\s]*)/g, (part, separator, rawKey) => {
|
|
50
|
+
let key = rawKey;
|
|
51
|
+
try { key = decodeURIComponent(rawKey.replace(/\+/g, ' ')); } catch (_) {}
|
|
52
|
+
return isOtpCodeField(key) ? `${separator}${rawKey}=${marker}` : part;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function redactOtpAttributes(attributes = {}) {
|
|
57
|
+
const out = { ...attributes };
|
|
58
|
+
for (const [key, value] of Object.entries(out)) {
|
|
59
|
+
if (isOtpCodeField(key)) {
|
|
60
|
+
out[key] = '[REDACTED]';
|
|
61
|
+
} else if (typeof value === 'string') {
|
|
62
|
+
if (/^http\.(request|response)\.(body|headers|multipart\.fields)$/.test(key)) {
|
|
63
|
+
try {
|
|
64
|
+
// Scrub each string value before serializing. Running URL matching on
|
|
65
|
+
// the serialized JSON would consume closing quotes after query values.
|
|
66
|
+
out[key] = JSON.stringify(redactOtpData(JSON.parse(value)));
|
|
67
|
+
continue;
|
|
68
|
+
} catch (_) {}
|
|
69
|
+
}
|
|
70
|
+
out[key] = redactOtpQuery(out[key]);
|
|
71
|
+
// Stable URL semconv stores the query without a leading question mark.
|
|
72
|
+
if (key === 'url.query') out[key] = redactOtpQuery(`?${value}`).slice(1);
|
|
73
|
+
} else if (Array.isArray(value) && /^http\.(request|response)\.header\./.test(key)) {
|
|
74
|
+
out[key] = value.map((item) => redactOtpQuery(item));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Export a view rather than mutating the completed span or the real request.
|
|
81
|
+
// The final pass catches Next.js/Nitro/fetch attributes written after HTTP hooks.
|
|
82
|
+
function withOtpRedactionExporter(exporter) {
|
|
83
|
+
return {
|
|
84
|
+
export(spans, callback) {
|
|
85
|
+
return exporter.export(spans.map((span) => Object.create(span, {
|
|
86
|
+
attributes: { value: redactOtpAttributes(span.attributes), enumerable: true },
|
|
87
|
+
name: { value: redactOtpQuery(span.name), enumerable: true },
|
|
88
|
+
events: { value: (span.events || []).map((event) => ({ ...event, attributes: redactOtpAttributes(event.attributes) })), enumerable: true },
|
|
89
|
+
})), callback);
|
|
90
|
+
},
|
|
91
|
+
shutdown: () => exporter.shutdown(),
|
|
92
|
+
forceFlush: () => typeof exporter.forceFlush === 'function' ? exporter.forceFlush() : Promise.resolve(),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
OTP_SENSITIVE_FIELDS, isOtpCodeField, matchesSensitiveField, redactOtpData,
|
|
98
|
+
redactOtpGraphQL, redactOtpQuery, redactOtpAttributes, withOtpRedactionExporter,
|
|
99
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "8.11.
|
|
3
|
+
"version": "8.11.3",
|
|
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",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"securenow-mcp": "mcp/server.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "node test-app-config.js && node test-resolve-ip.js && node test-otel-defaults.js && node test-telemetry.js && node test-legacy-semconv.js && node test-internal-thread.js && node test-package-contents.js"
|
|
13
|
+
"test": "node test-app-config.js && node test-resolve-ip.js && node test-otel-defaults.js && node test-telemetry.js && node test-legacy-semconv.js && node test-otp-redaction.js && node test-internal-thread.js && node test-package-contents.js"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"opentelemetry",
|
|
@@ -161,6 +161,7 @@
|
|
|
161
161
|
"otel-defaults.js",
|
|
162
162
|
"internal-thread.js",
|
|
163
163
|
"legacy-semconv.js",
|
|
164
|
+
"otp-redaction.js",
|
|
164
165
|
"rate-limits.js",
|
|
165
166
|
"rate-limits.d.ts",
|
|
166
167
|
"firewall-only.js",
|
package/register.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SecureNow Register Module TypeScript Declarations
|
|
3
|
-
*
|
|
4
|
-
* Main entry point for Node.js/Express/Fastify/NestJS applications.
|
|
5
|
-
* This module is automatically loaded when using NODE_OPTIONS="-r securenow/register"
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* SecureNow Register Module TypeScript Declarations
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for Node.js/Express/Fastify/NestJS applications.
|
|
5
|
+
* This module is automatically loaded when using NODE_OPTIONS="-r securenow/register"
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
9
|
* Configuration:
|
|
10
10
|
*
|
|
11
11
|
* Local development reads .securenow/credentials.json. Run
|
|
@@ -17,28 +17,28 @@
|
|
|
17
17
|
* generated JSON to .securenow/credentials.json in the running app.
|
|
18
18
|
*
|
|
19
19
|
* Legacy env vars are fallback-only for existing installs.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
// This module has side effects (initializes OpenTelemetry)
|
|
23
|
-
// It's typically used via NODE_OPTIONS, not imported directly
|
|
24
|
-
export {};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @example
|
|
28
|
-
* ```bash
|
|
29
|
-
* # Using with Node.js preload
|
|
30
|
-
* NODE_OPTIONS="-r securenow/register" node app.js
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```bash
|
|
35
|
-
* # Using with PM2
|
|
36
|
-
* pm2 start app.js --node-args="-r securenow/register"
|
|
37
|
-
* ```
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```javascript
|
|
41
|
-
* // ecosystem.config.js
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// This module has side effects (initializes OpenTelemetry)
|
|
23
|
+
// It's typically used via NODE_OPTIONS, not imported directly
|
|
24
|
+
export {};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @example
|
|
28
|
+
* ```bash
|
|
29
|
+
* # Using with Node.js preload
|
|
30
|
+
* NODE_OPTIONS="-r securenow/register" node app.js
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```bash
|
|
35
|
+
* # Using with PM2
|
|
36
|
+
* pm2 start app.js --node-args="-r securenow/register"
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```javascript
|
|
41
|
+
* // ecosystem.config.js
|
|
42
42
|
* module.exports = {
|
|
43
43
|
* apps: [{
|
|
44
44
|
* name: 'my-app',
|
|
@@ -47,15 +47,15 @@ export {};
|
|
|
47
47
|
* // Local and production use .securenow/credentials.json.
|
|
48
48
|
* }]
|
|
49
49
|
* };
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* // Explicit import (optional, mainly for side effects)
|
|
55
|
-
* import 'securenow/register';
|
|
56
|
-
*
|
|
57
|
-
* import express from 'express';
|
|
58
|
-
* const app = express();
|
|
59
|
-
* // Your app code...
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* // Explicit import (optional, mainly for side effects)
|
|
55
|
+
* import 'securenow/register';
|
|
56
|
+
*
|
|
57
|
+
* import express from 'express';
|
|
58
|
+
* const app = express();
|
|
59
|
+
* // Your app code...
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
package/register.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
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.
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
// Do nothing inside Node's internal ESM-hooks thread (see internal-thread.js).
|
|
10
|
-
if (require('./internal-thread').inInternalThread()) return;
|
|
11
|
-
|
|
12
|
-
// 1. Auto-register the ESM loader hook so customers never need --import
|
|
13
|
-
(() => {
|
|
14
|
-
try {
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
18
|
-
if (!fs.existsSync(pkgPath)) return;
|
|
19
|
-
|
|
20
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8').replace(/^\uFEFF/, ''));
|
|
21
|
-
if (pkg.type !== 'module') return;
|
|
22
|
-
|
|
23
|
-
// Already registered via --import?
|
|
24
|
-
const execArgv = process.execArgv.join(' ');
|
|
25
|
-
if (execArgv.includes('hook.mjs') || execArgv.includes('import-in-the-middle')) return;
|
|
26
|
-
|
|
27
|
-
// Node >=20.6 exposes module.register() for programmatic ESM hooks
|
|
28
|
-
const mod = require('node:module');
|
|
29
|
-
if (typeof mod.register !== 'function') {
|
|
30
|
-
console.warn('[securenow] ESM app detected but Node %s lacks module.register().', process.version);
|
|
31
|
-
console.warn('[securenow] Upgrade to Node >=20.6 or add: --import @opentelemetry/instrumentation/hook.mjs');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const { pathToFileURL } = require('node:url');
|
|
36
|
-
mod.register('@opentelemetry/instrumentation/hook.mjs', pathToFileURL(__filename));
|
|
37
|
-
console.log('[securenow] ESM loader hook auto-registered (module.register)');
|
|
38
|
-
} catch (_) {
|
|
39
|
-
// Non-fatal — tracing.js will show its own ESM warning if the hook is missing
|
|
40
|
-
}
|
|
41
|
-
})();
|
|
42
|
-
|
|
43
|
-
// 2. Run the OTel SDK setup
|
|
44
|
-
require('./tracing');
|
|
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.
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// Do nothing inside Node's internal ESM-hooks thread (see internal-thread.js).
|
|
10
|
+
if (require('./internal-thread').inInternalThread()) return;
|
|
11
|
+
|
|
12
|
+
// 1. Auto-register the ESM loader hook so customers never need --import
|
|
13
|
+
(() => {
|
|
14
|
+
try {
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
18
|
+
if (!fs.existsSync(pkgPath)) return;
|
|
19
|
+
|
|
20
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8').replace(/^\uFEFF/, ''));
|
|
21
|
+
if (pkg.type !== 'module') return;
|
|
22
|
+
|
|
23
|
+
// Already registered via --import?
|
|
24
|
+
const execArgv = process.execArgv.join(' ');
|
|
25
|
+
if (execArgv.includes('hook.mjs') || execArgv.includes('import-in-the-middle')) return;
|
|
26
|
+
|
|
27
|
+
// Node >=20.6 exposes module.register() for programmatic ESM hooks
|
|
28
|
+
const mod = require('node:module');
|
|
29
|
+
if (typeof mod.register !== 'function') {
|
|
30
|
+
console.warn('[securenow] ESM app detected but Node %s lacks module.register().', process.version);
|
|
31
|
+
console.warn('[securenow] Upgrade to Node >=20.6 or add: --import @opentelemetry/instrumentation/hook.mjs');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { pathToFileURL } = require('node:url');
|
|
36
|
+
mod.register('@opentelemetry/instrumentation/hook.mjs', pathToFileURL(__filename));
|
|
37
|
+
console.log('[securenow] ESM loader hook auto-registered (module.register)');
|
|
38
|
+
} catch (_) {
|
|
39
|
+
// Non-fatal — tracing.js will show its own ESM warning if the hook is missing
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
// 2. Run the OTel SDK setup
|
|
44
|
+
require('./tracing');
|
package/resolve-ip.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const appConfig = require('./app-config');
|
|
5
5
|
|
|
@@ -121,20 +121,20 @@ function resolveClientIpWithDetails(request) {
|
|
|
121
121
|
|
|
122
122
|
return details;
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Resolve the real client IP from an HTTP request, respecting trusted proxies.
|
|
127
|
-
* Reads X-Forwarded-For / X-Real-IP only when the direct connection comes
|
|
128
|
-
* from a private/trusted proxy IP. Prevents client-side IP spoofing.
|
|
129
|
-
*/
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the real client IP from an HTTP request, respecting trusted proxies.
|
|
127
|
+
* Reads X-Forwarded-For / X-Real-IP only when the direct connection comes
|
|
128
|
+
* from a private/trusted proxy IP. Prevents client-side IP spoofing.
|
|
129
|
+
*/
|
|
130
130
|
function resolveClientIp(request) {
|
|
131
131
|
return resolveClientIpWithDetails(request).ip || '';
|
|
132
132
|
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Resolve IP from a raw TCP socket (no HTTP headers available).
|
|
136
|
-
* Normalizes IPv6-mapped IPv4 addresses.
|
|
137
|
-
*/
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve IP from a raw TCP socket (no HTTP headers available).
|
|
136
|
+
* Normalizes IPv6-mapped IPv4 addresses.
|
|
137
|
+
*/
|
|
138
138
|
function resolveSocketIp(socket) {
|
|
139
139
|
const raw = socket?.remoteAddress || '';
|
|
140
140
|
return normalizeIp(raw);
|
package/telemetry-schema.json
CHANGED
|
@@ -111,12 +111,14 @@
|
|
|
111
111
|
"userId": "attributes_string['enduser.id']",
|
|
112
112
|
"sessionId": "attributes_string['session.id']",
|
|
113
113
|
"ip": "attributes_string['http.client_ip']",
|
|
114
|
-
"userAgent": "attributes_string['
|
|
114
|
+
"userAgent": "attributes_string['user_agent.original']",
|
|
115
115
|
"severity": "severity_text",
|
|
116
116
|
"attributes.string": "attributes_string['<custom-key>']",
|
|
117
117
|
"attributes.number": "attributes_number['<custom-key>']",
|
|
118
118
|
"attributes.boolean": "attributes_bool['<custom-key>']"
|
|
119
119
|
},
|
|
120
|
+
"reservedAttributePrefixes": ["event.", "enduser.", "session.", "http.", "service.", "securenow.", "client."],
|
|
121
|
+
"reservedAttributeNote": "Custom attribute keys with a reserved prefix are stored as attr.<key>. Events keep the sender-supplied timestamp and carry no event ID. client.asn / client.as_org are added from ip only when the instance has ASN enrichment.",
|
|
120
122
|
"recommendedAuthenticationEvents": [
|
|
121
123
|
"auth.login.failure",
|
|
122
124
|
"auth.login.success",
|