securenow 5.12.2 → 5.15.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/CONSUMING-APPS-GUIDE.md +20 -14
- package/NPM_README.md +368 -124
- package/README.md +23 -17
- package/SKILL-API.md +597 -0
- package/SKILL-CLI.md +395 -0
- package/cli/firewall.js +23 -4
- package/cli/init.js +170 -69
- package/cli/security.js +102 -0
- package/cli.js +18 -1
- package/docs/FIREWALL-GUIDE.md +35 -12
- package/firewall-only.js +38 -0
- package/firewall-tcp.js +20 -4
- package/firewall.js +206 -17
- package/nextjs-webpack-config.js +88 -53
- package/nuxt-server-plugin.mjs +23 -0
- package/package.json +8 -2
package/firewall.js
CHANGED
|
@@ -17,6 +17,15 @@ let _consecutiveErrors = 0;
|
|
|
17
17
|
let _layers = [];
|
|
18
18
|
let _rawIps = [];
|
|
19
19
|
let _stats = { syncs: 0, blocked: 0, allowed: 0, versionChecks: 0, errors: 0 };
|
|
20
|
+
let _localhostFallbackTried = false;
|
|
21
|
+
|
|
22
|
+
// Allowlist state
|
|
23
|
+
let _allowlistMatcher = null;
|
|
24
|
+
let _allowlistRawIps = [];
|
|
25
|
+
let _lastAllowlistModified = null;
|
|
26
|
+
let _lastAllowlistVersion = null;
|
|
27
|
+
let _lastAllowlistSyncEtag = null;
|
|
28
|
+
let _allowlistVersionTimer = null;
|
|
20
29
|
|
|
21
30
|
// ────── Blocklist Sync ──────
|
|
22
31
|
|
|
@@ -87,6 +96,136 @@ function notifyLayers(ips) {
|
|
|
87
96
|
}
|
|
88
97
|
}
|
|
89
98
|
|
|
99
|
+
// ────── Allowlist Sync ──────
|
|
100
|
+
|
|
101
|
+
function syncAllowlist(callback) {
|
|
102
|
+
const url = buildUrl(_options.apiUrl, '/firewall/allowlist');
|
|
103
|
+
const mod = url.startsWith('https') ? https : http;
|
|
104
|
+
const parsed = new URL(url);
|
|
105
|
+
|
|
106
|
+
const reqOptions = {
|
|
107
|
+
hostname: parsed.hostname,
|
|
108
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
109
|
+
path: parsed.pathname + parsed.search,
|
|
110
|
+
method: 'GET',
|
|
111
|
+
headers: {
|
|
112
|
+
'Authorization': `Bearer ${_options.apiKey}`,
|
|
113
|
+
'User-Agent': 'securenow-firewall-sdk',
|
|
114
|
+
},
|
|
115
|
+
timeout: 10000,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (_lastAllowlistSyncEtag) {
|
|
119
|
+
reqOptions.headers['If-None-Match'] = _lastAllowlistSyncEtag;
|
|
120
|
+
} else if (_lastAllowlistModified) {
|
|
121
|
+
reqOptions.headers['If-Modified-Since'] = _lastAllowlistModified;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const req = mod.request(reqOptions, (res) => {
|
|
125
|
+
if (res.statusCode === 304) {
|
|
126
|
+
callback(null, false);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let data = '';
|
|
131
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
132
|
+
res.on('end', () => {
|
|
133
|
+
if (res.statusCode !== 200) {
|
|
134
|
+
callback(new Error(`API returned ${res.statusCode}: ${data.slice(0, 200)}`));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const body = JSON.parse(data);
|
|
139
|
+
const ips = body.ips || [];
|
|
140
|
+
_allowlistRawIps = ips;
|
|
141
|
+
_allowlistMatcher = createMatcher(ips);
|
|
142
|
+
_lastAllowlistModified = res.headers['last-modified'] || null;
|
|
143
|
+
if (res.headers['etag']) _lastAllowlistSyncEtag = res.headers['etag'];
|
|
144
|
+
callback(null, true, _allowlistMatcher.stats());
|
|
145
|
+
} catch (e) {
|
|
146
|
+
callback(new Error(`Failed to parse allowlist response: ${e.message}`));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
req.on('error', (err) => callback(err));
|
|
152
|
+
req.on('timeout', () => { req.destroy(); callback(new Error('Allowlist sync request timed out')); });
|
|
153
|
+
req.end();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function checkAllowlistVersion(callback) {
|
|
157
|
+
const url = buildUrl(_options.apiUrl, '/firewall/allowlist/version');
|
|
158
|
+
const mod = url.startsWith('https') ? https : http;
|
|
159
|
+
const parsed = new URL(url);
|
|
160
|
+
|
|
161
|
+
const headers = {
|
|
162
|
+
'Authorization': `Bearer ${_options.apiKey}`,
|
|
163
|
+
'User-Agent': 'securenow-firewall-sdk',
|
|
164
|
+
};
|
|
165
|
+
if (_lastAllowlistVersion) headers['If-None-Match'] = _lastAllowlistVersion;
|
|
166
|
+
|
|
167
|
+
const req = mod.request({
|
|
168
|
+
hostname: parsed.hostname,
|
|
169
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
170
|
+
path: parsed.pathname + parsed.search,
|
|
171
|
+
method: 'GET',
|
|
172
|
+
headers,
|
|
173
|
+
timeout: 5000,
|
|
174
|
+
}, (res) => {
|
|
175
|
+
if (res.statusCode === 304) {
|
|
176
|
+
res.resume();
|
|
177
|
+
callback(null, false);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let data = '';
|
|
182
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
183
|
+
res.on('end', () => {
|
|
184
|
+
if (res.statusCode !== 200) {
|
|
185
|
+
callback(null, false);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const body = JSON.parse(data);
|
|
190
|
+
const version = body.version || null;
|
|
191
|
+
const changed = version !== _lastAllowlistVersion;
|
|
192
|
+
if (changed) _lastAllowlistVersion = version;
|
|
193
|
+
callback(null, changed);
|
|
194
|
+
} catch (_e) { callback(null, false); }
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
req.on('error', () => { callback(null, false); });
|
|
199
|
+
req.on('timeout', () => { req.destroy(); callback(null, false); });
|
|
200
|
+
req.end();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function doFullAllowlistSync() {
|
|
204
|
+
syncAllowlist((err, changed, stats) => {
|
|
205
|
+
if (err) {
|
|
206
|
+
if (_options.log) console.warn('[securenow] Firewall: allowlist sync failed:', err.message);
|
|
207
|
+
} else if (changed && stats && _options.log) {
|
|
208
|
+
console.log('[securenow] Firewall: re-synced %d allowed IPs', stats.total);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function scheduleNextAllowlistVersionCheck() {
|
|
214
|
+
const baseMs = (_options.versionCheckInterval || 10) * 1000;
|
|
215
|
+
const delayMs = jitter(baseMs);
|
|
216
|
+
|
|
217
|
+
_allowlistVersionTimer = setTimeout(() => {
|
|
218
|
+
checkAllowlistVersion((_err, changed) => {
|
|
219
|
+
if (changed) {
|
|
220
|
+
if (_options.log) console.log('[securenow] Firewall: allowlist version changed, syncing…');
|
|
221
|
+
doFullAllowlistSync();
|
|
222
|
+
}
|
|
223
|
+
scheduleNextAllowlistVersionCheck();
|
|
224
|
+
});
|
|
225
|
+
}, delayMs);
|
|
226
|
+
if (_allowlistVersionTimer.unref) _allowlistVersionTimer.unref();
|
|
227
|
+
}
|
|
228
|
+
|
|
90
229
|
function checkVersion(callback) {
|
|
91
230
|
const url = buildUrl(_options.apiUrl, '/firewall/blocklist/version');
|
|
92
231
|
const mod = url.startsWith('https') ? https : http;
|
|
@@ -178,6 +317,16 @@ function startSyncLoop() {
|
|
|
178
317
|
function initialSync() {
|
|
179
318
|
syncBlocklist((err, changed, stats) => {
|
|
180
319
|
if (err) {
|
|
320
|
+
const isConnErr = /ECONNREFUSED|ENOTFOUND|timed out/i.test(err.message);
|
|
321
|
+
if (isConnErr && !_localhostFallbackTried && _options.apiUrl !== 'http://localhost:4000') {
|
|
322
|
+
_localhostFallbackTried = true;
|
|
323
|
+
const origUrl = _options.apiUrl;
|
|
324
|
+
_options.apiUrl = 'http://localhost:4000';
|
|
325
|
+
if (_options.log) console.log('[securenow] Firewall: %s unreachable, trying http://localhost:4000', origUrl);
|
|
326
|
+
const retryTimer = setTimeout(initialSync, 1000);
|
|
327
|
+
if (retryTimer.unref) retryTimer.unref();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
181
330
|
if (_options.log) console.warn('[securenow] Firewall: initial sync failed:', err.message);
|
|
182
331
|
if (_options.failMode === 'closed') {
|
|
183
332
|
_matcher = { isBlocked: () => true, stats: () => ({ exact: 0, cidr: 0, total: 0 }) };
|
|
@@ -193,11 +342,27 @@ function startSyncLoop() {
|
|
|
193
342
|
});
|
|
194
343
|
}
|
|
195
344
|
|
|
345
|
+
function initialAllowlistSync() {
|
|
346
|
+
syncAllowlist((err, changed, stats) => {
|
|
347
|
+
if (err) {
|
|
348
|
+
if (_options.log) console.warn('[securenow] Firewall: initial allowlist sync failed:', err.message);
|
|
349
|
+
const retryTimer = setTimeout(initialAllowlistSync, RETRY_DELAY);
|
|
350
|
+
if (retryTimer.unref) retryTimer.unref();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (changed && stats && stats.total > 0) {
|
|
354
|
+
if (_options.log) console.log('[securenow] Firewall: synced %d allowed IPs (%d exact + %d CIDR ranges)', stats.total, stats.exact, stats.cidr);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
196
359
|
initialSync();
|
|
360
|
+
initialAllowlistSync();
|
|
197
361
|
|
|
198
362
|
scheduleNextVersionCheck();
|
|
363
|
+
scheduleNextAllowlistVersionCheck();
|
|
199
364
|
|
|
200
|
-
_syncTimer = setInterval(() => { doFullSync(); }, fullSyncIntervalMs);
|
|
365
|
+
_syncTimer = setInterval(() => { doFullSync(); doFullAllowlistSync(); }, fullSyncIntervalMs);
|
|
201
366
|
if (_syncTimer.unref) _syncTimer.unref();
|
|
202
367
|
}
|
|
203
368
|
|
|
@@ -255,24 +420,41 @@ function wrapListener(originalListener) {
|
|
|
255
420
|
};
|
|
256
421
|
}
|
|
257
422
|
|
|
423
|
+
function sendBlockResponse(req, res, ip) {
|
|
424
|
+
const code = (_options && _options.statusCode) || 403;
|
|
425
|
+
const accept = req.headers['accept'] || '';
|
|
426
|
+
if (accept.includes('text/html')) {
|
|
427
|
+
res.writeHead(code, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
428
|
+
res.end(blockedHtml(ip));
|
|
429
|
+
} else {
|
|
430
|
+
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
431
|
+
res.end(JSON.stringify({ error: 'Forbidden', ip }));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
258
435
|
function firewallRequestHandler(req, res) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
436
|
+
const ip = resolveClientIp(req);
|
|
437
|
+
|
|
438
|
+
// Allowlist check: if active, only listed IPs are allowed through
|
|
439
|
+
if (_allowlistMatcher && _allowlistMatcher.stats().total > 0) {
|
|
440
|
+
if (!_allowlistMatcher.isBlocked(ip)) {
|
|
262
441
|
_stats.blocked++;
|
|
263
|
-
if (_options && _options.log) console.log('[securenow] Firewall: blocked %s via HTTP', ip);
|
|
264
|
-
|
|
265
|
-
const accept = req.headers['accept'] || '';
|
|
266
|
-
if (accept.includes('text/html')) {
|
|
267
|
-
res.writeHead(code, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
268
|
-
res.end(blockedHtml(ip));
|
|
269
|
-
} else {
|
|
270
|
-
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
271
|
-
res.end(JSON.stringify({ error: 'Forbidden', ip }));
|
|
272
|
-
}
|
|
442
|
+
if (_options && _options.log) console.log('[securenow] Firewall: blocked %s via HTTP (not in allowlist)', ip);
|
|
443
|
+
sendBlockResponse(req, res, ip);
|
|
273
444
|
return true;
|
|
274
445
|
}
|
|
446
|
+
// IP is on the allowlist — skip blocklist check, allow through
|
|
447
|
+
return false;
|
|
275
448
|
}
|
|
449
|
+
|
|
450
|
+
// Blocklist check
|
|
451
|
+
if (_matcher && _matcher.isBlocked(ip)) {
|
|
452
|
+
_stats.blocked++;
|
|
453
|
+
if (_options && _options.log) console.log('[securenow] Firewall: blocked %s via HTTP', ip);
|
|
454
|
+
sendBlockResponse(req, res, ip);
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
276
458
|
return false;
|
|
277
459
|
}
|
|
278
460
|
|
|
@@ -330,7 +512,7 @@ function init(options) {
|
|
|
330
512
|
if (_options.tcp) {
|
|
331
513
|
try {
|
|
332
514
|
const tcpLayer = require('./firewall-tcp');
|
|
333
|
-
tcpLayer.init(() => _matcher, _options);
|
|
515
|
+
tcpLayer.init(() => _matcher, _options, () => _allowlistMatcher);
|
|
334
516
|
_layers.push(tcpLayer);
|
|
335
517
|
if (_options.log) console.log('[securenow] Firewall: Layer 2 (TCP drop) active');
|
|
336
518
|
} catch (e) {
|
|
@@ -373,6 +555,7 @@ function init(options) {
|
|
|
373
555
|
|
|
374
556
|
function shutdown() {
|
|
375
557
|
if (_versionTimer) { clearTimeout(_versionTimer); _versionTimer = null; }
|
|
558
|
+
if (_allowlistVersionTimer) { clearTimeout(_allowlistVersionTimer); _allowlistVersionTimer = null; }
|
|
376
559
|
if (_syncTimer) { clearInterval(_syncTimer); _syncTimer = null; }
|
|
377
560
|
|
|
378
561
|
for (const layer of _layers) {
|
|
@@ -392,9 +575,15 @@ function shutdown() {
|
|
|
392
575
|
}
|
|
393
576
|
|
|
394
577
|
function getStats() {
|
|
395
|
-
return {
|
|
578
|
+
return {
|
|
579
|
+
..._stats,
|
|
580
|
+
matcher: _matcher ? _matcher.stats() : null,
|
|
581
|
+
allowlistMatcher: _allowlistMatcher ? _allowlistMatcher.stats() : null,
|
|
582
|
+
initialized: _initialized,
|
|
583
|
+
};
|
|
396
584
|
}
|
|
397
585
|
|
|
398
586
|
function getMatcher() { return _matcher; }
|
|
587
|
+
function getAllowlistMatcher() { return _allowlistMatcher; }
|
|
399
588
|
|
|
400
|
-
module.exports = { init, shutdown, getStats, getMatcher };
|
|
589
|
+
module.exports = { init, shutdown, getStats, getMatcher, getAllowlistMatcher };
|
package/nextjs-webpack-config.js
CHANGED
|
@@ -1,33 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Next.js configuration helpers for SecureNow
|
|
5
|
+
*
|
|
6
|
+
* Usage (recommended — zero-list approach):
|
|
7
|
+
*
|
|
8
|
+
* const { withSecureNow } = require('securenow/nextjs-webpack-config');
|
|
9
|
+
* module.exports = withSecureNow({
|
|
10
|
+
* // your existing next.config options
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* Legacy webpack-only helper (still exported for backwards compat):
|
|
14
|
+
*
|
|
15
|
+
* const { getSecureNowWebpackConfig } = require('securenow/nextjs-webpack-config');
|
|
16
|
+
* module.exports = { webpack: (config, opts) => getSecureNowWebpackConfig(config, opts) };
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const EXTERNAL_PACKAGES = [
|
|
20
|
+
'securenow',
|
|
21
|
+
'@opentelemetry/sdk-node',
|
|
22
|
+
'@opentelemetry/auto-instrumentations-node',
|
|
23
|
+
'@opentelemetry/instrumentation-http',
|
|
24
|
+
'@opentelemetry/exporter-trace-otlp-http',
|
|
25
|
+
'@opentelemetry/exporter-logs-otlp-http',
|
|
26
|
+
'@opentelemetry/sdk-logs',
|
|
27
|
+
'@opentelemetry/instrumentation',
|
|
28
|
+
'@opentelemetry/resources',
|
|
29
|
+
'@opentelemetry/semantic-conventions',
|
|
30
|
+
'@opentelemetry/api',
|
|
31
|
+
'@opentelemetry/api-logs',
|
|
32
|
+
'@vercel/otel',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function detectNextMajor() {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = require('next/package.json');
|
|
38
|
+
return parseInt(pkg.version, 10) || 14;
|
|
39
|
+
} catch {
|
|
40
|
+
return 14;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
1
44
|
/**
|
|
2
|
-
* Next.js
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* const { getSecureNowWebpackConfig } = require('securenow/nextjs-webpack-config');
|
|
8
|
-
*
|
|
9
|
-
* module.exports = {
|
|
10
|
-
* webpack: (config, options) => {
|
|
11
|
-
* return getSecureNowWebpackConfig(config, options);
|
|
12
|
-
* }
|
|
13
|
-
* };
|
|
45
|
+
* Wrap a Next.js config object to auto-externalize SecureNow + OTel
|
|
46
|
+
* packages and enable the instrumentation hook.
|
|
47
|
+
*
|
|
48
|
+
* module.exports = withSecureNow({ reactStrictMode: true });
|
|
14
49
|
*/
|
|
50
|
+
function withSecureNow(userConfig) {
|
|
51
|
+
if (typeof userConfig === 'function') {
|
|
52
|
+
return (...args) => withSecureNow(userConfig(...args));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const cfg = { ...userConfig };
|
|
56
|
+
const major = detectNextMajor();
|
|
57
|
+
|
|
58
|
+
if (major >= 15) {
|
|
59
|
+
cfg.serverExternalPackages = dedup([
|
|
60
|
+
...(cfg.serverExternalPackages || []),
|
|
61
|
+
...EXTERNAL_PACKAGES,
|
|
62
|
+
]);
|
|
63
|
+
} else {
|
|
64
|
+
cfg.experimental = { ...(cfg.experimental || {}) };
|
|
65
|
+
cfg.experimental.instrumentationHook = true;
|
|
66
|
+
cfg.experimental.serverComponentsExternalPackages = dedup([
|
|
67
|
+
...(cfg.experimental.serverComponentsExternalPackages || []),
|
|
68
|
+
...EXTERNAL_PACKAGES,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const origWebpack = cfg.webpack;
|
|
73
|
+
cfg.webpack = (config, options) => {
|
|
74
|
+
const c = origWebpack ? origWebpack(config, options) : config;
|
|
75
|
+
return getSecureNowWebpackConfig(c, options);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return cfg;
|
|
79
|
+
}
|
|
15
80
|
|
|
81
|
+
function dedup(arr) {
|
|
82
|
+
return [...new Set(arr)];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Legacy: suppress OTel webpack warnings and add externals.
|
|
87
|
+
*/
|
|
16
88
|
function getSecureNowWebpackConfig(config, options) {
|
|
17
89
|
const { isServer } = options;
|
|
18
|
-
|
|
19
|
-
// Only apply to server-side builds
|
|
90
|
+
|
|
20
91
|
if (isServer) {
|
|
21
|
-
// Suppress warnings for OpenTelemetry instrumentations
|
|
22
92
|
config.ignoreWarnings = config.ignoreWarnings || [];
|
|
23
|
-
|
|
24
93
|
config.ignoreWarnings.push(
|
|
25
|
-
// Ignore "Critical dependency" warnings from instrumentations
|
|
26
94
|
{
|
|
27
95
|
module: /@opentelemetry\/instrumentation/,
|
|
28
96
|
message: /Critical dependency: the request of a dependency is an expression/,
|
|
29
97
|
},
|
|
30
|
-
// Ignore missing optional peer dependencies
|
|
31
98
|
{
|
|
32
99
|
module: /@opentelemetry/,
|
|
33
100
|
message: /Module not found.*@opentelemetry\/winston-transport/,
|
|
@@ -35,43 +102,11 @@ function getSecureNowWebpackConfig(config, options) {
|
|
|
35
102
|
{
|
|
36
103
|
module: /@opentelemetry/,
|
|
37
104
|
message: /Module not found.*@opentelemetry\/exporter-jaeger/,
|
|
38
|
-
}
|
|
105
|
+
},
|
|
39
106
|
);
|
|
40
|
-
|
|
41
|
-
// Externalize problematic packages (don't bundle them)
|
|
42
|
-
config.externals = config.externals || [];
|
|
43
|
-
|
|
44
|
-
// Add OpenTelemetry packages as externals
|
|
45
|
-
if (typeof config.externals === 'function') {
|
|
46
|
-
const originalExternals = config.externals;
|
|
47
|
-
config.externals = async (...args) => {
|
|
48
|
-
const result = await originalExternals(...args);
|
|
49
|
-
if (result) return result;
|
|
50
|
-
|
|
51
|
-
const [context, request] = args;
|
|
52
|
-
|
|
53
|
-
// Externalize OpenTelemetry instrumentation packages
|
|
54
|
-
if (request.startsWith('@opentelemetry/')) {
|
|
55
|
-
return `commonjs ${request}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return undefined;
|
|
59
|
-
};
|
|
60
|
-
} else if (Array.isArray(config.externals)) {
|
|
61
|
-
config.externals.push(/@opentelemetry\//);
|
|
62
|
-
} else {
|
|
63
|
-
config.externals = [/@opentelemetry\//];
|
|
64
|
-
}
|
|
65
107
|
}
|
|
66
|
-
|
|
108
|
+
|
|
67
109
|
return config;
|
|
68
110
|
}
|
|
69
111
|
|
|
70
|
-
module.exports = { getSecureNowWebpackConfig };
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
module.exports = { withSecureNow, getSecureNowWebpackConfig, EXTERNAL_PACKAGES };
|
package/nuxt-server-plugin.mjs
CHANGED
|
@@ -324,11 +324,34 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
324
324
|
// not critical
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// ── Firewall — runs independently from OTel ──
|
|
328
|
+
const firewallApiKey = env('SECURENOW_API_KEY');
|
|
329
|
+
if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
|
|
330
|
+
try {
|
|
331
|
+
const { init: fwInit } = await import('./firewall.js');
|
|
332
|
+
fwInit({
|
|
333
|
+
apiKey: firewallApiKey,
|
|
334
|
+
apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
|
|
335
|
+
versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
|
|
336
|
+
syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
|
|
337
|
+
failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
|
|
338
|
+
statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
|
|
339
|
+
log: env('SECURENOW_FIREWALL_LOG') !== '0',
|
|
340
|
+
tcp: env('SECURENOW_FIREWALL_TCP') === '1',
|
|
341
|
+
iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
|
|
342
|
+
cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
|
|
343
|
+
});
|
|
344
|
+
} catch (e) {
|
|
345
|
+
console.warn('[securenow] Firewall init failed:', e.message);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
327
349
|
// ── Graceful shutdown ──
|
|
328
350
|
const shutdown = async (sig) => {
|
|
329
351
|
try {
|
|
330
352
|
await sdk.shutdown?.();
|
|
331
353
|
if (loggerProvider) await loggerProvider.shutdown?.();
|
|
354
|
+
try { const fw = await import('./firewall.js'); fw.shutdown?.(); } catch {}
|
|
332
355
|
console.log(`[securenow] Shut down on ${sig}`);
|
|
333
356
|
} catch {
|
|
334
357
|
// swallow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.15.0",
|
|
4
4
|
"description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "register.js",
|
|
@@ -81,6 +81,9 @@
|
|
|
81
81
|
"./firewall": {
|
|
82
82
|
"default": "./firewall.js"
|
|
83
83
|
},
|
|
84
|
+
"./firewall-only": {
|
|
85
|
+
"default": "./firewall-only.js"
|
|
86
|
+
},
|
|
84
87
|
"./cidr": {
|
|
85
88
|
"default": "./cidr.js"
|
|
86
89
|
},
|
|
@@ -117,6 +120,7 @@
|
|
|
117
120
|
"resolve-ip.js",
|
|
118
121
|
"cidr.js",
|
|
119
122
|
"firewall.js",
|
|
123
|
+
"firewall-only.js",
|
|
120
124
|
"firewall-tcp.js",
|
|
121
125
|
"firewall-iptables.js",
|
|
122
126
|
"firewall-cloud.js",
|
|
@@ -127,7 +131,9 @@
|
|
|
127
131
|
"docs/",
|
|
128
132
|
"README.md",
|
|
129
133
|
"NPM_README.md",
|
|
130
|
-
"CONSUMING-APPS-GUIDE.md"
|
|
134
|
+
"CONSUMING-APPS-GUIDE.md",
|
|
135
|
+
"SKILL-CLI.md",
|
|
136
|
+
"SKILL-API.md"
|
|
131
137
|
],
|
|
132
138
|
"dependencies": {
|
|
133
139
|
"@opentelemetry/api": "1.7.0",
|