securenow 7.2.0 → 7.3.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/app-config.js +25 -0
- package/cli/diagnostics.js +14 -6
- package/cli/firewall.js +75 -1
- package/cli.js +6 -2
- package/docs/ENVIRONMENT-VARIABLES.md +1 -1
- package/firewall-only.js +6 -1
- package/firewall.js +42 -5
- package/nextjs.js +8 -2
- package/nuxt-server-plugin.mjs +6 -4
- package/package.json +1 -1
- package/tracing.js +15 -4
- package/web-vite.mjs +9 -1
package/app-config.js
CHANGED
|
@@ -157,6 +157,30 @@ function resolveAll() {
|
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// Decide whether to append a per-worker UUID suffix to service.name.
|
|
161
|
+
//
|
|
162
|
+
// The dashboard filters traces with an exact match on service.name
|
|
163
|
+
// (`resource_string_service$$name IN (...)`), so when appId IS the routing
|
|
164
|
+
// UUID (customer is logged in → credentials file provides app.key), the
|
|
165
|
+
// suffix guarantees a miss. Per-worker disambiguation still happens via
|
|
166
|
+
// service.instance.id, which is never used for filtering.
|
|
167
|
+
//
|
|
168
|
+
// Precedence:
|
|
169
|
+
// 1. Explicit opts.noUuid (caller passed it) — wins
|
|
170
|
+
// 2. Explicit SECURENOW_NO_UUID env var (0/1/true/false) — wins
|
|
171
|
+
// 3. appKey resolved (logged-in, appId is routing UUID) → true
|
|
172
|
+
// 4. Otherwise (pre-login, appId = package.json#name) → false
|
|
173
|
+
function resolveNoUuid(opts = {}) {
|
|
174
|
+
if (opts.noUuid !== undefined && opts.noUuid !== null) return !!opts.noUuid;
|
|
175
|
+
|
|
176
|
+
const raw = process.env.SECURENOW_NO_UUID;
|
|
177
|
+
if (raw !== undefined && raw !== '') {
|
|
178
|
+
return /^(1|true)$/i.test(String(raw).trim());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return !!resolveAppKey();
|
|
182
|
+
}
|
|
183
|
+
|
|
160
184
|
module.exports = {
|
|
161
185
|
FREE_TRIAL_INSTANCE,
|
|
162
186
|
resolveAppKey,
|
|
@@ -165,6 +189,7 @@ module.exports = {
|
|
|
165
189
|
resolveApiKey,
|
|
166
190
|
resolveInstance,
|
|
167
191
|
resolveAll,
|
|
192
|
+
resolveNoUuid,
|
|
168
193
|
loadCredentials,
|
|
169
194
|
loadLocalCredentials,
|
|
170
195
|
loadGlobalCredentials,
|
package/cli/diagnostics.js
CHANGED
|
@@ -8,12 +8,20 @@ const config = require('./config');
|
|
|
8
8
|
// ── Config resolution (mirrors tracing.js priority order) ──
|
|
9
9
|
|
|
10
10
|
function resolvedConfig() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Mirror the SDK's resolution: env → credentials file → package.json#name.
|
|
12
|
+
// Doing it here means `securenow doctor` reports what the SDK will actually
|
|
13
|
+
// send — critical for diagnosing "my traces don't show up" (the common
|
|
14
|
+
// cause is a service.name mismatch with the dashboard's exact-match filter).
|
|
15
|
+
const appConfig = require('../app-config');
|
|
16
|
+
const resolvedApp = appConfig.resolveAll();
|
|
17
|
+
const noUuid = appConfig.resolveNoUuid();
|
|
18
|
+
|
|
19
|
+
const baseName = (process.env.OTEL_SERVICE_NAME || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '') || null;
|
|
20
|
+
const serviceName = baseName
|
|
21
|
+
? (noUuid ? baseName : `${baseName}-<uuid-per-worker>`)
|
|
22
|
+
: '(auto-generated)';
|
|
15
23
|
const instance =
|
|
16
|
-
|
|
24
|
+
resolvedApp.instance || 'https://freetrial.securenow.ai:4318';
|
|
17
25
|
const tracesEndpoint =
|
|
18
26
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
|
|
19
27
|
(process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
@@ -297,7 +305,7 @@ function env(_args, flags) {
|
|
|
297
305
|
SECURENOW_API_URL: cfg.apiUrl,
|
|
298
306
|
SECURENOW_LOGGING_ENABLED: cfg.loggingEnabled ? '1' : '0',
|
|
299
307
|
SECURENOW_CAPTURE_BODY: cfg.captureBody ? '1' : '0',
|
|
300
|
-
SECURENOW_NO_UUID: process.env.SECURENOW_NO_UUID || '0'
|
|
308
|
+
SECURENOW_NO_UUID: process.env.SECURENOW_NO_UUID || `(auto: ${require('../app-config').resolveNoUuid() ? '1' : '0'})`,
|
|
301
309
|
SECURENOW_FIREWALL_TCP: process.env.SECURENOW_FIREWALL_TCP || '0',
|
|
302
310
|
SECURENOW_FIREWALL_IPTABLES: process.env.SECURENOW_FIREWALL_IPTABLES || '0',
|
|
303
311
|
SECURENOW_FIREWALL_CLOUD: process.env.SECURENOW_FIREWALL_CLOUD || null,
|
package/cli/firewall.js
CHANGED
|
@@ -97,4 +97,78 @@ async function testIp(args, flags) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
async function resolveAppKey(flags) {
|
|
101
|
+
if (flags && flags.app) return String(flags.app);
|
|
102
|
+
try {
|
|
103
|
+
const { resolveAppKey: r } = require('../app-config');
|
|
104
|
+
const k = r();
|
|
105
|
+
if (k) return k;
|
|
106
|
+
} catch (_) {}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function setEnabled(args, flags, enabled) {
|
|
111
|
+
requireAuth();
|
|
112
|
+
const appKey = await resolveAppKey(flags);
|
|
113
|
+
if (!appKey) {
|
|
114
|
+
ui.error('No app selected. Pass --app <key> or run `securenow login` / `securenow apps default <key>`.');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const verb = enabled ? 'Enabling' : 'Disabling';
|
|
119
|
+
const s = ui.spinner(`${verb} firewall for ${appKey}`);
|
|
120
|
+
try {
|
|
121
|
+
const data = await api.patch(`/firewall/app/${encodeURIComponent(appKey)}`, { enabled });
|
|
122
|
+
s.stop(`Firewall ${enabled ? 'enabled' : 'disabled'}`);
|
|
123
|
+
|
|
124
|
+
if (flags.json) { ui.json(data); return; }
|
|
125
|
+
|
|
126
|
+
const app = data.app || {};
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log(` ${ui.c.bold(enabled ? ui.c.green('Firewall: ENABLED') : ui.c.yellow('Firewall: DISABLED'))} — ${app.name || appKey}`);
|
|
129
|
+
console.log(` ${ui.c.dim('App key:')} ${app.key || appKey}`);
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(` ${ui.c.dim('Running SDK instances pick up the change within ~10s.')}`);
|
|
132
|
+
console.log('');
|
|
133
|
+
} catch (err) {
|
|
134
|
+
s.fail(`Failed to ${enabled ? 'enable' : 'disable'} firewall`);
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function enable(args, flags) { return setEnabled(args, flags, true); }
|
|
140
|
+
async function disable(args, flags) { return setEnabled(args, flags, false); }
|
|
141
|
+
|
|
142
|
+
async function appsList(args, flags) {
|
|
143
|
+
requireAuth();
|
|
144
|
+
const s = ui.spinner('Loading apps');
|
|
145
|
+
try {
|
|
146
|
+
const data = await api.get('/firewall/apps');
|
|
147
|
+
s.stop('Apps loaded');
|
|
148
|
+
|
|
149
|
+
if (flags.json) { ui.json(data); return; }
|
|
150
|
+
|
|
151
|
+
const apps = data.apps || [];
|
|
152
|
+
if (apps.length === 0) {
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log(` ${ui.c.dim('No apps yet. Create one with `securenow apps create <name>`.')}`);
|
|
155
|
+
console.log('');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('');
|
|
160
|
+
for (const a of apps) {
|
|
161
|
+
const tag = a.firewallEnabled ? ui.c.green('ON ') : ui.c.yellow('OFF');
|
|
162
|
+
console.log(` ${tag} ${ui.c.bold(a.name)} ${ui.c.dim(a.key)}`);
|
|
163
|
+
}
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log(` ${ui.c.dim('Toggle:')} securenow firewall enable --app <key>`);
|
|
166
|
+
console.log(` ${ui.c.dim(' :')} securenow firewall disable --app <key>`);
|
|
167
|
+
console.log('');
|
|
168
|
+
} catch (err) {
|
|
169
|
+
s.fail('Failed to list apps');
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = { status, testIp, enable, disable, appsList };
|
package/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const ui = require('./cli/ui');
|
|
@@ -178,10 +178,14 @@ const COMMANDS = {
|
|
|
178
178
|
defaultSub: 'list',
|
|
179
179
|
},
|
|
180
180
|
firewall: {
|
|
181
|
-
desc: 'Firewall status and IP testing',
|
|
181
|
+
desc: 'Firewall status, per-app toggle, and IP testing',
|
|
182
182
|
usage: 'securenow firewall <subcommand> [options]',
|
|
183
|
+
flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
|
|
183
184
|
sub: {
|
|
184
185
|
status: { desc: 'Show firewall status, layers, and blocklist info', run: (a, f) => require('./cli/firewall').status(a, f) },
|
|
186
|
+
apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
|
|
187
|
+
enable: { desc: 'Turn the firewall ON for an app', usage: 'securenow firewall enable [--app <key>]', flags: { app: 'App key (defaults to logged-in app)' }, run: (a, f) => require('./cli/firewall').enable(a, f) },
|
|
188
|
+
disable: { desc: 'Turn the firewall OFF for an app', usage: 'securenow firewall disable [--app <key>]', flags: { app: 'App key (defaults to logged-in app)' }, run: (a, f) => require('./cli/firewall').disable(a, f) },
|
|
185
189
|
'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip>', run: (a, f) => require('./cli/firewall').testIp(a, f) },
|
|
186
190
|
},
|
|
187
191
|
defaultSub: 'status',
|
|
@@ -25,7 +25,7 @@ Complete reference for all environment variables supported by SecureNow.
|
|
|
25
25
|
| **SECURENOW_DISABLE_INSTRUMENTATIONS** | Optional | - | Comma-separated list of OTel instrumentations to disable |
|
|
26
26
|
| **SECURENOW_TEST_SPAN** | Optional | `0` | Emit a single test span on startup (prefer `npx securenow test-span`) |
|
|
27
27
|
| **SECURENOW_HIDE_BANNER** | Optional | `0` | Hide the free-trial banner |
|
|
28
|
-
| **SECURENOW_FIREWALL_ENABLED** | Optional | `
|
|
28
|
+
| **SECURENOW_FIREWALL_ENABLED** | Optional | (dashboard toggle) | Local override only — set to `0` to force-off regardless of dashboard. Primary control is the per-app toggle at `/dashboard/firewall` (≥ 7.3.0). |
|
|
29
29
|
| **SECURENOW_ENABLE_MONGODB_INSTRUMENTATION** | Optional | `0` | Opt in to MongoDB instrumentation (off by default since a cursor bug on mongodb@6.6+; safe since SDK v6.0.2) |
|
|
30
30
|
| **OTEL_SERVICE_NAME** | Optional | - | Alternative to SECURENOW_APPID (label only, no routing) |
|
|
31
31
|
| **OTEL_EXPORTER_OTLP_ENDPOINT** | Optional | - | Alternative to SECURENOW_INSTANCE |
|
package/firewall-only.js
CHANGED
|
@@ -16,12 +16,17 @@ try { require('dotenv').config(); } catch (_) {}
|
|
|
16
16
|
const env = (k) =>
|
|
17
17
|
process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
|
|
18
18
|
|
|
19
|
-
const { resolveApiKey } = require('./app-config');
|
|
19
|
+
const { resolveApiKey, resolveAppKey } = require('./app-config');
|
|
20
20
|
const firewallApiKey = resolveApiKey();
|
|
21
|
+
const appKey = resolveAppKey();
|
|
21
22
|
|
|
23
|
+
// SECURENOW_FIREWALL_ENABLED=0 is a hard local override (ops escape hatch).
|
|
24
|
+
// In all other cases the toggle lives in the dashboard; default ON when an
|
|
25
|
+
// API key is present.
|
|
22
26
|
if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
|
|
23
27
|
require('./firewall').init({
|
|
24
28
|
apiKey: firewallApiKey,
|
|
29
|
+
appKey: appKey || null,
|
|
25
30
|
apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
|
|
26
31
|
versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
|
|
27
32
|
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
|
package/firewall.js
CHANGED
|
@@ -16,9 +16,16 @@ let _initialized = false;
|
|
|
16
16
|
let _consecutiveErrors = 0;
|
|
17
17
|
let _layers = [];
|
|
18
18
|
let _rawIps = [];
|
|
19
|
-
let _stats = { syncs: 0, blocked: 0, allowed: 0, versionChecks: 0, errors: 0 };
|
|
19
|
+
let _stats = { syncs: 0, blocked: 0, allowed: 0, versionChecks: 0, errors: 0, suppressedDisabled: 0 };
|
|
20
20
|
let _localhostFallbackTried = false;
|
|
21
21
|
|
|
22
|
+
// Remote toggle — set by /firewall/sync when an appKey is in scope. Default
|
|
23
|
+
// true so a missing/unreachable backend fails open (matches pre-7.3 behavior).
|
|
24
|
+
// When the dashboard / CLI flips this off, the next poll suppresses
|
|
25
|
+
// enforcement without restarting the host process.
|
|
26
|
+
let _remoteEnabled = true;
|
|
27
|
+
let _lastRemoteEnabled = null;
|
|
28
|
+
|
|
22
29
|
// Allowlist state
|
|
23
30
|
let _allowlistMatcher = null;
|
|
24
31
|
let _allowlistRawIps = [];
|
|
@@ -133,7 +140,8 @@ function httpGet(url, extraHeaders, timeout, callback) {
|
|
|
133
140
|
// ────── Unified Sync (v2 — single request for everything) ──────
|
|
134
141
|
|
|
135
142
|
function doUnifiedSync(callback) {
|
|
136
|
-
const
|
|
143
|
+
const appQuery = _options.appKey ? `?app=${encodeURIComponent(_options.appKey)}` : '';
|
|
144
|
+
const url = buildUrl(_options.apiUrl, '/firewall/sync') + appQuery;
|
|
137
145
|
const headers = {};
|
|
138
146
|
if (_lastVersion) headers['X-Blocklist-Version'] = _lastVersion;
|
|
139
147
|
if (_lastAllowlistVersion) headers['X-Allowlist-Version'] = _lastAllowlistVersion;
|
|
@@ -165,6 +173,21 @@ function doUnifiedSync(callback) {
|
|
|
165
173
|
let blChanged = false;
|
|
166
174
|
let alChanged = false;
|
|
167
175
|
|
|
176
|
+
// Apply remote per-app toggle. Absent body.app means the backend either
|
|
177
|
+
// doesn't know about appKey-scoped sync (older API) or no appKey was
|
|
178
|
+
// sent — leave the previous value untouched (default true on first run).
|
|
179
|
+
if (body.app && typeof body.app.firewallEnabled === 'boolean') {
|
|
180
|
+
const next = body.app.firewallEnabled;
|
|
181
|
+
if (next !== _lastRemoteEnabled) {
|
|
182
|
+
_remoteEnabled = next;
|
|
183
|
+
if (_options.log) {
|
|
184
|
+
console.log('[securenow] Firewall: remote toggle → %s (app=%s)',
|
|
185
|
+
next ? 'ENABLED' : 'DISABLED', body.app.key || _options.appKey);
|
|
186
|
+
}
|
|
187
|
+
_lastRemoteEnabled = next;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
168
191
|
// Update blocklist version + data
|
|
169
192
|
if (body.blocklist) {
|
|
170
193
|
const newVer = body.blocklist.version;
|
|
@@ -559,6 +582,14 @@ function sendBlockResponse(req, res, ip) {
|
|
|
559
582
|
}
|
|
560
583
|
|
|
561
584
|
function firewallRequestHandler(req, res) {
|
|
585
|
+
// Remote disable wins over everything: when the dashboard / CLI flips the
|
|
586
|
+
// toggle off, requests pass through the SDK as if the firewall weren't
|
|
587
|
+
// installed. The poll loop keeps running so we re-enable within seconds.
|
|
588
|
+
if (_remoteEnabled === false) {
|
|
589
|
+
_stats.suppressedDisabled++;
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
|
|
562
593
|
const ip = resolveClientIp(req);
|
|
563
594
|
|
|
564
595
|
// Allowlist check: if active, only listed IPs are allowed through
|
|
@@ -711,10 +742,16 @@ function getStats() {
|
|
|
711
742
|
circuitState: _circuitState,
|
|
712
743
|
consecutiveErrors: _consecutiveErrors,
|
|
713
744
|
unifiedSync: _useUnifiedSync,
|
|
745
|
+
remoteEnabled: _remoteEnabled,
|
|
746
|
+
appKey: _options ? _options.appKey || null : null,
|
|
714
747
|
};
|
|
715
748
|
}
|
|
716
749
|
|
|
717
|
-
|
|
718
|
-
|
|
750
|
+
// Layers (TCP / iptables / cloud) read the matcher to populate kernel-level
|
|
751
|
+
// rules. When the remote toggle is off, return null so they treat the policy
|
|
752
|
+
// as "no IPs to block" without us mutating the cached matcher.
|
|
753
|
+
function getMatcher() { return _remoteEnabled === false ? null : _matcher; }
|
|
754
|
+
function getAllowlistMatcher() { return _remoteEnabled === false ? null : _allowlistMatcher; }
|
|
755
|
+
function isRemoteEnabled() { return _remoteEnabled !== false; }
|
|
719
756
|
|
|
720
|
-
module.exports = { init, shutdown, getStats, getMatcher, getAllowlistMatcher };
|
|
757
|
+
module.exports = { init, shutdown, getStats, getMatcher, getAllowlistMatcher, isRemoteEnabled };
|
package/nextjs.js
CHANGED
|
@@ -150,7 +150,10 @@ function registerSecureNow(options = {}) {
|
|
|
150
150
|
|
|
151
151
|
const rawBase = (options.serviceName || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
|
|
152
152
|
const baseName = rawBase || null;
|
|
153
|
-
|
|
153
|
+
// Default: auto-disable suffix when logged in (appId is the routing UUID
|
|
154
|
+
// and the dashboard does exact match). opts.noUuid or SECURENOW_NO_UUID
|
|
155
|
+
// override.
|
|
156
|
+
const noUuid = appConfig.resolveNoUuid({ noUuid: options.noUuid });
|
|
154
157
|
|
|
155
158
|
// service.name
|
|
156
159
|
let serviceName;
|
|
@@ -613,11 +616,14 @@ function registerSecureNow(options = {}) {
|
|
|
613
616
|
// Firewall — runs independently from OTel so it works even if tracing fails.
|
|
614
617
|
// Key comes from env OR .securenow/credentials.json (set via
|
|
615
618
|
// `npx securenow api-key set snk_live_...`), so you don't need a .env entry.
|
|
616
|
-
const
|
|
619
|
+
const appConfig = require('./app-config');
|
|
620
|
+
const firewallApiKey = appConfig.resolveApiKey();
|
|
621
|
+
const firewallAppKey = appConfig.resolveAppKey();
|
|
617
622
|
if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
|
|
618
623
|
try {
|
|
619
624
|
require('./firewall').init({
|
|
620
625
|
apiKey: firewallApiKey,
|
|
626
|
+
appKey: firewallAppKey || null,
|
|
621
627
|
apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
|
|
622
628
|
versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
|
|
623
629
|
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
|
package/nuxt-server-plugin.mjs
CHANGED
|
@@ -83,10 +83,10 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
83
83
|
// ── Naming ──
|
|
84
84
|
const rawBase = (opts.serviceName || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
|
|
85
85
|
const baseName = rawBase || null;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
// Default: auto-disable per-worker suffix when logged in (appId is the
|
|
87
|
+
// routing UUID and the dashboard does exact match). opts.noUuid or
|
|
88
|
+
// SECURENOW_NO_UUID override.
|
|
89
|
+
const noUuid = appConfig.resolveNoUuid({ noUuid: opts.noUuid });
|
|
90
90
|
|
|
91
91
|
let serviceName;
|
|
92
92
|
if (baseName) {
|
|
@@ -328,11 +328,13 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
328
328
|
|
|
329
329
|
// ── Firewall — runs independently from OTel ──
|
|
330
330
|
const firewallApiKey = env('SECURENOW_API_KEY');
|
|
331
|
+
const firewallAppKey = env('SECURENOW_APPID') || null;
|
|
331
332
|
if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
|
|
332
333
|
try {
|
|
333
334
|
const { init: fwInit } = await import('./firewall.js');
|
|
334
335
|
fwInit({
|
|
335
336
|
apiKey: firewallApiKey,
|
|
337
|
+
appKey: firewallAppKey,
|
|
336
338
|
apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
|
|
337
339
|
versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
|
|
338
340
|
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
|
package/package.json
CHANGED
package/tracing.js
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Env:
|
|
12
12
|
* SECURENOW_APPID=logical-name # or OTEL_SERVICE_NAME=logical-name
|
|
13
|
-
* SECURENOW_NO_UUID=1
|
|
13
|
+
* SECURENOW_NO_UUID=1|0 # override. Default: auto — 1 when
|
|
14
|
+
* logged in (appId is routing UUID,
|
|
15
|
+
* dashboard does exact match),
|
|
16
|
+
* 0 pre-login (use suffix to
|
|
17
|
+
* distinguish PM2 cluster workers).
|
|
14
18
|
* SECURENOW_INSTANCE=http://host:4318 # OTLP/HTTP base (default https://freetrial.securenow.ai:4318)
|
|
15
19
|
* OTEL_EXPORTER_OTLP_ENDPOINT=... # alternative base
|
|
16
20
|
* OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=... # full traces URL
|
|
@@ -288,7 +292,10 @@ const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
|
|
|
288
292
|
// -------- naming rules --------
|
|
289
293
|
const rawBase = (resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
|
|
290
294
|
const baseName = rawBase || null;
|
|
291
|
-
|
|
295
|
+
// Auto-disables the per-worker suffix when we resolved a routing UUID from
|
|
296
|
+
// credentials — the dashboard does exact-match IN on service.name, so any
|
|
297
|
+
// suffix breaks routing. Env SECURENOW_NO_UUID=0|1 still overrides.
|
|
298
|
+
const noUuid = appConfig.resolveNoUuid();
|
|
292
299
|
const strict = String(env('SECURENOW_STRICT')) === '1' || String(env('SECURENOW_STRICT')).toLowerCase() === 'true';
|
|
293
300
|
const inPm2Cluster = !!(process.env.NODE_APP_INSTANCE || process.env.pm_id);
|
|
294
301
|
|
|
@@ -604,11 +611,15 @@ const sdk = new NodeSDK({
|
|
|
604
611
|
patchHttpForBanner();
|
|
605
612
|
}
|
|
606
613
|
|
|
607
|
-
// Firewall — auto-activates when
|
|
608
|
-
|
|
614
|
+
// Firewall — auto-activates only when a real snk_live_ key is resolvable.
|
|
615
|
+
// resolveApiKey() enforces the prefix, so we skip cleanly when the app has
|
|
616
|
+
// only an app-routing UUID (or nothing at all) — no 401 polling loops.
|
|
617
|
+
const firewallApiKey = appConfig.resolveApiKey();
|
|
618
|
+
const firewallAppKey = appConfig.resolveAppKey();
|
|
609
619
|
if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
|
|
610
620
|
require('./firewall').init({
|
|
611
621
|
apiKey: firewallApiKey,
|
|
622
|
+
appKey: firewallAppKey || null,
|
|
612
623
|
apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
|
|
613
624
|
versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
|
|
614
625
|
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
|
package/web-vite.mjs
CHANGED
|
@@ -52,7 +52,15 @@ const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
|
|
|
52
52
|
// ---- naming rules (mirrors tracing.js) ----
|
|
53
53
|
const rawBase = (env('OTEL_SERVICE_NAME') || env('SECURENOW_APPID') || '').trim().replace(/^['"]|['"]$/g, '');
|
|
54
54
|
const baseName = rawBase || null;
|
|
55
|
-
|
|
55
|
+
// Default to no suffix whenever a baseName is resolved: the dashboard filters
|
|
56
|
+
// service.name by exact match, and browsers have no PM2 cluster problem
|
|
57
|
+
// (each tab has its own service.instance.id). Explicit SECURENOW_NO_UUID=0
|
|
58
|
+
// still re-enables the suffix if someone really wants it.
|
|
59
|
+
const noUuidEnv = env('SECURENOW_NO_UUID');
|
|
60
|
+
const noUuid =
|
|
61
|
+
(noUuidEnv !== undefined && noUuidEnv !== '')
|
|
62
|
+
? /^(1|true)$/i.test(String(noUuidEnv).trim())
|
|
63
|
+
: !!baseName;
|
|
56
64
|
const strict = String(env('SECURENOW_STRICT')) === '1' || String(env('SECURENOW_STRICT')).toLowerCase() === 'true';
|
|
57
65
|
|
|
58
66
|
function uuidv4(): string {
|