securenow 7.7.3 → 7.7.5
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 +15 -6
- package/SKILL-API.md +7 -7
- package/app-config.js +6 -6
- package/cli/diagnostics.js +80 -0
- package/console-instrumentation.js +2 -1
- package/firewall.js +111 -87
- package/package.json +2 -2
- package/tracing.js +4 -3
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
|
|
1096
|
-
2. **Full blocklist sync** only when the version changes (or every
|
|
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
|
|
1248
|
-
| `SECURENOW_FIREWALL_SYNC_INTERVAL` |
|
|
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` |
|
|
@@ -1261,7 +1261,7 @@ Use `npx securenow help firewall` for complete details on all layers.
|
|
|
1261
1261
|
|
|
1262
1262
|
| Variable | Description | Default |
|
|
1263
1263
|
|----------|-------------|---------|
|
|
1264
|
-
| `OTEL_LOG_LEVEL` | OpenTelemetry
|
|
1264
|
+
| `OTEL_LOG_LEVEL` | OpenTelemetry diagnostic override. Options: `debug`, `info`, `warn`, `error`, `none`. Overrides `config.otel.logLevel` for emergency debugging. | `error` |
|
|
1265
1265
|
| `SECURENOW_TEST_SPAN` | Set to `1` to emit a test span on startup. | `0` |
|
|
1266
1266
|
|
|
1267
1267
|
#### Environment
|
|
@@ -1600,7 +1600,16 @@ curl http://localhost:4318/v1/traces
|
|
|
1600
1600
|
# Should return 200 or 405 (method not allowed)
|
|
1601
1601
|
```
|
|
1602
1602
|
|
|
1603
|
-
**Check 3:
|
|
1603
|
+
**Check 3: Run doctor and enable debug diagnostics**
|
|
1604
|
+
|
|
1605
|
+
```bash
|
|
1606
|
+
npx securenow doctor --json
|
|
1607
|
+
OTEL_LOG_LEVEL=debug node -r securenow/register app.js
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
Doctor checks OTLP reachability and flags duplicate `@opentelemetry/api` versions that can silently leave tracing on the OpenTelemetry noop provider.
|
|
1611
|
+
|
|
1612
|
+
For a persistent setting, update credentials:
|
|
1604
1613
|
|
|
1605
1614
|
```json
|
|
1606
1615
|
{
|
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:
|
|
318
|
-
versionCheckInterval: 10, // lightweight
|
|
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,
|
|
@@ -495,7 +495,7 @@ Local development and production use `.securenow/credentials.json`. Every settin
|
|
|
495
495
|
| `SECURENOW_DISABLE_INSTRUMENTATIONS` | Comma-separated packages to skip (e.g. `fs,dns`) | — |
|
|
496
496
|
| `SECURENOW_TEST_SPAN` | `1` to emit a test span on startup | `0` |
|
|
497
497
|
| `SECURENOW_HIDE_BANNER` | `1` to suppress free-trial upgrade banner | `0` |
|
|
498
|
-
| `OTEL_LOG_LEVEL` | SDK
|
|
498
|
+
| `OTEL_LOG_LEVEL` | SDK diagnostic override: `error`, `warn`, `info`, `debug`, or `none` | `error` |
|
|
499
499
|
| `SECURENOW_ENVIRONMENT` / `SECURENOW_DEPLOYMENT_ENVIRONMENT` / `NODE_ENV` | Legacy fallback for `config.runtime.deploymentEnvironment` | `production` |
|
|
500
500
|
|
|
501
501
|
### Firewall
|
|
@@ -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
|
|
508
|
-
| `SECURENOW_FIREWALL_SYNC_INTERVAL` |
|
|
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
|
|
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
|
|
|
@@ -634,7 +634,7 @@ On startup, securenow logs its configuration:
|
|
|
634
634
|
[securenow] Firewall: synced 142 blocked IPs
|
|
635
635
|
```
|
|
636
636
|
|
|
637
|
-
Set `config.otel.logLevel` to `debug
|
|
637
|
+
Set `config.otel.logLevel` to `debug`, or temporarily run with `OTEL_LOG_LEVEL=debug`, and run `securenow doctor --json` to troubleshoot connectivity, duplicate OpenTelemetry API packages, and provider registration issues.
|
|
638
638
|
|
|
639
639
|
**CLI equivalent** (works without booting the SDK — useful when the app won't start):
|
|
640
640
|
|
package/app-config.js
CHANGED
|
@@ -46,7 +46,7 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
46
46
|
tracesEndpoint: null,
|
|
47
47
|
logsEndpoint: null,
|
|
48
48
|
headers: {},
|
|
49
|
-
logLevel: '
|
|
49
|
+
logLevel: 'error',
|
|
50
50
|
disableInstrumentations: [],
|
|
51
51
|
},
|
|
52
52
|
runtime: {
|
|
@@ -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:
|
|
63
|
+
syncInterval: 3600,
|
|
64
64
|
failMode: 'open',
|
|
65
65
|
statusCode: 403,
|
|
66
66
|
log: true,
|
|
@@ -102,7 +102,7 @@ const CONFIG_EXPLANATIONS = Object.freeze({
|
|
|
102
102
|
'config.otel.tracesEndpoint': 'Optional full traces endpoint override, for split collectors.',
|
|
103
103
|
'config.otel.logsEndpoint': 'Optional full logs endpoint override, for split collectors.',
|
|
104
104
|
'config.otel.headers': 'Optional OTLP headers. The SDK auto-adds x-api-key from app.key when missing.',
|
|
105
|
-
'config.otel.logLevel': 'OpenTelemetry diagnostic log level:
|
|
105
|
+
'config.otel.logLevel': 'OpenTelemetry diagnostic log level: error, warn, info, debug, or none.',
|
|
106
106
|
'config.otel.disableInstrumentations': 'Optional OTel instrumentation package names to disable.',
|
|
107
107
|
'config.runtime.deploymentEnvironment': 'deployment.environment resource attribute. Set this in the credentials file for production.',
|
|
108
108
|
'config.runtime.noUuid': 'null means auto: true when app.key is present. Set true/false only for advanced routing needs.',
|
|
@@ -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',
|
|
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),
|
package/cli/diagnostics.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
4
6
|
const url = require('url');
|
|
5
7
|
const ui = require('./ui');
|
|
6
8
|
const config = require('./config');
|
|
@@ -37,6 +39,7 @@ function resolvedConfig(options = {}) {
|
|
|
37
39
|
loggingEnabled: appConfig.boolEnv('SECURENOW_LOGGING_ENABLED', true),
|
|
38
40
|
captureBody: appConfig.boolEnv('SECURENOW_CAPTURE_BODY', true),
|
|
39
41
|
captureMultipart: appConfig.boolEnv('SECURENOW_CAPTURE_MULTIPART', true),
|
|
42
|
+
otelLogLevel: (process.env.OTEL_LOG_LEVEL != null ? process.env.OTEL_LOG_LEVEL : appConfig.env('OTEL_LOG_LEVEL')) || 'error',
|
|
40
43
|
firewallEnabled,
|
|
41
44
|
firewallLayers: {
|
|
42
45
|
http: firewallEnabled,
|
|
@@ -47,6 +50,68 @@ function resolvedConfig(options = {}) {
|
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
function readPackageVersion(packageJsonPath, label) {
|
|
54
|
+
try {
|
|
55
|
+
if (!packageJsonPath || !fs.existsSync(packageJsonPath)) return null;
|
|
56
|
+
const realPath = fs.realpathSync(packageJsonPath);
|
|
57
|
+
const pkg = JSON.parse(fs.readFileSync(realPath, 'utf8').replace(/^\uFEFF/, ''));
|
|
58
|
+
return { label, path: realPath, version: pkg.version || null };
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function resolvePackageJson(pkgName, paths, label) {
|
|
65
|
+
try {
|
|
66
|
+
let current = path.dirname(require.resolve(pkgName, { paths }));
|
|
67
|
+
while (current && current !== path.dirname(current)) {
|
|
68
|
+
const candidate = path.join(current, 'package.json');
|
|
69
|
+
const pkg = readPackageVersion(candidate, label);
|
|
70
|
+
if (pkg) return pkg;
|
|
71
|
+
current = path.dirname(current);
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function collectOtelApiPackages() {
|
|
80
|
+
const cwd = process.cwd();
|
|
81
|
+
const packageName = '@opentelemetry/api';
|
|
82
|
+
const candidates = [
|
|
83
|
+
readPackageVersion(path.join(cwd, 'node_modules', '@opentelemetry', 'api', 'package.json'), 'project node_modules'),
|
|
84
|
+
readPackageVersion(path.join(cwd, 'node_modules', 'securenow', 'node_modules', '@opentelemetry', 'api', 'package.json'), 'nested under securenow'),
|
|
85
|
+
resolvePackageJson(packageName, [cwd], 'resolved from project'),
|
|
86
|
+
resolvePackageJson(packageName, [path.resolve(__dirname, '..')], 'resolved from securenow CLI'),
|
|
87
|
+
].filter(Boolean);
|
|
88
|
+
|
|
89
|
+
const byPath = new Map();
|
|
90
|
+
for (const item of candidates) {
|
|
91
|
+
if (!byPath.has(item.path)) byPath.set(item.path, { ...item, labels: [item.label] });
|
|
92
|
+
else byPath.get(item.path).labels.push(item.label);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [...byPath.values()].map(({ label, labels, ...rest }) => ({
|
|
96
|
+
...rest,
|
|
97
|
+
label: labels.join(', '),
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function otelApiSingletonCheck() {
|
|
102
|
+
const packages = collectOtelApiPackages();
|
|
103
|
+
const versions = [...new Set(packages.map((p) => p.version).filter(Boolean))];
|
|
104
|
+
return {
|
|
105
|
+
name: 'otel-api-singleton',
|
|
106
|
+
ok: versions.length <= 1,
|
|
107
|
+
versions,
|
|
108
|
+
packages,
|
|
109
|
+
...(versions.length > 1 ? {
|
|
110
|
+
error: `Multiple @opentelemetry/api versions detected (${versions.join(', ')}). This can leave tracing on the NoopTracerProvider.`,
|
|
111
|
+
} : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
50
115
|
function maskSecret(value) {
|
|
51
116
|
if (!value) return '';
|
|
52
117
|
const text = String(value);
|
|
@@ -308,6 +373,7 @@ function env(_args, flags) {
|
|
|
308
373
|
firewallApiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 12)}...` : null,
|
|
309
374
|
apiUrl: cfg.apiUrl,
|
|
310
375
|
loggingEnabled: cfg.loggingEnabled,
|
|
376
|
+
otelLogLevel: cfg.otelLogLevel,
|
|
311
377
|
captureBody: cfg.captureBody,
|
|
312
378
|
captureMultipart: cfg.captureMultipart,
|
|
313
379
|
noUuid: appConfig.resolveNoUuid(),
|
|
@@ -326,6 +392,7 @@ function env(_args, flags) {
|
|
|
326
392
|
['Environment', cfg.deploymentEnvironment],
|
|
327
393
|
['Traces endpoint', cfg.tracesEndpoint],
|
|
328
394
|
['Logs endpoint', cfg.logsEndpoint],
|
|
395
|
+
['OTel diagnostics', cfg.otelLogLevel],
|
|
329
396
|
['Logging', cfg.loggingEnabled ? ui.c.green('enabled') : ui.c.dim('disabled')],
|
|
330
397
|
['Body capture', cfg.captureBody ? ui.c.green('enabled') : ui.c.dim('disabled')],
|
|
331
398
|
['Multipart capture', cfg.captureMultipart ? ui.c.green('enabled') : ui.c.dim('disabled')],
|
|
@@ -342,6 +409,8 @@ function env(_args, flags) {
|
|
|
342
409
|
async function doctor(_args, flags) {
|
|
343
410
|
const cfg = resolvedConfig();
|
|
344
411
|
const checks = [];
|
|
412
|
+
const otelApiCheck = otelApiSingletonCheck();
|
|
413
|
+
checks.push(otelApiCheck);
|
|
345
414
|
|
|
346
415
|
const jsonHeaders = { 'Content-Type': 'application/json', ...cfg.headers };
|
|
347
416
|
|
|
@@ -386,6 +455,15 @@ async function doctor(_args, flags) {
|
|
|
386
455
|
}
|
|
387
456
|
|
|
388
457
|
const warnings = [];
|
|
458
|
+
const singletonOkMessage = otelApiCheck.ok && otelApiCheck.packages.length
|
|
459
|
+
? `OpenTelemetry API singleton OK (${otelApiCheck.versions[0] || 'unknown'})`
|
|
460
|
+
: null;
|
|
461
|
+
if (!otelApiCheck.ok) {
|
|
462
|
+
warnings.push(`${otelApiCheck.error} Align the app and SecureNow to one 1.9.x copy, then reinstall.`);
|
|
463
|
+
}
|
|
464
|
+
if (String(cfg.otelLogLevel || '').toLowerCase() === 'none') {
|
|
465
|
+
warnings.push('OpenTelemetry diagnostic log level is `none`; provider registration/export errors are hidden. Set config.otel.logLevel to `error`/`warn`, or temporarily run with OTEL_LOG_LEVEL=debug.');
|
|
466
|
+
}
|
|
389
467
|
if (!cfg.appKey) {
|
|
390
468
|
warnings.push('No app key resolved. Run `npx securenow login` or set app.key in .securenow/credentials.json.');
|
|
391
469
|
}
|
|
@@ -410,6 +488,8 @@ async function doctor(_args, flags) {
|
|
|
410
488
|
for (const w of warnings) ui.warn(w);
|
|
411
489
|
}
|
|
412
490
|
|
|
491
|
+
if (singletonOkMessage) ui.success(singletonOkMessage);
|
|
492
|
+
|
|
413
493
|
console.log('');
|
|
414
494
|
if (ok) ui.success('All checks passed.');
|
|
415
495
|
else ui.error('One or more checks failed. Run with --json for details.');
|
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
123
|
+
fwWarn('[securenow] Firewall: API returned 429, backing off for %ds', secs);
|
|
103
124
|
}
|
|
104
125
|
}
|
|
105
126
|
}
|
|
106
127
|
|
|
107
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (res.
|
|
284
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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)
|
|
485
|
-
else if (changed && stats && _options.log)
|
|
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)
|
|
492
|
-
else if (changed && stats && _options.log)
|
|
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
|
-
//
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
//
|
|
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
|
|
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)
|
|
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)
|
|
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
|
-
//
|
|
828
|
+
// Init
|
|
805
829
|
|
|
806
830
|
function init(options) {
|
|
807
831
|
_options = options;
|
|
808
832
|
|
|
809
|
-
if (_options.log)
|
|
810
|
-
if (_options.log && _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)
|
|
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)
|
|
844
|
+
if (_options.log) fwLog('[securenow] Firewall: Layer 2 (TCP drop) active');
|
|
821
845
|
} catch (e) {
|
|
822
|
-
if (_options.log)
|
|
846
|
+
if (_options.log) fwWarn('[securenow] Firewall: Layer 2 (TCP drop) failed:', e.message);
|
|
823
847
|
}
|
|
824
848
|
} else {
|
|
825
|
-
if (_options.log)
|
|
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)
|
|
857
|
+
if (_options.log) fwLog('[securenow] Firewall: Layer 3 (iptables) active');
|
|
834
858
|
} catch (e) {
|
|
835
|
-
if (_options.log)
|
|
859
|
+
if (_options.log) fwWarn('[securenow] Firewall: Layer 3 (iptables) failed:', e.message);
|
|
836
860
|
}
|
|
837
861
|
} else {
|
|
838
|
-
if (_options.log)
|
|
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)
|
|
870
|
+
if (_options.log) fwLog('[securenow] Firewall: Layer 4 (Cloud WAF) active (%s)', _options.cloud);
|
|
847
871
|
} catch (e) {
|
|
848
|
-
if (_options.log)
|
|
872
|
+
if (_options.log) fwWarn('[securenow] Firewall: Layer 4 (Cloud WAF) failed:', e.message);
|
|
849
873
|
}
|
|
850
874
|
} else {
|
|
851
|
-
if (_options.log)
|
|
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.
|
|
3
|
+
"version": "7.7.5",
|
|
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",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"SKILL-API.md"
|
|
142
142
|
],
|
|
143
143
|
"dependencies": {
|
|
144
|
-
"@opentelemetry/api": "1.9.1",
|
|
144
|
+
"@opentelemetry/api": ">=1.9.0 <1.10.0",
|
|
145
145
|
"@opentelemetry/api-logs": "0.218.0",
|
|
146
146
|
"@opentelemetry/auto-instrumentations-node": "0.76.0",
|
|
147
147
|
"@opentelemetry/core": "2.7.1",
|
package/tracing.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* OTEL_EXPORTER_OTLP_HEADERS="k=v,k2=v2"
|
|
22
22
|
* SECURENOW_DISABLE_INSTRUMENTATIONS="pkg1,pkg2"
|
|
23
23
|
* SECURENOW_CAPTURE_MULTIPART=1 # capture multipart/form-data fields & file metadata (streaming, no file content buffered)
|
|
24
|
-
* OTEL_LOG_LEVEL=info|debug
|
|
24
|
+
* OTEL_LOG_LEVEL=error|warn|info|debug|none
|
|
25
25
|
* SECURENOW_TEST_SPAN=1
|
|
26
26
|
*
|
|
27
27
|
* Safety:
|
|
@@ -264,7 +264,7 @@ function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFiel
|
|
|
264
264
|
})();
|
|
265
265
|
|
|
266
266
|
// -------- diagnostics --------
|
|
267
|
-
const diagLevel = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
|
|
267
|
+
const diagLevel = ((process.env.OTEL_LOG_LEVEL != null ? process.env.OTEL_LOG_LEVEL : env('OTEL_LOG_LEVEL')) || '').toLowerCase();
|
|
268
268
|
(() => {
|
|
269
269
|
const level = diagLevel === 'debug' ? DiagLogLevel.DEBUG :
|
|
270
270
|
diagLevel === 'info' ? DiagLogLevel.INFO :
|
|
@@ -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 {
|