securenow 5.18.0 → 5.19.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/security.js CHANGED
@@ -861,88 +861,6 @@ async function ipTraces(args, flags) {
861
861
  }
862
862
  }
863
863
 
864
- // ── API Map ──
865
-
866
- async function apiMapList(args, flags) {
867
- requireAuth();
868
- const s = ui.spinner('Fetching API map');
869
- try {
870
- const data = await api.get('/api-map');
871
- const apiMap = data.apiMap;
872
- s.stop('API map loaded');
873
-
874
- if (flags.json) { ui.json(data); return; }
875
-
876
- if (!apiMap) {
877
- console.log('');
878
- ui.info(data.message || 'No API map discovered yet. Run discovery from the dashboard.');
879
- console.log('');
880
- return;
881
- }
882
-
883
- console.log('');
884
- if (apiMap.apps && typeof apiMap.apps === 'object') {
885
- for (const [appName, appData] of Object.entries(apiMap.apps)) {
886
- ui.subheading(appName);
887
- console.log('');
888
- const endpoints = appData.endpoints || [];
889
- if (endpoints.length) {
890
- const rows = endpoints.map(e => [
891
- e.method || '—',
892
- e.path || e.route || '—',
893
- e.requestCount != null ? String(e.requestCount) : '—',
894
- e.description || ui.c.dim('—'),
895
- ]);
896
- ui.table(['Method', 'Path', 'Requests', 'Description'], rows);
897
- } else {
898
- ui.info('No endpoints discovered for this app.');
899
- }
900
- console.log('');
901
- }
902
- } else {
903
- ui.json(apiMap);
904
- }
905
- } catch (err) {
906
- s.fail('Failed to fetch API map');
907
- throw err;
908
- }
909
- }
910
-
911
- async function apiMapStats(args, flags) {
912
- requireAuth();
913
- const s = ui.spinner('Fetching API map stats');
914
- try {
915
- const data = await api.get('/api-map/stats');
916
- const stats = data.stats;
917
- s.stop('Stats loaded');
918
-
919
- if (flags.json) { ui.json(stats); return; }
920
-
921
- if (!stats) {
922
- console.log('');
923
- ui.info('No API map stats available.');
924
- console.log('');
925
- return;
926
- }
927
-
928
- console.log('');
929
- ui.heading('API Map Statistics');
930
- console.log('');
931
- ui.keyValue([
932
- ['Total Apps', String(stats.totalApps ?? '—')],
933
- ['Total Endpoints', String(stats.totalEndpoints ?? '—')],
934
- ['Total Requests', String(stats.totalRequests ?? '—')],
935
- ['Discovery Status', stats.discoveryStatus || '—'],
936
- ['Last Discovered', stats.lastDiscoveredAt ? new Date(stats.lastDiscoveredAt).toLocaleString() : '—'],
937
- ['Version', String(stats.version ?? '—')],
938
- ]);
939
- console.log('');
940
- } catch (err) {
941
- s.fail('Failed to fetch stats');
942
- throw err;
943
- }
944
- }
945
-
946
864
  // ── Instances ──
947
865
 
948
866
  async function instancesList(args, flags) {
@@ -1056,8 +974,6 @@ module.exports = {
1056
974
  forensicsLibrary,
1057
975
  ipLookup,
1058
976
  ipTraces,
1059
- apiMapList,
1060
- apiMapStats,
1061
977
  instancesList,
1062
978
  instancesTest,
1063
979
  analytics,
package/cli.js CHANGED
@@ -202,15 +202,6 @@ const COMMANDS = {
202
202
  },
203
203
  defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
204
204
  },
205
- 'api-map': {
206
- desc: 'View API map',
207
- usage: 'securenow api-map [stats]',
208
- sub: {
209
- list: { desc: 'List discovered API endpoints', run: (a, f) => require('./cli/security').apiMapList(a, f) },
210
- stats: { desc: 'API map statistics', run: (a, f) => require('./cli/security').apiMapStats(a, f) },
211
- },
212
- defaultSub: 'list',
213
- },
214
205
  instances: {
215
206
  desc: 'Manage ClickHouse instances',
216
207
  usage: 'securenow instances <subcommand> [options]',
@@ -353,7 +344,7 @@ function showHelp(commandName) {
353
344
  'Applications': ['apps', 'init', 'status'],
354
345
  'Observe': ['traces', 'logs', 'analytics'],
355
346
  'Detect & Respond': ['notifications', 'alerts', 'fp'],
356
- 'Investigate': ['ip', 'forensics', 'api-map'],
347
+ 'Investigate': ['ip', 'forensics'],
357
348
  'Firewall': ['firewall'],
358
349
  'Remediation': ['blocklist', 'allowlist', 'trusted'],
359
350
  'Settings': ['instances', 'config', 'version'],
package/nextjs.js CHANGED
@@ -367,6 +367,44 @@ function registerSecureNow(options = {}) {
367
367
  },
368
368
  });
369
369
 
370
+ // -------- Guard against OTLP exporter socket errors --------
371
+ // The OTLP HTTP exporter uses keep-alive connections that can be reset by
372
+ // the remote end (ECONNRESET / "socket hang up"). These transient errors
373
+ // sometimes escape as unhandled exceptions or rejections. We catch them
374
+ // here and log at debug level instead of crashing the host app.
375
+ const _TRANSIENT_CODES = new Set(['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN']);
376
+ function _isOtlpTransientError(err) {
377
+ if (!err) return false;
378
+ if (_TRANSIENT_CODES.has(err.code)) return true;
379
+ if (typeof err.message === 'string' && /socket hang up|ECONNRESET/.test(err.message)) return true;
380
+ return false;
381
+ }
382
+ function _looksLikeOtlpStack(err) {
383
+ const s = err && err.stack;
384
+ if (!s) return false;
385
+ return /OTLPTraceExporter|OTLPLogExporter|otlp|exporter.*http|BatchSpanProcessor|BatchLogRecordProcessor/i.test(s)
386
+ || /node:_http_client|ClientRequest|TLSSocket/i.test(s);
387
+ }
388
+ const _diagDebug = (env('OTEL_LOG_LEVEL') || '').toLowerCase() === 'debug';
389
+ process.on('uncaughtException', (err, origin) => {
390
+ if (_isOtlpTransientError(err) && _looksLikeOtlpStack(err)) {
391
+ if (_diagDebug) {
392
+ console.debug('[securenow] Suppressed transient OTLP exporter error (%s): %s', origin, err.message);
393
+ }
394
+ return;
395
+ }
396
+ throw err;
397
+ });
398
+ process.on('unhandledRejection', (reason) => {
399
+ if (_isOtlpTransientError(reason) && _looksLikeOtlpStack(reason)) {
400
+ if (_diagDebug) {
401
+ console.debug('[securenow] Suppressed transient OTLP exporter rejection: %s', reason && reason.message);
402
+ }
403
+ return;
404
+ }
405
+ throw reason;
406
+ });
407
+
370
408
  if (isVercel) {
371
409
  // -------- Vercel Environment: Use @vercel/otel --------
372
410
  const { registerOTel } = require('@vercel/otel');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "5.18.0",
3
+ "version": "5.19.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",
package/tracing.js CHANGED
@@ -258,12 +258,12 @@ function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFiel
258
258
  })();
259
259
 
260
260
  // -------- diagnostics --------
261
+ const diagLevel = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
261
262
  (() => {
262
- const L = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
263
- const level = L === 'debug' ? DiagLogLevel.DEBUG :
264
- L === 'info' ? DiagLogLevel.INFO :
265
- L === 'warn' ? DiagLogLevel.WARN :
266
- L === 'error' ? DiagLogLevel.ERROR : DiagLogLevel.NONE;
263
+ const level = diagLevel === 'debug' ? DiagLogLevel.DEBUG :
264
+ diagLevel === 'info' ? DiagLogLevel.INFO :
265
+ diagLevel === 'warn' ? DiagLogLevel.WARN :
266
+ diagLevel === 'error' ? DiagLogLevel.ERROR : DiagLogLevel.NONE;
267
267
  diag.setLogger(new DiagConsoleLogger(), level);
268
268
  console.log('[securenow] preload loaded pid=%d', process.pid);
269
269
  })();
@@ -506,6 +506,48 @@ if (loggingEnabled) {
506
506
  console.__securenow_patched = true;
507
507
  }
508
508
 
509
+ // -------- Guard against OTLP exporter socket errors --------
510
+ // The OTLP HTTP exporter uses keep-alive connections that can be reset by the
511
+ // remote end (ECONNRESET / "socket hang up"). These transient errors sometimes
512
+ // escape as unhandled exceptions or rejections because the underlying HTTP
513
+ // request's error path isn't fully covered by the OTel library. We install
514
+ // targeted process-level handlers to catch them and log at debug level instead
515
+ // of crashing the host app.
516
+ const _TRANSIENT_CODES = new Set(['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN']);
517
+ function _isOtlpTransientError(err) {
518
+ if (!err) return false;
519
+ if (_TRANSIENT_CODES.has(err.code)) return true;
520
+ if (typeof err.message === 'string' && /socket hang up|ECONNRESET/.test(err.message)) return true;
521
+ return false;
522
+ }
523
+ function _looksLikeOtlpStack(err) {
524
+ const s = err && err.stack;
525
+ if (!s) return false;
526
+ return /OTLPTraceExporter|OTLPLogExporter|otlp|exporter.*http|BatchSpanProcessor|BatchLogRecordProcessor/i.test(s)
527
+ || /node:_http_client|ClientRequest|TLSSocket/i.test(s);
528
+ }
529
+
530
+ process.on('uncaughtException', (err, origin) => {
531
+ if (_isOtlpTransientError(err) && _looksLikeOtlpStack(err)) {
532
+ if (diagLevel === 'debug') {
533
+ console.debug('[securenow] Suppressed transient OTLP exporter error (%s): %s', origin, err.message);
534
+ }
535
+ return; // swallow — do not crash
536
+ }
537
+ // Not ours — re-throw so the default handler (or the app's own handler) fires
538
+ throw err;
539
+ });
540
+ process.on('unhandledRejection', (reason) => {
541
+ if (_isOtlpTransientError(reason) && _looksLikeOtlpStack(reason)) {
542
+ if (diagLevel === 'debug') {
543
+ console.debug('[securenow] Suppressed transient OTLP exporter rejection: %s', reason && reason.message);
544
+ }
545
+ return; // swallow
546
+ }
547
+ // Not ours — re-throw as unhandled so Node's default behaviour applies
548
+ throw reason;
549
+ });
550
+
509
551
  // -------- SDK --------
510
552
  const traceExporter = new OTLPTraceExporter({ url: tracesUrl, headers });
511
553
  const sdk = new NodeSDK({