securenow 7.6.6 → 7.6.8
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 +13 -13
- package/README.md +21 -37
- package/app-config.js +5 -3
- package/cli/config.js +4 -3
- package/cli/diagnostics.js +54 -15
- package/cli/run.js +40 -11
- package/firewall-only.js +1 -1
- package/firewall.js +88 -57
- package/mcp/catalog.js +1 -1
- package/nextjs-webpack-config.js +3 -15
- package/nextjs.js +21 -23
- package/nuxt-server-plugin.mjs +20 -10
- package/package.json +33 -34
- package/register.js +1 -1
- package/tracing.js +17 -7
- package/web-vite.mjs +23 -13
- package/CONSUMING-APPS-GUIDE.md +0 -463
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +0 -1388
- package/docs/API-KEYS-GUIDE.md +0 -278
- package/docs/ARCHITECTURE.md +0 -408
- package/docs/AUTO-BODY-CAPTURE.md +0 -412
- package/docs/AUTO-SETUP-SUMMARY.md +0 -331
- package/docs/AUTO-SETUP.md +0 -419
- package/docs/AUTOMATIC-IP-CAPTURE.md +0 -359
- package/docs/BODY-CAPTURE-FIX.md +0 -261
- package/docs/BODY-CAPTURE-QUICKSTART.md +0 -147
- package/docs/CHANGELOG-NEXTJS.md +0 -235
- package/docs/COMPLETION-REPORT.md +0 -408
- package/docs/CUSTOMER-GUIDE.md +0 -364
- package/docs/EASIEST-SETUP.md +0 -342
- package/docs/ENVIRONMENT-VARIABLES.md +0 -166
- package/docs/ENVIRONMENTS.md +0 -60
- package/docs/EXPRESS-BODY-CAPTURE.md +0 -1028
- package/docs/EXPRESS-SETUP-GUIDE.md +0 -722
- package/docs/FINAL-SOLUTION.md +0 -335
- package/docs/FIREWALL-GUIDE.md +0 -440
- package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
- package/docs/INDEX.md +0 -222
- package/docs/LOGGING-GUIDE.md +0 -704
- package/docs/LOGGING-QUICKSTART.md +0 -221
- package/docs/MCP-GUIDE.md +0 -58
- package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
- package/docs/NEXTJS-BODY-CAPTURE.md +0 -368
- package/docs/NEXTJS-GUIDE.md +0 -392
- package/docs/NEXTJS-QUICKSTART.md +0 -83
- package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
- package/docs/NEXTJS-WEBPACK-WARNINGS.md +0 -267
- package/docs/NEXTJS-WRAPPER-APPROACH.md +0 -414
- package/docs/NUXT-GUIDE.md +0 -173
- package/docs/QUICKSTART-BODY-CAPTURE.md +0 -293
- package/docs/REDACTION-EXAMPLES.md +0 -484
- package/docs/REQUEST-BODY-CAPTURE.md +0 -587
- package/docs/SOLUTION-SUMMARY.md +0 -312
- package/docs/VERCEL-OTEL-MIGRATION.md +0 -255
- package/examples/README.md +0 -265
- package/examples/express-with-logging.js +0 -137
- package/examples/instrumentation-with-auto-capture.ts +0 -41
- package/examples/next.config.js +0 -37
- package/examples/nextjs-api-route-with-body-capture.ts +0 -54
- package/examples/nextjs-env-example.txt +0 -32
- package/examples/nextjs-instrumentation.js +0 -36
- package/examples/nextjs-instrumentation.ts +0 -36
- package/examples/nextjs-middleware.js +0 -37
- package/examples/nextjs-middleware.ts +0 -37
- package/examples/nextjs-with-logging-example.md +0 -301
- package/examples/nextjs-with-options.ts +0 -36
- package/examples/test-nextjs-setup.js +0 -70
- package/postinstall.js +0 -296
package/firewall.js
CHANGED
|
@@ -45,12 +45,15 @@ let _circuitOpenedAt = 0;
|
|
|
45
45
|
let _pollInflight = false;
|
|
46
46
|
let _retryAfterUntil = 0;
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// Firewall control-plane traffic is low-frequency and correctness-critical.
|
|
49
|
+
// Use non-keep-alive agents so clustered apps do not reuse sockets that an
|
|
50
|
+
// upstream load balancer has silently closed between sync cycles.
|
|
51
|
+
const _httpAgent = new http.Agent({ keepAlive: false });
|
|
52
|
+
const _httpsAgent = new https.Agent({ keepAlive: false });
|
|
51
53
|
const EVENT_FLUSH_INTERVAL_MS = 2_000;
|
|
52
54
|
const EVENT_BATCH_SIZE = 25;
|
|
53
55
|
const EVENT_QUEUE_MAX = 1_000;
|
|
56
|
+
const TRANSIENT_NETWORK_CODES = new Set(['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EAI_AGAIN']);
|
|
54
57
|
|
|
55
58
|
// Unified sync uses /firewall/sync (v2). Falls back to legacy on 404.
|
|
56
59
|
let _useUnifiedSync = true;
|
|
@@ -119,67 +122,95 @@ function jitter(baseMs) {
|
|
|
119
122
|
return baseMs * (0.8 + Math.random() * 0.4);
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
function agentFor(url) {
|
|
123
|
-
return url.startsWith('https') ? _httpsAgent : _httpAgent;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function httpGet(url, extraHeaders, timeout, callback) {
|
|
127
|
-
const mod = url.startsWith('https') ? https : http;
|
|
128
|
-
const parsed = new URL(url);
|
|
129
|
-
|
|
130
|
-
const req = mod.request({
|
|
131
|
-
hostname: parsed.hostname,
|
|
132
|
-
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
133
|
-
path: parsed.pathname + parsed.search,
|
|
134
|
-
method: 'GET',
|
|
135
|
-
headers: {
|
|
136
|
-
'Authorization': `Bearer ${_options.apiKey}`,
|
|
137
|
-
'User-Agent': 'securenow-firewall-sdk',
|
|
138
|
-
...extraHeaders,
|
|
139
|
-
},
|
|
140
|
-
timeout,
|
|
141
|
-
agent: agentFor(url),
|
|
142
|
-
}, (res) => {
|
|
143
|
-
let data = '';
|
|
144
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
145
|
-
res.on('end', () => { callback(null, res, data); });
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
req.on('error', (err) => callback(err));
|
|
149
|
-
req.on('timeout', () => { req.destroy(); callback(new Error('Request timed out')); });
|
|
150
|
-
req.end();
|
|
125
|
+
function agentFor(url) {
|
|
126
|
+
return url.startsWith('https') ? _httpsAgent : _httpAgent;
|
|
151
127
|
}
|
|
152
128
|
|
|
153
|
-
function
|
|
129
|
+
function isTransientNetworkError(err) {
|
|
130
|
+
if (!err) return false;
|
|
131
|
+
if (err.code && TRANSIENT_NETWORK_CODES.has(err.code)) return true;
|
|
132
|
+
return /socket hang up|connection reset|ECONNRESET/i.test(String(err.message || ''));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatRequestError(err) {
|
|
136
|
+
if (!err) return 'unknown error';
|
|
137
|
+
const parts = [err.message || String(err)];
|
|
138
|
+
if (err.code && !parts[0].includes(err.code)) parts.push(`code=${err.code}`);
|
|
139
|
+
if (err.syscall) parts.push(`syscall=${err.syscall}`);
|
|
140
|
+
return parts.join(' ');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function requestOnce(method, url, body, extraHeaders, timeout, callback) {
|
|
154
144
|
const mod = url.startsWith('https') ? https : http;
|
|
155
145
|
const parsed = new URL(url);
|
|
156
|
-
const payload = JSON.stringify(body || {});
|
|
146
|
+
const payload = body == null ? null : JSON.stringify(body || {});
|
|
157
147
|
|
|
158
148
|
const req = mod.request({
|
|
159
149
|
hostname: parsed.hostname,
|
|
160
150
|
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
161
151
|
path: parsed.pathname + parsed.search,
|
|
162
|
-
method
|
|
152
|
+
method,
|
|
163
153
|
headers: {
|
|
164
154
|
'Authorization': `Bearer ${_options.apiKey}`,
|
|
165
155
|
'User-Agent': 'securenow-firewall-sdk',
|
|
166
|
-
'
|
|
167
|
-
|
|
156
|
+
'Connection': 'close',
|
|
157
|
+
...(payload
|
|
158
|
+
? {
|
|
159
|
+
'Content-Type': 'application/json',
|
|
160
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
161
|
+
}
|
|
162
|
+
: {}),
|
|
163
|
+
...extraHeaders,
|
|
168
164
|
},
|
|
169
165
|
timeout,
|
|
170
166
|
agent: agentFor(url),
|
|
171
167
|
}, (res) => {
|
|
172
168
|
let data = '';
|
|
173
169
|
res.on('data', (chunk) => { data += chunk; });
|
|
174
|
-
res.on('end', () => {
|
|
170
|
+
res.on('end', () => { done(null, res, data); });
|
|
175
171
|
});
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
173
|
+
let finished = false;
|
|
174
|
+
function done(...args) {
|
|
175
|
+
if (finished) return;
|
|
176
|
+
finished = true;
|
|
177
|
+
callback(...args);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
req.on('error', (err) => done(err));
|
|
181
|
+
req.on('timeout', () => {
|
|
182
|
+
const err = new Error('Request timed out');
|
|
183
|
+
err.code = 'ETIMEDOUT';
|
|
184
|
+
done(err);
|
|
185
|
+
req.destroy(err);
|
|
186
|
+
});
|
|
187
|
+
if (payload) req.write(payload);
|
|
180
188
|
req.end();
|
|
181
189
|
}
|
|
182
190
|
|
|
191
|
+
function requestWithRetry(method, url, body, extraHeaders, timeout, callback) {
|
|
192
|
+
requestOnce(method, url, body, extraHeaders, timeout, (err, res, data) => {
|
|
193
|
+
if (!err || !isTransientNetworkError(err)) return callback(err, res, data);
|
|
194
|
+
|
|
195
|
+
if (_options && _options.log) {
|
|
196
|
+
console.warn('[securenow] Firewall: transient API socket error, retrying once:', formatRequestError(err));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const retryTimer = setTimeout(() => {
|
|
200
|
+
requestOnce(method, url, body, extraHeaders, timeout, callback);
|
|
201
|
+
}, 100);
|
|
202
|
+
if (retryTimer.unref) retryTimer.unref();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function httpGet(url, extraHeaders, timeout, callback) {
|
|
207
|
+
requestWithRetry('GET', url, null, extraHeaders, timeout, callback);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function httpPostJson(url, body, timeout, callback) {
|
|
211
|
+
requestWithRetry('POST', url, body, {}, timeout, callback);
|
|
212
|
+
}
|
|
213
|
+
|
|
183
214
|
function scheduleEventFlush() {
|
|
184
215
|
if (_eventTimer || _eventQueue.length === 0) return;
|
|
185
216
|
_eventTimer = setTimeout(() => {
|
|
@@ -202,7 +233,7 @@ function flushFirewallEvents() {
|
|
|
202
233
|
httpPostJson(url, { events: batch }, 5000, (err, res) => {
|
|
203
234
|
if (err || !res || res.statusCode >= 400) {
|
|
204
235
|
if (_options.log) {
|
|
205
|
-
const msg = err ? err
|
|
236
|
+
const msg = err ? formatRequestError(err) : `API returned ${res.statusCode}`;
|
|
206
237
|
console.warn('[securenow] Firewall: failed to report blocked-request ledger:', msg);
|
|
207
238
|
}
|
|
208
239
|
}
|
|
@@ -448,19 +479,19 @@ function doLegacyPoll(callback) {
|
|
|
448
479
|
callback(null, { blChanged, alChanged });
|
|
449
480
|
}
|
|
450
481
|
|
|
451
|
-
if (blChanged) {
|
|
452
|
-
legacyBlocklistSync((err, changed, stats) => {
|
|
453
|
-
if (err && _options.log) console.warn('[securenow] Firewall: sync failed (using stale list):', err
|
|
454
|
-
else if (changed && stats && _options.log) console.log('[securenow] Firewall: re-synced %d blocked IPs', stats.total);
|
|
455
|
-
syncDone();
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
if (alChanged) {
|
|
459
|
-
legacyAllowlistSync((err, changed, stats) => {
|
|
460
|
-
if (err && _options.log) console.warn('[securenow] Firewall: allowlist sync failed:', err
|
|
461
|
-
else if (changed && stats && _options.log) console.log('[securenow] Firewall: re-synced %d allowed IPs', stats.total);
|
|
462
|
-
syncDone();
|
|
463
|
-
});
|
|
482
|
+
if (blChanged) {
|
|
483
|
+
legacyBlocklistSync((err, changed, stats) => {
|
|
484
|
+
if (err && _options.log) console.warn('[securenow] Firewall: sync failed (using stale list):', formatRequestError(err));
|
|
485
|
+
else if (changed && stats && _options.log) console.log('[securenow] Firewall: re-synced %d blocked IPs', stats.total);
|
|
486
|
+
syncDone();
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (alChanged) {
|
|
490
|
+
legacyAllowlistSync((err, changed, stats) => {
|
|
491
|
+
if (err && _options.log) console.warn('[securenow] Firewall: allowlist sync failed:', formatRequestError(err));
|
|
492
|
+
else if (changed && stats && _options.log) console.log('[securenow] Firewall: re-synced %d allowed IPs', stats.total);
|
|
493
|
+
syncDone();
|
|
494
|
+
});
|
|
464
495
|
}
|
|
465
496
|
}
|
|
466
497
|
|
|
@@ -495,7 +526,7 @@ function pollOnce(callback) {
|
|
|
495
526
|
_consecutiveErrors++;
|
|
496
527
|
_stats.errors++;
|
|
497
528
|
maybeOpenCircuit();
|
|
498
|
-
if (_options.log) console.warn('[securenow] Firewall: poll failed:', err
|
|
529
|
+
if (_options.log) console.warn('[securenow] Firewall: poll failed:', formatRequestError(err));
|
|
499
530
|
return callback(err);
|
|
500
531
|
}
|
|
501
532
|
_consecutiveErrors = 0;
|
|
@@ -565,7 +596,7 @@ function startSyncLoop() {
|
|
|
565
596
|
if (retryTimer.unref) retryTimer.unref();
|
|
566
597
|
return;
|
|
567
598
|
}
|
|
568
|
-
if (_options.log) console.warn('[securenow] Firewall: initial sync failed:', err
|
|
599
|
+
if (_options.log) console.warn('[securenow] Firewall: initial sync failed:', formatRequestError(err));
|
|
569
600
|
if (_options.failMode === 'closed') {
|
|
570
601
|
_matcher = { isBlocked: () => true, stats: () => ({ exact: 0, cidr: 0, total: 0 }) };
|
|
571
602
|
}
|
package/mcp/catalog.js
CHANGED
|
@@ -32,7 +32,7 @@ Runbook:
|
|
|
32
32
|
node -p "require('./node_modules/securenow/package.json').version"
|
|
33
33
|
npx securenow version
|
|
34
34
|
Stop and fix the install if either is below 7.5.1 or npx still resolves an older local package.
|
|
35
|
-
3. Read the installed package surface before editing files: node_modules/securenow/package.json, README/NPM_README, SKILL-API, SKILL-CLI,
|
|
35
|
+
3. Read the installed package surface before editing files: node_modules/securenow/package.json, README/NPM_README, SKILL-API, SKILL-CLI, npx securenow help, and relevant subcommand help for login/init/firewall/doctor/env/test-span/log/mcp.
|
|
36
36
|
4. Mandatory auth gate:
|
|
37
37
|
- Run npx securenow whoami from the project root.
|
|
38
38
|
- If not logged in, run npx securenow login from the project root and wait for the browser flow.
|
package/nextjs-webpack-config.js
CHANGED
|
@@ -16,21 +16,9 @@
|
|
|
16
16
|
* module.exports = { webpack: (config, opts) => getSecureNowWebpackConfig(config, opts) };
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
const EXTERNAL_PACKAGES = [
|
|
20
|
-
'securenow',
|
|
21
|
-
|
|
22
|
-
'@opentelemetry/auto-instrumentations-node',
|
|
23
|
-
'@opentelemetry/instrumentation-http',
|
|
24
|
-
'@opentelemetry/exporter-trace-otlp-http',
|
|
25
|
-
'@opentelemetry/exporter-logs-otlp-http',
|
|
26
|
-
'@opentelemetry/sdk-logs',
|
|
27
|
-
'@opentelemetry/instrumentation',
|
|
28
|
-
'@opentelemetry/resources',
|
|
29
|
-
'@opentelemetry/semantic-conventions',
|
|
30
|
-
'@opentelemetry/api',
|
|
31
|
-
'@opentelemetry/api-logs',
|
|
32
|
-
'@vercel/otel',
|
|
33
|
-
];
|
|
19
|
+
const EXTERNAL_PACKAGES = [
|
|
20
|
+
'securenow',
|
|
21
|
+
];
|
|
34
22
|
|
|
35
23
|
function detectNextMajor() {
|
|
36
24
|
try {
|
package/nextjs.js
CHANGED
|
@@ -5,31 +5,19 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage in Next.js app:
|
|
7
7
|
*
|
|
8
|
-
* 1. Add
|
|
8
|
+
* 1. Add securenow to serverExternalPackages in next.config.js:
|
|
9
9
|
*
|
|
10
10
|
* const nextConfig = {
|
|
11
|
-
* serverExternalPackages: [
|
|
12
|
-
* "securenow",
|
|
13
|
-
* "@opentelemetry/sdk-node",
|
|
14
|
-
* "@opentelemetry/auto-instrumentations-node",
|
|
15
|
-
* "@opentelemetry/instrumentation-http",
|
|
16
|
-
* "@opentelemetry/exporter-trace-otlp-http",
|
|
17
|
-
* "@opentelemetry/exporter-logs-otlp-http",
|
|
18
|
-
* "@opentelemetry/sdk-logs",
|
|
19
|
-
* "@opentelemetry/instrumentation",
|
|
20
|
-
* "@opentelemetry/resources",
|
|
21
|
-
* "@opentelemetry/semantic-conventions",
|
|
22
|
-
* "@opentelemetry/api",
|
|
23
|
-
* "@opentelemetry/api-logs",
|
|
24
|
-
* "@vercel/otel",
|
|
25
|
-
* ],
|
|
11
|
+
* serverExternalPackages: ["securenow"],
|
|
26
12
|
* };
|
|
27
13
|
*
|
|
28
14
|
* 2. Create instrumentation.ts (or .js) in your project root:
|
|
29
15
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
16
|
+
* export async function register() {
|
|
17
|
+
* if (process.env.NEXT_RUNTIME !== "nodejs") return;
|
|
18
|
+
* const securenowNext = await import("securenow/nextjs");
|
|
19
|
+
* securenowNext.registerSecureNow({ captureBody: true });
|
|
20
|
+
* await import("securenow/nextjs-auto-capture");
|
|
33
21
|
* }
|
|
34
22
|
*
|
|
35
23
|
* 3. Run `npx securenow login` and `npx securenow init`.
|
|
@@ -39,11 +27,22 @@
|
|
|
39
27
|
|
|
40
28
|
const { randomUUID } = require('crypto');
|
|
41
29
|
const appConfig = require('./app-config');
|
|
30
|
+
const otelResources = require('@opentelemetry/resources');
|
|
42
31
|
|
|
43
32
|
const env = appConfig.env;
|
|
44
33
|
|
|
45
34
|
let isRegistered = false;
|
|
46
35
|
|
|
36
|
+
function createResource(attributes) {
|
|
37
|
+
if (typeof otelResources.resourceFromAttributes === 'function') {
|
|
38
|
+
return otelResources.resourceFromAttributes(attributes);
|
|
39
|
+
}
|
|
40
|
+
if (typeof otelResources.Resource === 'function') {
|
|
41
|
+
return new otelResources.Resource(attributes);
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Unsupported @opentelemetry/resources version');
|
|
44
|
+
}
|
|
45
|
+
|
|
47
46
|
// Default sensitive fields to redact from request bodies
|
|
48
47
|
const DEFAULT_SENSITIVE_FIELDS = [
|
|
49
48
|
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
@@ -430,7 +429,6 @@ function registerSecureNow(options = {}) {
|
|
|
430
429
|
// -------- Self-Hosted (EC2/PM2): Use Vanilla OpenTelemetry SDK --------
|
|
431
430
|
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
|
432
431
|
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
433
|
-
const { Resource } = require('@opentelemetry/resources');
|
|
434
432
|
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
|
|
435
433
|
|
|
436
434
|
const traceExporter = new OTLPTraceExporter({
|
|
@@ -442,7 +440,7 @@ function registerSecureNow(options = {}) {
|
|
|
442
440
|
serviceName: serviceName,
|
|
443
441
|
traceExporter: traceExporter,
|
|
444
442
|
instrumentations: [httpInstrumentation],
|
|
445
|
-
resource:
|
|
443
|
+
resource: createResource({
|
|
446
444
|
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
447
445
|
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: deploymentEnvironment,
|
|
448
446
|
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version || undefined,
|
|
@@ -466,12 +464,12 @@ function registerSecureNow(options = {}) {
|
|
|
466
464
|
});
|
|
467
465
|
|
|
468
466
|
const loggerProvider = new LoggerProvider({
|
|
469
|
-
resource:
|
|
467
|
+
resource: createResource({
|
|
470
468
|
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
471
469
|
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: deploymentEnvironment,
|
|
472
470
|
}),
|
|
471
|
+
processors: [new BatchLogRecordProcessor(logExporter)],
|
|
473
472
|
});
|
|
474
|
-
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
|
|
475
473
|
|
|
476
474
|
// Patch console to forward logs as OTLP log records
|
|
477
475
|
const logger = loggerProvider.getLogger('console', '1.0.0');
|
package/nuxt-server-plugin.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
11
11
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
12
|
-
import
|
|
12
|
+
import * as otelResources from '@opentelemetry/resources';
|
|
13
13
|
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
|
14
14
|
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
15
15
|
import {
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
trace as otelTrace,
|
|
18
18
|
SpanStatusCode,
|
|
19
19
|
} from '@opentelemetry/api';
|
|
20
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
21
20
|
import { createRequire } from 'node:module';
|
|
21
|
+
import { randomUUID } from 'node:crypto';
|
|
22
22
|
|
|
23
23
|
const nodeRequire = createRequire(import.meta.url);
|
|
24
24
|
const appConfig = nodeRequire('./app-config');
|
|
@@ -27,6 +27,16 @@ const appConfig = nodeRequire('./app-config');
|
|
|
27
27
|
|
|
28
28
|
const env = appConfig.env;
|
|
29
29
|
|
|
30
|
+
function createResource(attributes) {
|
|
31
|
+
if (typeof otelResources.resourceFromAttributes === 'function') {
|
|
32
|
+
return otelResources.resourceFromAttributes(attributes);
|
|
33
|
+
}
|
|
34
|
+
if (typeof otelResources.Resource === 'function') {
|
|
35
|
+
return new otelResources.Resource(attributes);
|
|
36
|
+
}
|
|
37
|
+
throw new Error('Unsupported @opentelemetry/resources version');
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
const DEFAULT_SENSITIVE_FIELDS = [
|
|
31
41
|
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
32
42
|
'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
|
|
@@ -79,9 +89,9 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
79
89
|
|
|
80
90
|
let serviceName;
|
|
81
91
|
if (baseName) {
|
|
82
|
-
serviceName = noUuid ? baseName : `${baseName}-${
|
|
92
|
+
serviceName = noUuid ? baseName : `${baseName}-${randomUUID()}`;
|
|
83
93
|
} else {
|
|
84
|
-
serviceName = `nuxt-app-${
|
|
94
|
+
serviceName = `nuxt-app-${randomUUID()}`;
|
|
85
95
|
console.warn(
|
|
86
96
|
'[securenow] ⚠️ No app identity resolved. Using fallback: %s',
|
|
87
97
|
serviceName,
|
|
@@ -91,7 +101,7 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
91
101
|
);
|
|
92
102
|
}
|
|
93
103
|
|
|
94
|
-
const serviceInstanceId = `${baseName || 'securenow'}-${
|
|
104
|
+
const serviceInstanceId = `${baseName || 'securenow'}-${randomUUID()}`;
|
|
95
105
|
|
|
96
106
|
// ── Endpoints ──
|
|
97
107
|
const resolvedEndpoints = appConfig.resolveEndpoints({ endpoint: opts.endpoint || resolvedApp.instance });
|
|
@@ -101,7 +111,7 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
101
111
|
const headers = resolvedEndpoints.headers;
|
|
102
112
|
|
|
103
113
|
// ── Resource ──
|
|
104
|
-
const resource =
|
|
114
|
+
const resource = createResource({
|
|
105
115
|
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
106
116
|
[SemanticResourceAttributes.SERVICE_INSTANCE_ID]: serviceInstanceId,
|
|
107
117
|
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: deploymentEnvironment,
|
|
@@ -237,10 +247,10 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
237
247
|
);
|
|
238
248
|
|
|
239
249
|
const logExporter = new OTLPLogExporter({ url: logsUrl, headers });
|
|
240
|
-
loggerProvider = new LoggerProvider({
|
|
241
|
-
|
|
242
|
-
new BatchLogRecordProcessor(logExporter),
|
|
243
|
-
);
|
|
250
|
+
loggerProvider = new LoggerProvider({
|
|
251
|
+
resource,
|
|
252
|
+
processors: [new BatchLogRecordProcessor(logExporter)],
|
|
253
|
+
});
|
|
244
254
|
|
|
245
255
|
const logger = loggerProvider.getLogger('console', '1.0.0');
|
|
246
256
|
const SEV = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "7.6.
|
|
3
|
+
"version": "7.6.8",
|
|
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",
|
|
@@ -9,9 +9,6 @@
|
|
|
9
9
|
"securenow": "cli.js",
|
|
10
10
|
"securenow-mcp": "mcp/server.js"
|
|
11
11
|
},
|
|
12
|
-
"scripts": {
|
|
13
|
-
"postinstall": "node postinstall.js || exit 0"
|
|
14
|
-
},
|
|
15
12
|
"keywords": [
|
|
16
13
|
"opentelemetry",
|
|
17
14
|
"otel",
|
|
@@ -135,47 +132,49 @@
|
|
|
135
132
|
"firewall-tcp.js",
|
|
136
133
|
"firewall-iptables.js",
|
|
137
134
|
"firewall-cloud.js",
|
|
138
|
-
"postinstall.js",
|
|
139
135
|
"register-vite.js",
|
|
140
136
|
"web-vite.mjs",
|
|
141
137
|
"app-config.js",
|
|
142
|
-
"examples/",
|
|
143
|
-
"docs/",
|
|
144
138
|
"README.md",
|
|
145
139
|
"NPM_README.md",
|
|
146
|
-
"CONSUMING-APPS-GUIDE.md",
|
|
147
140
|
"SKILL-CLI.md",
|
|
148
141
|
"SKILL-API.md"
|
|
149
142
|
],
|
|
150
143
|
"dependencies": {
|
|
151
|
-
"@opentelemetry/api": "1.
|
|
152
|
-
"@opentelemetry/api-logs": "0.
|
|
153
|
-
"@opentelemetry/auto-instrumentations-node": "0.
|
|
154
|
-
"@opentelemetry/
|
|
155
|
-
"@opentelemetry/exporter-
|
|
156
|
-
"@opentelemetry/
|
|
157
|
-
"@opentelemetry/instrumentation
|
|
158
|
-
"@opentelemetry/instrumentation-
|
|
159
|
-
"@opentelemetry/instrumentation-
|
|
160
|
-
"@opentelemetry/instrumentation-
|
|
161
|
-
"@opentelemetry/instrumentation-
|
|
162
|
-
"@opentelemetry/instrumentation-
|
|
163
|
-
"@opentelemetry/
|
|
164
|
-
"@opentelemetry/
|
|
165
|
-
"@opentelemetry/sdk-
|
|
166
|
-
"@opentelemetry/sdk-
|
|
167
|
-
"@opentelemetry/
|
|
168
|
-
"
|
|
169
|
-
"
|
|
144
|
+
"@opentelemetry/api": "1.9.1",
|
|
145
|
+
"@opentelemetry/api-logs": "0.218.0",
|
|
146
|
+
"@opentelemetry/auto-instrumentations-node": "0.76.0",
|
|
147
|
+
"@opentelemetry/core": "2.7.1",
|
|
148
|
+
"@opentelemetry/exporter-logs-otlp-http": "0.218.0",
|
|
149
|
+
"@opentelemetry/exporter-trace-otlp-http": "0.218.0",
|
|
150
|
+
"@opentelemetry/instrumentation": "0.218.0",
|
|
151
|
+
"@opentelemetry/instrumentation-document-load": "0.63.0",
|
|
152
|
+
"@opentelemetry/instrumentation-fetch": "0.218.0",
|
|
153
|
+
"@opentelemetry/instrumentation-http": "0.218.0",
|
|
154
|
+
"@opentelemetry/instrumentation-mongodb": "0.71.0",
|
|
155
|
+
"@opentelemetry/instrumentation-user-interaction": "0.62.0",
|
|
156
|
+
"@opentelemetry/instrumentation-xml-http-request": "0.218.0",
|
|
157
|
+
"@opentelemetry/resources": "2.7.1",
|
|
158
|
+
"@opentelemetry/sdk-logs": "0.218.0",
|
|
159
|
+
"@opentelemetry/sdk-metrics": "2.7.1",
|
|
160
|
+
"@opentelemetry/sdk-node": "0.218.0",
|
|
161
|
+
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
162
|
+
"@opentelemetry/sdk-trace-web": "2.7.1",
|
|
163
|
+
"@opentelemetry/semantic-conventions": "1.41.1",
|
|
164
|
+
"dotenv": "17.2.1"
|
|
170
165
|
},
|
|
171
166
|
"optionalDependencies": {
|
|
172
|
-
"@vercel/otel": "1.
|
|
173
|
-
},
|
|
174
|
-
"overrides": {
|
|
175
|
-
"@opentelemetry/api": "1.7.0",
|
|
176
|
-
"@opentelemetry/api-logs": "0.47.0",
|
|
177
|
-
"protobufjs": "^7.5.5"
|
|
167
|
+
"@vercel/otel": "2.1.2"
|
|
178
168
|
},
|
|
179
169
|
"sideEffects": true,
|
|
180
|
-
"license": "ISC"
|
|
170
|
+
"license": "ISC",
|
|
171
|
+
"directories": {
|
|
172
|
+
"doc": "docs",
|
|
173
|
+
"example": "examples"
|
|
174
|
+
},
|
|
175
|
+
"devDependencies": {},
|
|
176
|
+
"scripts": {
|
|
177
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
178
|
+
},
|
|
179
|
+
"author": ""
|
|
181
180
|
}
|
package/register.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// 1. Load dotenv quietly only for legacy installs. Normal local and production
|
|
10
10
|
// configuration comes from .securenow/credentials.json via app-config.js.
|
|
11
11
|
try {
|
|
12
|
-
require('dotenv').config();
|
|
12
|
+
require('dotenv').config({ quiet: true });
|
|
13
13
|
} catch (e) {
|
|
14
14
|
// dotenv is optional.
|
|
15
15
|
}
|
package/tracing.js
CHANGED
|
@@ -33,15 +33,25 @@ const { NodeSDK } = require('@opentelemetry/sdk-node');
|
|
|
33
33
|
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
34
34
|
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
|
|
35
35
|
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
|
|
36
|
-
const
|
|
36
|
+
const otelResources = require('@opentelemetry/resources');
|
|
37
37
|
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
|
|
38
38
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
|
|
39
39
|
const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb');
|
|
40
|
-
const {
|
|
40
|
+
const { randomUUID } = require('crypto');
|
|
41
41
|
const appConfig = require('./app-config');
|
|
42
42
|
|
|
43
43
|
const env = appConfig.env;
|
|
44
44
|
|
|
45
|
+
function createResource(attributes) {
|
|
46
|
+
if (typeof otelResources.resourceFromAttributes === 'function') {
|
|
47
|
+
return otelResources.resourceFromAttributes(attributes);
|
|
48
|
+
}
|
|
49
|
+
if (typeof otelResources.Resource === 'function') {
|
|
50
|
+
return new otelResources.Resource(attributes);
|
|
51
|
+
}
|
|
52
|
+
throw new Error('Unsupported @opentelemetry/resources version');
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
// Default sensitive fields to redact from request bodies
|
|
46
56
|
const DEFAULT_SENSITIVE_FIELDS = [
|
|
47
57
|
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
@@ -297,15 +307,15 @@ if (!baseName && inPm2Cluster && strict) {
|
|
|
297
307
|
// service.name
|
|
298
308
|
let serviceName;
|
|
299
309
|
if (baseName) {
|
|
300
|
-
serviceName = noUuid ? baseName : `${baseName}-${
|
|
310
|
+
serviceName = noUuid ? baseName : `${baseName}-${randomUUID()}`;
|
|
301
311
|
} else {
|
|
302
312
|
// last-resort fallback (only if STRlCT is off). You can rename this to make it obvious in monitoring.
|
|
303
|
-
serviceName = `securenow-free-${
|
|
313
|
+
serviceName = `securenow-free-${randomUUID()}`;
|
|
304
314
|
}
|
|
305
315
|
|
|
306
316
|
// service.instance.id = <appid-or-fallback>-<uuid> (unique per worker)
|
|
307
317
|
const instancePrefix = baseName || 'securenow';
|
|
308
|
-
const serviceInstanceId = `${instancePrefix}-${
|
|
318
|
+
const serviceInstanceId = `${instancePrefix}-${randomUUID()}`;
|
|
309
319
|
|
|
310
320
|
// Loud line per worker to prove what was used
|
|
311
321
|
console.log('[securenow] pid=%d appId=%s instance=%s apiKey=%s → service.name=%s instance.id=%s',
|
|
@@ -466,7 +476,7 @@ const httpInstrumentation = new HttpInstrumentation({
|
|
|
466
476
|
const loggingEnabled = !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLED') ?? ''));
|
|
467
477
|
|
|
468
478
|
// Create shared resource for both traces and logs
|
|
469
|
-
const sharedResource =
|
|
479
|
+
const sharedResource = createResource({
|
|
470
480
|
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
471
481
|
[SemanticResourceAttributes.SERVICE_INSTANCE_ID]: serviceInstanceId,
|
|
472
482
|
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: appConfig.resolveDeploymentEnvironment(),
|
|
@@ -485,8 +495,8 @@ if (loggingEnabled) {
|
|
|
485
495
|
const batchLogProcessor = new BatchLogRecordProcessor(logExporter);
|
|
486
496
|
loggerProvider = new LoggerProvider({
|
|
487
497
|
resource: sharedResource,
|
|
498
|
+
processors: [batchLogProcessor],
|
|
488
499
|
});
|
|
489
|
-
loggerProvider.addLogRecordProcessor(batchLogProcessor);
|
|
490
500
|
|
|
491
501
|
// Auto-patch console.* so every log/warn/error becomes an OTel log record
|
|
492
502
|
const _logger = loggerProvider.getLogger('console', '1.0.0');
|