securenow 7.7.2 → 7.7.4

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 CHANGED
@@ -1092,8 +1092,8 @@ On startup, you'll see:
1092
1092
 
1093
1093
  The firewall uses a version-based sync protocol for efficiency:
1094
1094
 
1095
- 1. **Version check** every 10 seconds (lightweight HEAD-like request with ETag)
1096
- 2. **Full blocklist sync** only when the version changes (or every 5 minutes as a safety net)
1095
+ 1. **Version check** every 10 seconds (lightweight request with ETag; unchanged polls return 304 with no body)
1096
+ 2. **Full blocklist sync** only when the version changes (or every hour as a safety net)
1097
1097
  3. **In-memory matching** with a pre-compiled set (exact IPs) and sorted CIDR list for sub-millisecond lookups
1098
1098
  4. **Exponential backoff** with jitter when the API is temporarily unreachable
1099
1099
  5. **Allowlist support** -- trusted IPs are never blocked, even if they appear on the blocklist
@@ -1244,8 +1244,8 @@ Legacy env fallback aliases are listed below for existing installs only.
1244
1244
  |----------|-------------|---------|
1245
1245
  | `SECURENOW_API_KEY` | Legacy firewall key override. Prefer `apiKey` in `.securenow/credentials.json`. | from creds file |
1246
1246
  | `SECURENOW_API_URL` | SecureNow API base URL. Auto-detected for co-located deployments (falls back to `http://localhost:4000` on ECONNREFUSED). | `https://api.securenow.ai` |
1247
- | `SECURENOW_FIREWALL_VERSION_INTERVAL` | Seconds between version checks (lightweight ETag-based). | `10` |
1248
- | `SECURENOW_FIREWALL_SYNC_INTERVAL` | Full blocklist refresh interval in seconds (safety net). | `300` |
1247
+ | `SECURENOW_FIREWALL_VERSION_INTERVAL` | Seconds between lightweight ETag checks. | `10` |
1248
+ | `SECURENOW_FIREWALL_SYNC_INTERVAL` | Safety-net full blocklist refresh interval in seconds. | `3600` |
1249
1249
  | `SECURENOW_FIREWALL_FAIL_MODE` | `open` (allow when unavailable) or `closed` (block all). | `open` |
1250
1250
  | `SECURENOW_FIREWALL_STATUS_CODE` | HTTP status code for blocked requests. | `403` |
1251
1251
  | `SECURENOW_FIREWALL_LOG` | Log blocked requests and sync events to console. Set to `0` to silence. | `1` |
package/SKILL-API.md CHANGED
@@ -314,8 +314,8 @@ const appConfig = require('securenow/app-config');
314
314
  await firewall.init({
315
315
  ...appConfig.resolveFirewallOptions(),
316
316
  apiUrl: 'https://api.securenow.ai',
317
- syncInterval: 300, // full sync every 5 min
318
- versionCheckInterval: 10, // lightweight version check every 10s
317
+ syncInterval: 3600, // safety-net full sync every hour
318
+ versionCheckInterval: 10, // lightweight ETag check every 10s
319
319
  failMode: 'open', // 'open' or 'closed'
320
320
  statusCode: 403,
321
321
  log: true,
@@ -504,8 +504,8 @@ Local development and production use `.securenow/credentials.json`. Every settin
504
504
  |----------|-------------|---------|
505
505
  | `SECURENOW_API_KEY` | Legacy env override for the `apiKey` field (`snk_live_...`). Since v7.5.1, login writes the scoped firewall key to `.securenow/credentials.json`. | - |
506
506
  | `SECURENOW_API_URL` | SecureNow API base URL | `https://api.securenow.ai` |
507
- | `SECURENOW_FIREWALL_VERSION_INTERVAL` | Seconds between lightweight version checks | `10` |
508
- | `SECURENOW_FIREWALL_SYNC_INTERVAL` | Full blocklist refresh interval in seconds | `300` |
507
+ | `SECURENOW_FIREWALL_VERSION_INTERVAL` | Seconds between lightweight ETag checks | `10` |
508
+ | `SECURENOW_FIREWALL_SYNC_INTERVAL` | Safety-net full blocklist refresh interval in seconds | `3600` |
509
509
  | `SECURENOW_FIREWALL_FAIL_MODE` | `open` (allow all when unavailable) or `closed` | `open` |
510
510
  | `SECURENOW_FIREWALL_STATUS_CODE` | HTTP status for blocked requests | `403` |
511
511
  | `SECURENOW_FIREWALL_LOG` | Log blocked requests | `1` |
@@ -515,7 +515,7 @@ Local development and production use `.securenow/credentials.json`. Every settin
515
515
  | `SECURENOW_FIREWALL_CLOUD_DRY_RUN` | `1` to log cloud pushes without applying | `0` |
516
516
  | `SECURENOW_TRUSTED_PROXIES` | Comma-separated trusted proxy IPs | — |
517
517
 
518
- **Resilience:** The firewall SDK includes a circuit breaker (opens after 5 consecutive errors, 2-min cooldown), in-flight request guards (prevents overlapping requests), 429 Retry-After support, and exponential backoff on both version checks and initial sync retries.
518
+ **Resilience:** The firewall SDK includes a circuit breaker (opens after 5 consecutive errors, 2-min cooldown), in-flight request guards (prevents overlapping requests), 429 Retry-After support, and exponential backoff on both lightweight ETag checks and initial sync retries.
519
519
 
520
520
  ### Cloud WAF Provider Variables
521
521
 
package/app-config.js CHANGED
@@ -60,7 +60,7 @@ const DEFAULT_CONFIG = Object.freeze({
60
60
  enabled: true,
61
61
  apiUrl: DEFAULT_API_URL,
62
62
  versionCheckInterval: 10,
63
- syncInterval: 300,
63
+ syncInterval: 3600,
64
64
  failMode: 'open',
65
65
  statusCode: 403,
66
66
  log: true,
@@ -111,8 +111,8 @@ const CONFIG_EXPLANATIONS = Object.freeze({
111
111
  'config.runtime.hideBanner': 'Hide the free-trial response banner when using the managed free-trial collector.',
112
112
  'config.firewall.enabled': 'Secure default: app firewall enforcement starts when apiKey is present and the dashboard toggle is on.',
113
113
  'config.firewall.apiUrl': 'SecureNow API base URL for firewall sync.',
114
- 'config.firewall.versionCheckInterval': 'Seconds between lightweight firewall version checks.',
115
- 'config.firewall.syncInterval': 'Seconds between full firewall blocklist syncs.',
114
+ 'config.firewall.versionCheckInterval': 'Seconds between lightweight firewall version/ETag checks.',
115
+ 'config.firewall.syncInterval': 'Seconds between safety-net full firewall blocklist syncs.',
116
116
  'config.firewall.failMode': 'open allows traffic if SecureNow is temporarily unreachable; closed blocks all on sync failure.',
117
117
  'config.firewall.statusCode': 'HTTP status returned by application-layer firewall blocks.',
118
118
  'config.firewall.log': 'Log firewall decisions locally.',
@@ -680,7 +680,7 @@ function resolveFirewallOptions() {
680
680
  enabled: resolveFirewallEnabled(),
681
681
  apiUrl: env('SECURENOW_API_URL') || DEFAULT_API_URL,
682
682
  versionCheckInterval: numberEnv('SECURENOW_FIREWALL_VERSION_INTERVAL', 10, 1),
683
- syncInterval: numberEnv('SECURENOW_FIREWALL_SYNC_INTERVAL', 300, 1),
683
+ syncInterval: numberEnv('SECURENOW_FIREWALL_SYNC_INTERVAL', 3600, 1),
684
684
  failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
685
685
  statusCode: numberEnv('SECURENOW_FIREWALL_STATUS_CODE', 403, 100),
686
686
  log: boolEnv('SECURENOW_FIREWALL_LOG', true),
@@ -43,13 +43,14 @@ if (console.__securenow_patched) {
43
43
  }
44
44
 
45
45
  // Store original console methods
46
- const originalConsole = {
46
+ const originalConsole = console.__securenow_original || {
47
47
  log: console.log,
48
48
  info: console.info,
49
49
  warn: console.warn,
50
50
  error: console.error,
51
51
  debug: console.debug,
52
52
  };
53
+ if (!console.__securenow_original) console.__securenow_original = originalConsole;
53
54
 
54
55
  // Map severity levels (OpenTelemetry standard)
55
56
  const SeverityNumber = {
package/firewall.js CHANGED
@@ -10,9 +10,10 @@ let _matcher = null;
10
10
  let _syncTimer = null;
11
11
  let _pollTimer = null;
12
12
  let _lastModified = null;
13
- let _lastVersion = null;
14
- let _lastSyncEtag = null;
15
- let _initialized = false;
13
+ let _lastVersion = null;
14
+ let _lastSyncEtag = null;
15
+ let _lastUnifiedEtag = null;
16
+ let _initialized = false;
16
17
  let _consecutiveErrors = 0;
17
18
  let _layers = [];
18
19
  let _rawIps = [];
@@ -21,7 +22,7 @@ let _localhostFallbackTried = false;
21
22
  let _eventQueue = [];
22
23
  let _eventTimer = null;
23
24
 
24
- // Remote toggle set by /firewall/sync when an appKey is in scope. Default
25
+ // Remote toggle - set by /firewall/sync when an appKey is in scope. Default
25
26
  // true so a missing/unreachable backend fails open (matches pre-7.3 behavior).
26
27
  // When the dashboard / CLI flips this off, the next poll suppresses
27
28
  // enforcement without restarting the host process.
@@ -56,9 +57,29 @@ const EVENT_QUEUE_MAX = 1_000;
56
57
  const TRANSIENT_NETWORK_CODES = new Set(['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EAI_AGAIN']);
57
58
 
58
59
  // Unified sync uses /firewall/sync (v2). Falls back to legacy on 404.
59
- let _useUnifiedSync = true;
60
+ let _useUnifiedSync = true;
61
+
62
+ function originalConsoleMethod(method) {
63
+ const originals = console.__securenow_original || console.__securenowOriginalConsole;
64
+ return (originals && originals[method]) || console[method] || console.log;
65
+ }
66
+
67
+ function firewallConsole(method, ...args) {
68
+ if (!_options || !_options.log) return;
69
+ try {
70
+ originalConsoleMethod(method).apply(console, args);
71
+ } catch (_) {}
72
+ }
73
+
74
+ function fwLog(...args) {
75
+ firewallConsole('log', ...args);
76
+ }
77
+
78
+ function fwWarn(...args) {
79
+ firewallConsole('warn', ...args);
80
+ }
60
81
 
61
- // ────── Circuit Breaker ──────
82
+ // Circuit Breaker
62
83
 
63
84
  function maybeOpenCircuit() {
64
85
  if (_circuitState === 'open') return;
@@ -66,7 +87,7 @@ function maybeOpenCircuit() {
66
87
  _circuitState = 'open';
67
88
  _circuitOpenedAt = Date.now();
68
89
  if (_options && _options.log) {
69
- console.warn('[securenow] Firewall: circuit breaker OPEN pausing polling for %ds', CIRCUIT_OPEN_COOLDOWN_MS / 1000);
90
+ fwWarn('[securenow] Firewall: circuit breaker OPEN - pausing polling for %ds', CIRCUIT_OPEN_COOLDOWN_MS / 1000);
70
91
  }
71
92
  }
72
93
  }
@@ -74,7 +95,7 @@ function maybeOpenCircuit() {
74
95
  function resetCircuit() {
75
96
  if (_circuitState !== 'closed') {
76
97
  _circuitState = 'closed';
77
- if (_options && _options.log) console.log('[securenow] Firewall: circuit breaker CLOSED API healthy');
98
+ if (_options && _options.log) fwLog('[securenow] Firewall: circuit breaker CLOSED - API healthy');
78
99
  }
79
100
  _consecutiveErrors = 0;
80
101
  }
@@ -99,12 +120,12 @@ function handleRetryAfter(res) {
99
120
  if (secs > 0 && secs <= 300) {
100
121
  _retryAfterUntil = Date.now() + secs * 1000;
101
122
  if (_options && _options.log) {
102
- console.warn('[securenow] Firewall: API returned 429, backing off for %ds', secs);
123
+ fwWarn('[securenow] Firewall: API returned 429, backing off for %ds', secs);
103
124
  }
104
125
  }
105
126
  }
106
127
 
107
- // ────── HTTP helpers ──────
128
+ // HTTP helpers
108
129
 
109
130
  function buildUrl(apiUrl, path) {
110
131
  return apiUrl.replace(/\/+$/, '') + '/api/v1' + path;
@@ -193,7 +214,7 @@ function requestWithRetry(method, url, body, extraHeaders, timeout, callback) {
193
214
  if (!err || !isTransientNetworkError(err)) return callback(err, res, data);
194
215
 
195
216
  if (_options && _options.log) {
196
- console.warn('[securenow] Firewall: transient API socket error, retrying once:', formatRequestError(err));
217
+ fwWarn('[securenow] Firewall: transient API socket error, retrying once:', formatRequestError(err));
197
218
  }
198
219
 
199
220
  const retryTimer = setTimeout(() => {
@@ -234,7 +255,7 @@ function flushFirewallEvents() {
234
255
  if (err || !res || res.statusCode >= 400) {
235
256
  if (_options.log) {
236
257
  const msg = err ? formatRequestError(err) : `API returned ${res.statusCode}`;
237
- console.warn('[securenow] Firewall: failed to report blocked-request ledger:', msg);
258
+ fwWarn('[securenow] Firewall: failed to report blocked-request ledger:', msg);
238
259
  }
239
260
  }
240
261
  if (_eventQueue.length) scheduleEventFlush();
@@ -262,7 +283,7 @@ function reportFirewallEvent(event) {
262
283
  else scheduleEventFlush();
263
284
  }
264
285
 
265
- // ────── Unified Sync (v2 single request for everything) ──────
286
+ // Unified Sync (v2 - single request for everything)
266
287
 
267
288
  function doUnifiedSync(callback) {
268
289
  const query = new URLSearchParams();
@@ -272,21 +293,23 @@ function doUnifiedSync(callback) {
272
293
  const url = buildUrl(_options.apiUrl, '/firewall/sync') + suffix;
273
294
  const headers = {};
274
295
  if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
275
- if (_lastVersion) headers['X-Blocklist-Version'] = _lastVersion;
276
- if (_lastAllowlistVersion) headers['X-Allowlist-Version'] = _lastAllowlistVersion;
277
-
278
- httpGet(url, headers, 8000, (err, res, data) => {
279
- if (err) return callback(err);
280
-
281
- _stats.versionChecks++;
282
-
283
- if (res.statusCode === 304) {
284
- return callback(null, { blChanged: false, alChanged: false });
296
+ if (_lastVersion) headers['X-Blocklist-Version'] = _lastVersion;
297
+ if (_lastAllowlistVersion) headers['X-Allowlist-Version'] = _lastAllowlistVersion;
298
+ if (_lastUnifiedEtag) headers['If-None-Match'] = _lastUnifiedEtag;
299
+
300
+ httpGet(url, headers, 8000, (err, res, data) => {
301
+ if (err) return callback(err);
302
+
303
+ _stats.versionChecks++;
304
+ if (res.headers && res.headers.etag) _lastUnifiedEtag = res.headers.etag;
305
+
306
+ if (res.statusCode === 304) {
307
+ return callback(null, { blChanged: false, alChanged: false });
285
308
  }
286
309
 
287
310
  if (res.statusCode === 404) {
288
311
  _useUnifiedSync = false;
289
- if (_options.log) console.log('[securenow] Firewall: /sync not available, using legacy endpoints');
312
+ if (_options.log) fwLog('[securenow] Firewall: /sync not available, using legacy endpoints');
290
313
  return callback(null, { blChanged: false, alChanged: false, useLegacy: true });
291
314
  }
292
315
 
@@ -304,30 +327,29 @@ function doUnifiedSync(callback) {
304
327
 
305
328
  // Apply remote per-app toggle. Absent body.app means the backend either
306
329
  // doesn't know about appKey-scoped sync (older API) or no appKey was
307
- // sent leave the previous value untouched (default true on first run).
330
+ // sent - leave the previous value untouched (default true on first run).
308
331
  if (body.app && typeof body.app.firewallEnabled === 'boolean') {
309
332
  const next = body.app.firewallEnabled;
310
333
  if (next !== _lastRemoteEnabled) {
311
334
  _remoteEnabled = next;
312
335
  if (_options.log) {
313
- console.log('[securenow] Firewall: remote toggle %s (app=%s)',
336
+ fwLog('[securenow] Firewall: remote toggle -> %s (app=%s)',
314
337
  next ? 'ENABLED' : 'DISABLED', body.app.key || _options.appKey);
315
338
  }
316
339
  _lastRemoteEnabled = next;
317
340
  }
318
341
  }
319
342
 
320
- // Update blocklist version + data
321
- if (body.blocklist) {
322
- const newVer = body.blocklist.version;
323
- if (newVer !== _lastVersion) {
324
- _lastVersion = newVer;
325
- blChanged = true;
326
- }
327
- }
328
-
329
- if (body.blocklistIps) {
330
- _rawIps = body.blocklistIps;
343
+ // Update blocklist version + data
344
+ if (body.blocklist) {
345
+ const newVer = body.blocklist.version;
346
+ if (newVer !== _lastVersion) {
347
+ _lastVersion = newVer;
348
+ }
349
+ }
350
+
351
+ if (body.blocklistIps) {
352
+ _rawIps = body.blocklistIps;
331
353
  _matcher = createMatcher(body.blocklistIps);
332
354
  _stats.syncs++;
333
355
  notifyLayers(body.blocklistIps);
@@ -335,13 +357,12 @@ function doUnifiedSync(callback) {
335
357
  }
336
358
 
337
359
  // Update allowlist version + data
338
- if (body.allowlist) {
339
- const newVer = body.allowlist.version;
340
- if (newVer !== _lastAllowlistVersion) {
341
- _lastAllowlistVersion = newVer;
342
- alChanged = true;
343
- }
344
- }
360
+ if (body.allowlist) {
361
+ const newVer = body.allowlist.version;
362
+ if (newVer !== _lastAllowlistVersion) {
363
+ _lastAllowlistVersion = newVer;
364
+ }
365
+ }
345
366
 
346
367
  if (body.allowlistIps) {
347
368
  _allowlistRawIps = body.allowlistIps;
@@ -356,7 +377,7 @@ function doUnifiedSync(callback) {
356
377
  });
357
378
  }
358
379
 
359
- // ────── Legacy Sync (v1 separate endpoints, kept for backward compat) ──────
380
+ // Legacy Sync (v1 - separate endpoints, kept for backward compat)
360
381
 
361
382
  function legacyBlocklistSync(callback) {
362
383
  const url = buildFirewallUrl('/firewall/blocklist');
@@ -481,15 +502,15 @@ function doLegacyPoll(callback) {
481
502
 
482
503
  if (blChanged) {
483
504
  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);
505
+ if (err && _options.log) fwWarn('[securenow] Firewall: sync failed (using stale list):', formatRequestError(err));
506
+ else if (changed && stats && _options.log) fwLog('[securenow] Firewall: re-synced %d blocked IPs', stats.total);
486
507
  syncDone();
487
508
  });
488
509
  }
489
510
  if (alChanged) {
490
511
  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);
512
+ if (err && _options.log) fwWarn('[securenow] Firewall: allowlist sync failed:', formatRequestError(err));
513
+ else if (changed && stats && _options.log) fwLog('[securenow] Firewall: re-synced %d allowed IPs', stats.total);
493
514
  syncDone();
494
515
  });
495
516
  }
@@ -508,7 +529,7 @@ function doLegacyPoll(callback) {
508
529
  });
509
530
  }
510
531
 
511
- // ────── Unified poll loop ──────
532
+ // Unified poll loop
512
533
 
513
534
  function notifyLayers(ips) {
514
535
  for (const layer of _layers) {
@@ -526,7 +547,7 @@ function pollOnce(callback) {
526
547
  _consecutiveErrors++;
527
548
  _stats.errors++;
528
549
  maybeOpenCircuit();
529
- if (_options.log) console.warn('[securenow] Firewall: poll failed:', formatRequestError(err));
550
+ if (_options.log) fwWarn('[securenow] Firewall: poll failed:', formatRequestError(err));
530
551
  return callback(err);
531
552
  }
532
553
  _consecutiveErrors = 0;
@@ -534,11 +555,11 @@ function pollOnce(callback) {
534
555
  if (result) {
535
556
  if (result.blChanged && _options.log && _matcher) {
536
557
  const s = _matcher.stats();
537
- console.log('[securenow] Firewall: re-synced %d blocked IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
558
+ fwLog('[securenow] Firewall: re-synced %d blocked IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
538
559
  }
539
560
  if (result.alChanged && _options.log && _allowlistMatcher) {
540
561
  const s = _allowlistMatcher.stats();
541
- console.log('[securenow] Firewall: re-synced %d allowed IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
562
+ fwLog('[securenow] Firewall: re-synced %d allowed IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
542
563
  }
543
564
  }
544
565
  callback(null);
@@ -586,7 +607,7 @@ function startSyncLoop() {
586
607
  _localhostFallbackTried = true;
587
608
  const origUrl = _options.apiUrl;
588
609
  _options.apiUrl = 'http://localhost:4000';
589
- if (_options.log) console.log('[securenow] Firewall: %s unreachable, trying http://localhost:4000', origUrl);
610
+ if (_options.log) fwLog('[securenow] Firewall: %s unreachable, trying http://localhost:4000', origUrl);
590
611
  const retryTimer = setTimeout(initialSync, 1000);
591
612
  if (retryTimer.unref) retryTimer.unref();
592
613
  return;
@@ -596,7 +617,7 @@ function startSyncLoop() {
596
617
  if (retryTimer.unref) retryTimer.unref();
597
618
  return;
598
619
  }
599
- if (_options.log) console.warn('[securenow] Firewall: initial sync failed:', formatRequestError(err));
620
+ if (_options.log) fwWarn('[securenow] Firewall: initial sync failed:', formatRequestError(err));
600
621
  if (_options.failMode === 'closed') {
601
622
  _matcher = { isBlocked: () => true, stats: () => ({ exact: 0, cidr: 0, total: 0 }) };
602
623
  }
@@ -616,11 +637,11 @@ function startSyncLoop() {
616
637
  _initialized = true;
617
638
  if (_options.log && _matcher) {
618
639
  const s = _matcher.stats();
619
- console.log('[securenow] Firewall: synced %d blocked IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
640
+ fwLog('[securenow] Firewall: synced %d blocked IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
620
641
  }
621
642
  if (_options.log && _allowlistMatcher) {
622
643
  const s = _allowlistMatcher.stats();
623
- if (s.total > 0) console.log('[securenow] Firewall: synced %d allowed IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
644
+ if (s.total > 0) fwLog('[securenow] Firewall: synced %d allowed IPs (%d exact + %d CIDR ranges)', s.total, s.exact, s.cidr);
624
645
  }
625
646
  });
626
647
  }
@@ -631,22 +652,25 @@ function startSyncLoop() {
631
652
  // Safety-net full sync timer (less frequent, uses same path)
632
653
  _syncTimer = setInterval(() => {
633
654
  if (shouldSkipRequest()) return;
634
- // Force a full re-fetch by clearing versions so unified endpoint returns full data
635
- const savedBlVer = _lastVersion;
636
- const savedAlVer = _lastAllowlistVersion;
637
- _lastVersion = null;
638
- _lastAllowlistVersion = null;
639
- pollOnce((err) => {
640
- if (err) {
641
- _lastVersion = savedBlVer;
642
- _lastAllowlistVersion = savedAlVer;
643
- }
644
- });
645
- }, fullSyncIntervalMs);
655
+ // Force a full re-fetch by clearing versions so unified endpoint returns full data
656
+ const savedBlVer = _lastVersion;
657
+ const savedAlVer = _lastAllowlistVersion;
658
+ const savedUnifiedEtag = _lastUnifiedEtag;
659
+ _lastVersion = null;
660
+ _lastAllowlistVersion = null;
661
+ _lastUnifiedEtag = null;
662
+ pollOnce((err) => {
663
+ if (err) {
664
+ _lastVersion = savedBlVer;
665
+ _lastAllowlistVersion = savedAlVer;
666
+ _lastUnifiedEtag = savedUnifiedEtag;
667
+ }
668
+ });
669
+ }, fullSyncIntervalMs);
646
670
  if (_syncTimer.unref) _syncTimer.unref();
647
671
  }
648
672
 
649
- // ────── Layer 1: HTTP Handler ──────
673
+ // Layer 1: HTTP Handler
650
674
 
651
675
  const _origHttpCreate = http.createServer;
652
676
  const _origHttpsCreate = https.createServer;
@@ -658,7 +682,7 @@ function blockedHtml(ip) {
658
682
  <html lang="en">
659
683
  <head>
660
684
  <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
661
- <title>Access Blocked Security Alert</title>
685
+ <title>Access Blocked - Security Alert</title>
662
686
  <style>
663
687
  *{margin:0;padding:0;box-sizing:border-box}
664
688
  body{min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0a0a0a;color:#e5e5e5}
@@ -729,7 +753,7 @@ function firewallRequestHandler(req, res) {
729
753
  if (_allowlistMatcher && _allowlistMatcher.stats().total > 0) {
730
754
  if (!_allowlistMatcher.isBlocked(ip)) {
731
755
  _stats.blocked++;
732
- if (_options && _options.log) console.log('[securenow] Firewall: blocked %s via HTTP (not in allowlist)', ip);
756
+ if (_options && _options.log) fwLog('[securenow] Firewall: blocked %s via HTTP (not in allowlist)', ip);
733
757
  reportFirewallEvent({
734
758
  source: 'allowlist',
735
759
  ip,
@@ -747,7 +771,7 @@ function firewallRequestHandler(req, res) {
747
771
  // Blocklist check
748
772
  if (_matcher && _matcher.isBlocked(ip)) {
749
773
  _stats.blocked++;
750
- if (_options && _options.log) console.log('[securenow] Firewall: blocked %s via HTTP', ip);
774
+ if (_options && _options.log) fwLog('[securenow] Firewall: blocked %s via HTTP', ip);
751
775
  reportFirewallEvent({
752
776
  source: 'blocklist',
753
777
  ip,
@@ -801,28 +825,28 @@ function patchHttpLayer() {
801
825
  Object.assign(https.createServer, _origHttpsCreate);
802
826
  }
803
827
 
804
- // ────── Init ──────
828
+ // Init
805
829
 
806
830
  function init(options) {
807
831
  _options = options;
808
832
 
809
- if (_options.log) console.log('[securenow] Firewall: ENABLED');
810
- if (_options.log && _options.environment) console.log('[securenow] Firewall: environment=%s', _options.environment);
833
+ if (_options.log) fwLog('[securenow] Firewall: ENABLED');
834
+ if (_options.log && _options.environment) fwLog('[securenow] Firewall: environment=%s', _options.environment);
811
835
 
812
836
  patchHttpLayer();
813
- if (_options.log) console.log('[securenow] Firewall: Layer 1 (HTTP 403) active');
837
+ if (_options.log) fwLog('[securenow] Firewall: Layer 1 (HTTP 403) active');
814
838
 
815
839
  if (_options.tcp) {
816
840
  try {
817
841
  const tcpLayer = require('./firewall-tcp');
818
842
  tcpLayer.init(() => _matcher, _options, () => _allowlistMatcher);
819
843
  _layers.push(tcpLayer);
820
- if (_options.log) console.log('[securenow] Firewall: Layer 2 (TCP drop) active');
844
+ if (_options.log) fwLog('[securenow] Firewall: Layer 2 (TCP drop) active');
821
845
  } catch (e) {
822
- if (_options.log) console.warn('[securenow] Firewall: Layer 2 (TCP drop) failed:', e.message);
846
+ if (_options.log) fwWarn('[securenow] Firewall: Layer 2 (TCP drop) failed:', e.message);
823
847
  }
824
848
  } else {
825
- if (_options.log) console.log('[securenow] Firewall: Layer 2 (TCP drop) disabled (set config.firewall.tcp=true)');
849
+ if (_options.log) fwLog('[securenow] Firewall: Layer 2 (TCP drop) disabled (set config.firewall.tcp=true)');
826
850
  }
827
851
 
828
852
  if (_options.iptables) {
@@ -830,12 +854,12 @@ function init(options) {
830
854
  const iptablesLayer = require('./firewall-iptables');
831
855
  iptablesLayer.init(_options);
832
856
  _layers.push(iptablesLayer);
833
- if (_options.log) console.log('[securenow] Firewall: Layer 3 (iptables) active');
857
+ if (_options.log) fwLog('[securenow] Firewall: Layer 3 (iptables) active');
834
858
  } catch (e) {
835
- if (_options.log) console.warn('[securenow] Firewall: Layer 3 (iptables) failed:', e.message);
859
+ if (_options.log) fwWarn('[securenow] Firewall: Layer 3 (iptables) failed:', e.message);
836
860
  }
837
861
  } else {
838
- if (_options.log) console.log('[securenow] Firewall: Layer 3 (iptables) disabled (set config.firewall.iptables=true)');
862
+ if (_options.log) fwLog('[securenow] Firewall: Layer 3 (iptables) disabled (set config.firewall.iptables=true)');
839
863
  }
840
864
 
841
865
  if (_options.cloud) {
@@ -843,12 +867,12 @@ function init(options) {
843
867
  const cloudLayer = require('./firewall-cloud');
844
868
  cloudLayer.init(_options);
845
869
  _layers.push(cloudLayer);
846
- if (_options.log) console.log('[securenow] Firewall: Layer 4 (Cloud WAF) active (%s)', _options.cloud);
870
+ if (_options.log) fwLog('[securenow] Firewall: Layer 4 (Cloud WAF) active (%s)', _options.cloud);
847
871
  } catch (e) {
848
- if (_options.log) console.warn('[securenow] Firewall: Layer 4 (Cloud WAF) failed:', e.message);
872
+ if (_options.log) fwWarn('[securenow] Firewall: Layer 4 (Cloud WAF) failed:', e.message);
849
873
  }
850
874
  } else {
851
- if (_options.log) console.log('[securenow] Firewall: Layer 4 (Cloud WAF) disabled (set config.firewall.cloud=cloudflare|aws|gcp)');
875
+ if (_options.log) fwLog('[securenow] Firewall: Layer 4 (Cloud WAF) disabled (set config.firewall.cloud=cloudflare|aws|gcp)');
852
876
  }
853
877
 
854
878
  startSyncLoop();
@@ -907,4 +931,4 @@ function getMatcher() { return _remoteEnabled === false ? null : _matcher; }
907
931
  function getAllowlistMatcher() { return _remoteEnabled === false ? null : _allowlistMatcher; }
908
932
  function isRemoteEnabled() { return _remoteEnabled !== false; }
909
933
 
910
- module.exports = { init, shutdown, getStats, getMatcher, getAllowlistMatcher, isRemoteEnabled };
934
+ module.exports = { init, shutdown, getStats, getMatcher, getAllowlistMatcher, isRemoteEnabled };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "7.7.2",
3
+ "version": "7.7.4",
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
@@ -521,7 +521,8 @@ if (loggingEnabled) {
521
521
 
522
522
  // Auto-patch console.* so every log/warn/error becomes an OTel log record
523
523
  const _logger = loggerProvider.getLogger('console', '1.0.0');
524
- const _orig = { log: console.log, info: console.info, warn: console.warn, error: console.error, debug: console.debug };
524
+ const _orig = console.__securenow_original || { log: console.log, info: console.info, warn: console.warn, error: console.error, debug: console.debug };
525
+ if (!console.__securenow_original) console.__securenow_original = _orig;
525
526
  const SEV = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
526
527
  function _emit(sn, st, args) {
527
528
  try {