securenow 6.0.2 → 6.1.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.
Files changed (87) hide show
  1. package/CONSUMING-APPS-GUIDE.md +455 -0
  2. package/NPM_README.md +2029 -0
  3. package/README.md +297 -40
  4. package/SKILL-API.md +634 -0
  5. package/SKILL-CLI.md +454 -0
  6. package/cidr.js +83 -0
  7. package/cli/apps.js +585 -0
  8. package/cli/auth.js +280 -0
  9. package/cli/client.js +115 -0
  10. package/cli/config.js +173 -0
  11. package/cli/diagnostics.js +387 -0
  12. package/cli/firewall.js +100 -0
  13. package/cli/fp.js +638 -0
  14. package/cli/init.js +201 -0
  15. package/cli/monitor.js +440 -0
  16. package/cli/run.js +148 -0
  17. package/cli/security.js +980 -0
  18. package/cli/ui.js +386 -0
  19. package/cli/utils.js +127 -0
  20. package/cli.js +466 -455
  21. package/console-instrumentation.js +147 -136
  22. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +1377 -455
  23. package/docs/API-KEYS-GUIDE.md +233 -0
  24. package/docs/ARCHITECTURE.md +3 -3
  25. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  26. package/docs/AUTO-SETUP-SUMMARY.md +331 -0
  27. package/docs/AUTO-SETUP.md +4 -4
  28. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  29. package/docs/BODY-CAPTURE-FIX.md +261 -0
  30. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  31. package/docs/CHANGELOG-NEXTJS.md +1 -35
  32. package/docs/COMPLETION-REPORT.md +408 -0
  33. package/docs/CUSTOMER-GUIDE.md +16 -16
  34. package/docs/EASIEST-SETUP.md +5 -5
  35. package/docs/ENVIRONMENT-VARIABLES.md +880 -652
  36. package/docs/EXPRESS-BODY-CAPTURE.md +13 -12
  37. package/docs/EXPRESS-SETUP-GUIDE.md +719 -720
  38. package/docs/FINAL-SOLUTION.md +335 -0
  39. package/docs/FIREWALL-GUIDE.md +426 -0
  40. package/docs/IMPLEMENTATION-SUMMARY.md +410 -0
  41. package/docs/INDEX.md +22 -4
  42. package/docs/LOGGING-GUIDE.md +701 -708
  43. package/docs/LOGGING-QUICKSTART.md +234 -255
  44. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +323 -0
  45. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  46. package/docs/NEXTJS-GUIDE.md +14 -14
  47. package/docs/NEXTJS-QUICKSTART.md +1 -1
  48. package/docs/NEXTJS-SETUP-COMPLETE.md +795 -0
  49. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  50. package/docs/NUXT-GUIDE.md +166 -0
  51. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  52. package/docs/REDACTION-EXAMPLES.md +1 -1
  53. package/docs/REQUEST-BODY-CAPTURE.md +19 -10
  54. package/docs/SOLUTION-SUMMARY.md +312 -0
  55. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  56. package/examples/README.md +6 -6
  57. package/examples/instrumentation-with-auto-capture.ts +1 -1
  58. package/examples/nextjs-env-example.txt +2 -2
  59. package/examples/nextjs-instrumentation.js +1 -1
  60. package/examples/nextjs-instrumentation.ts +1 -1
  61. package/examples/nextjs-with-logging-example.md +6 -6
  62. package/examples/nextjs-with-options.ts +1 -1
  63. package/examples/test-nextjs-setup.js +1 -1
  64. package/firewall-cloud.js +212 -0
  65. package/firewall-iptables.js +139 -0
  66. package/firewall-only.js +38 -0
  67. package/firewall-tcp.js +74 -0
  68. package/firewall.js +720 -0
  69. package/free-trial-banner.js +174 -0
  70. package/nextjs-auto-capture.js +199 -207
  71. package/nextjs-middleware.js +186 -181
  72. package/nextjs-webpack-config.js +88 -53
  73. package/nextjs-wrapper.js +158 -158
  74. package/nextjs.d.ts +1 -1
  75. package/nextjs.js +639 -647
  76. package/nuxt-server-plugin.mjs +423 -0
  77. package/nuxt.d.ts +60 -0
  78. package/nuxt.mjs +75 -0
  79. package/package.json +186 -164
  80. package/postinstall.js +6 -6
  81. package/register.d.ts +1 -1
  82. package/register.js +39 -4
  83. package/resolve-ip.js +77 -0
  84. package/tracing.d.ts +2 -1
  85. package/tracing.js +295 -34
  86. package/web-vite.mjs +239 -156
  87. package/LICENSE +0 -15
@@ -21,7 +21,7 @@ export function register() {
21
21
  * SECURENOW_APPID=my-nextjs-app
22
22
  *
23
23
  * Optional:
24
- * SECURENOW_INSTANCE=http://your-signoz-server:4318
24
+ * SECURENOW_INSTANCE=http://your-otlp-collector:4318
25
25
  * SECURENOW_NO_UUID=1 # Don't append UUID to service name
26
26
  * OTEL_LOG_LEVEL=info # debug|info|warn|error
27
27
  * SECURENOW_DISABLE_INSTRUMENTATIONS=fs # Comma-separated list
@@ -21,7 +21,7 @@ export function register() {
21
21
  * SECURENOW_APPID=my-nextjs-app
22
22
  *
23
23
  * Optional:
24
- * SECURENOW_INSTANCE=http://your-signoz-server:4318
24
+ * SECURENOW_INSTANCE=http://your-otlp-collector:4318
25
25
  * SECURENOW_NO_UUID=1 # Don't append UUID to service name
26
26
  * OTEL_LOG_LEVEL=info # debug|info|warn|error
27
27
  * SECURENOW_DISABLE_INSTRUMENTATIONS=fs # Comma-separated list
@@ -39,9 +39,9 @@ SECURENOW_LOGGING_ENABLED=1
39
39
  SECURENOW_APPID=my-nextjs-app
40
40
  SECURENOW_INSTANCE=http://localhost:4318
41
41
 
42
- # For SigNoz Cloud:
43
- # SECURENOW_INSTANCE=https://ingest.<region>.signoz.cloud:443
44
- # OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-key>"
42
+ # For SecureNow or managed OTLP (example):
43
+ # SECURENOW_INSTANCE=https://ingest.<region>.securenow.ai:443
44
+ # OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<your-key>"
45
45
  ```
46
46
 
47
47
  ### 4. Enable Instrumentation in `next.config.js`
@@ -239,9 +239,9 @@ You should see in the console:
239
239
  SecureNow initialized with logging enabled
240
240
  ```
241
241
 
242
- ## View Logs in SigNoz
242
+ ## View Logs in SecureNow
243
243
 
244
- 1. Open your SigNoz dashboard
244
+ 1. Open your SecureNow dashboard
245
245
  2. Navigate to **Logs** section
246
246
  3. Filter by `service.name = my-nextjs-app`
247
247
  4. See all your application logs with:
@@ -298,4 +298,4 @@ await import('securenow/console-instrumentation'); // Second
298
298
 
299
299
  - [Complete Logging Guide](../docs/LOGGING-GUIDE.md)
300
300
  - [Next.js Complete Guide](../docs/NEXTJS-GUIDE.md)
301
- - [View Logs in SigNoz](https://signoz.io/docs/logs-management/overview/)
301
+ - [SecureNow](https://securenow.ai/)
@@ -10,7 +10,7 @@ import { registerSecureNow } from 'securenow/nextjs';
10
10
  export function register() {
11
11
  registerSecureNow({
12
12
  serviceName: 'my-nextjs-app',
13
- endpoint: 'http://your-signoz-server:4318',
13
+ endpoint: 'http://your-otlp-collector:4318',
14
14
  noUuid: false,
15
15
  disableInstrumentations: ['fs', 'dns'],
16
16
  headers: {
@@ -58,7 +58,7 @@ setTimeout(() => {
58
58
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
59
59
  console.log('✅ All tests passed!');
60
60
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
61
- console.log('\nCheck your SigNoz dashboard for traces from "test-nextjs-app"\n');
61
+ console.log('\nCheck your SecureNow dashboard for traces from "test-nextjs-app"\n');
62
62
  process.exit(0);
63
63
  }, 2000);
64
64
 
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Layer 4: Cloud/Edge WAF integration.
5
+ * Pushes the blocklist to the customer's cloud WAF provider so traffic is
6
+ * blocked at the CDN/edge before it ever reaches the origin server.
7
+ *
8
+ * Supported providers: cloudflare, aws, gcp
9
+ * Cloud SDKs are optional peer dependencies — only required when enabled.
10
+ * Cloud credentials are never logged.
11
+ */
12
+
13
+ const https = require('https');
14
+
15
+ let _options = null;
16
+ let _active = false;
17
+ let _provider = null;
18
+ let _lastPushTime = 0;
19
+
20
+ const MIN_PUSH_INTERVAL_MS = 30000;
21
+
22
+ // ────── Cloudflare ──────
23
+
24
+ async function cloudflareSync(ips) {
25
+ const token = process.env.CLOUDFLARE_API_TOKEN;
26
+ const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
27
+ if (!token || !accountId) throw new Error('Missing CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID');
28
+
29
+ const listName = 'securenow-blocklist';
30
+
31
+ const lists = await cfApi('GET', `/accounts/${accountId}/rules/lists`, token);
32
+ let list = lists.result?.find(l => l.name === listName);
33
+
34
+ if (!list) {
35
+ const created = await cfApi('POST', `/accounts/${accountId}/rules/lists`, token, {
36
+ name: listName,
37
+ kind: 'ip',
38
+ description: 'Managed by SecureNow firewall SDK',
39
+ });
40
+ list = created.result;
41
+ }
42
+
43
+ const items = ips.map(ip => ({ ip: ip.includes('/') ? ip : ip + '/32' }));
44
+
45
+ await cfApi('PUT', `/accounts/${accountId}/rules/lists/${list.id}/items`, token, items);
46
+ }
47
+
48
+ function cfApi(method, path, token, body) {
49
+ return new Promise((resolve, reject) => {
50
+ const data = body ? JSON.stringify(body) : null;
51
+ const req = https.request({
52
+ hostname: 'api.cloudflare.com',
53
+ path: '/client/v4' + path,
54
+ method,
55
+ headers: {
56
+ 'Authorization': `Bearer ${token}`,
57
+ 'Content-Type': 'application/json',
58
+ ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
59
+ },
60
+ timeout: 15000,
61
+ }, (res) => {
62
+ let chunks = '';
63
+ res.on('data', c => chunks += c);
64
+ res.on('end', () => {
65
+ try { resolve(JSON.parse(chunks)); } catch (e) { reject(new Error(`CF parse error: ${chunks.slice(0, 200)}`)); }
66
+ });
67
+ });
68
+ req.on('error', reject);
69
+ req.on('timeout', () => { req.destroy(); reject(new Error('Cloudflare API timeout')); });
70
+ if (data) req.write(data);
71
+ req.end();
72
+ });
73
+ }
74
+
75
+ // ────── AWS WAF ──────
76
+
77
+ async function awsSync(ips) {
78
+ let WAFV2;
79
+ try {
80
+ WAFV2 = require('@aws-sdk/client-wafv2');
81
+ } catch {
82
+ throw new Error('AWS WAF requires @aws-sdk/client-wafv2 — install it: npm i @aws-sdk/client-wafv2');
83
+ }
84
+
85
+ const ipSetId = process.env.AWS_WAF_IP_SET_ID;
86
+ const ipSetName = process.env.AWS_WAF_IP_SET_NAME || 'securenow-blocklist';
87
+ const scope = process.env.AWS_WAF_SCOPE || 'REGIONAL';
88
+ if (!ipSetId) throw new Error('Missing AWS_WAF_IP_SET_ID');
89
+
90
+ const client = new WAFV2.WAFV2Client({});
91
+
92
+ const { IPSet, LockToken } = await client.send(new WAFV2.GetIPSetCommand({
93
+ Name: ipSetName,
94
+ Scope: scope,
95
+ Id: ipSetId,
96
+ }));
97
+
98
+ const addresses = ips.map(ip => ip.includes('/') ? ip : ip + '/32');
99
+
100
+ await client.send(new WAFV2.UpdateIPSetCommand({
101
+ Name: ipSetName,
102
+ Scope: scope,
103
+ Id: ipSetId,
104
+ Addresses: addresses,
105
+ LockToken,
106
+ Description: 'Managed by SecureNow firewall SDK',
107
+ }));
108
+ }
109
+
110
+ // ────── GCP Cloud Armor ──────
111
+
112
+ async function gcpSync(ips) {
113
+ let compute;
114
+ try {
115
+ compute = require('@google-cloud/compute');
116
+ } catch {
117
+ throw new Error('GCP Cloud Armor requires @google-cloud/compute — install it: npm i @google-cloud/compute');
118
+ }
119
+
120
+ const project = process.env.GCP_PROJECT_ID;
121
+ const policyName = process.env.GCP_SECURITY_POLICY;
122
+ if (!project || !policyName) throw new Error('Missing GCP_PROJECT_ID or GCP_SECURITY_POLICY');
123
+
124
+ const client = new compute.SecurityPoliciesClient();
125
+ const rulePriority = 2000;
126
+
127
+ const srcRanges = ips.map(ip => ip.includes('/') ? ip : ip + '/32');
128
+
129
+ try {
130
+ await client.patchRule({
131
+ project,
132
+ securityPolicy: policyName,
133
+ priority: String(rulePriority),
134
+ securityPolicyRuleResource: {
135
+ priority: rulePriority,
136
+ action: 'deny(403)',
137
+ match: { versionedExpr: 'SRC_IPS_V1', config: { srcIpRanges: srcRanges } },
138
+ description: 'Managed by SecureNow firewall SDK',
139
+ },
140
+ });
141
+ } catch (e) {
142
+ if (e.code === 404 || e.message?.includes('not found')) {
143
+ await client.addRule({
144
+ project,
145
+ securityPolicy: policyName,
146
+ securityPolicyRuleResource: {
147
+ priority: rulePriority,
148
+ action: 'deny(403)',
149
+ match: { versionedExpr: 'SRC_IPS_V1', config: { srcIpRanges: srcRanges } },
150
+ description: 'Managed by SecureNow firewall SDK',
151
+ },
152
+ });
153
+ } else {
154
+ throw e;
155
+ }
156
+ }
157
+ }
158
+
159
+ // ────── Provider dispatch ──────
160
+
161
+ const PROVIDERS = {
162
+ cloudflare: cloudflareSync,
163
+ aws: awsSync,
164
+ gcp: gcpSync,
165
+ };
166
+
167
+ // ────── Public API ──────
168
+
169
+ function init(options) {
170
+ _options = options;
171
+ const provider = (options.cloud || '').toLowerCase();
172
+
173
+ if (!PROVIDERS[provider]) {
174
+ if (_options.log) console.warn('[securenow] Firewall cloud: unknown provider "%s", expected cloudflare|aws|gcp', provider);
175
+ return;
176
+ }
177
+
178
+ _provider = provider;
179
+ _active = true;
180
+ }
181
+
182
+ /**
183
+ * Called by the firewall core after each successful blocklist sync.
184
+ * Throttled to max 1 push per MIN_PUSH_INTERVAL_MS.
185
+ * @param {string[]} ips - Array of IPs and CIDRs to push
186
+ */
187
+ async function sync(ips) {
188
+ if (!_active || !_provider) return;
189
+
190
+ const now = Date.now();
191
+ if (now - _lastPushTime < MIN_PUSH_INTERVAL_MS) return;
192
+ _lastPushTime = now;
193
+
194
+ const dryRun = process.env.SECURENOW_FIREWALL_CLOUD_DRY_RUN === '1';
195
+ if (dryRun) {
196
+ if (_options.log) console.log('[securenow] Firewall cloud: dry-run — would push %d IPs to %s', ips.length, _provider);
197
+ return;
198
+ }
199
+
200
+ try {
201
+ await PROVIDERS[_provider](ips);
202
+ if (_options.log) console.log('[securenow] Firewall cloud: pushed %d IPs to %s', ips.length, _provider);
203
+ } catch (e) {
204
+ if (_options.log) console.warn('[securenow] Firewall cloud: push to %s failed:', _provider, e.message);
205
+ }
206
+ }
207
+
208
+ function shutdown() {
209
+ _active = false;
210
+ }
211
+
212
+ module.exports = { init, sync, shutdown };
@@ -0,0 +1,139 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Layer 3: OS-level firewall via iptables/nftables.
5
+ * Manages a dedicated SECURENOW_BLOCK chain — never touches customer rules.
6
+ * Linux only, requires root or CAP_NET_ADMIN. Falls back gracefully otherwise.
7
+ */
8
+
9
+ const { execSync } = require('child_process');
10
+ const os = require('os');
11
+
12
+ const CHAIN_NAME = 'SECURENOW_BLOCK';
13
+
14
+ let _options = null;
15
+ let _active = false;
16
+ let _useNft = false;
17
+
18
+ function exec(cmd) {
19
+ return execSync(cmd, { stdio: 'pipe', timeout: 10000 }).toString().trim();
20
+ }
21
+
22
+ function canRun(cmd) {
23
+ try { exec(cmd); return true; } catch { return false; }
24
+ }
25
+
26
+ function detectBackend() {
27
+ if (canRun('nft --version')) return 'nft';
28
+ if (canRun('iptables --version')) return 'iptables';
29
+ return null;
30
+ }
31
+
32
+ // ────── iptables backend ──────
33
+
34
+ function iptablesSetup() {
35
+ try { exec(`iptables -N ${CHAIN_NAME}`); } catch (_) {} // chain may already exist
36
+ try { exec(`iptables -C INPUT -j ${CHAIN_NAME}`); } catch (_) {
37
+ exec(`iptables -I INPUT 1 -j ${CHAIN_NAME}`);
38
+ }
39
+ }
40
+
41
+ function iptablesSync(ips) {
42
+ exec(`iptables -F ${CHAIN_NAME}`);
43
+ for (const ip of ips) {
44
+ exec(`iptables -A ${CHAIN_NAME} -s ${ip} -j DROP`);
45
+ }
46
+ }
47
+
48
+ function iptablesCleanup() {
49
+ try { exec(`iptables -D INPUT -j ${CHAIN_NAME}`); } catch (_) {}
50
+ try { exec(`iptables -F ${CHAIN_NAME}`); } catch (_) {}
51
+ try { exec(`iptables -X ${CHAIN_NAME}`); } catch (_) {}
52
+ }
53
+
54
+ // ────── nftables backend ──────
55
+
56
+ function nftSetup() {
57
+ try { exec(`nft list chain ip filter ${CHAIN_NAME}`); } catch (_) {
58
+ exec(`nft add chain ip filter ${CHAIN_NAME}`);
59
+ }
60
+ try { exec(`nft insert rule ip filter INPUT jump ${CHAIN_NAME}`); } catch (_) {}
61
+ }
62
+
63
+ function nftSync(ips) {
64
+ exec(`nft flush chain ip filter ${CHAIN_NAME}`);
65
+ for (const ip of ips) {
66
+ exec(`nft add rule ip filter ${CHAIN_NAME} ip saddr ${ip} drop`);
67
+ }
68
+ }
69
+
70
+ function nftCleanup() {
71
+ try { exec(`nft flush chain ip filter ${CHAIN_NAME}`); } catch (_) {}
72
+ try { exec(`nft delete chain ip filter ${CHAIN_NAME}`); } catch (_) {}
73
+ }
74
+
75
+ // ────── Public API ──────
76
+
77
+ let _syncCallback = null;
78
+
79
+ function init(options) {
80
+ _options = options;
81
+
82
+ if (os.platform() !== 'linux') {
83
+ if (_options.log) console.warn('[securenow] Firewall iptables: only supported on Linux, skipping');
84
+ return;
85
+ }
86
+
87
+ const backend = detectBackend();
88
+ if (!backend) {
89
+ if (_options.log) console.warn('[securenow] Firewall iptables: neither iptables nor nft found, skipping');
90
+ return;
91
+ }
92
+
93
+ _useNft = backend === 'nft';
94
+ const label = _useNft ? 'nftables' : 'iptables';
95
+
96
+ try {
97
+ if (_useNft) nftSetup(); else iptablesSetup();
98
+ _active = true;
99
+ if (_options.log) console.log('[securenow] Firewall iptables: %s chain %s ready', label, CHAIN_NAME);
100
+ } catch (e) {
101
+ if (_options.log) console.warn('[securenow] Firewall iptables: setup failed (need root or CAP_NET_ADMIN):', e.message);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Called by the firewall core after each successful blocklist sync.
107
+ * @param {string[]} ips - Array of IPs and CIDRs to block
108
+ */
109
+ function sync(ips) {
110
+ if (!_active) return;
111
+
112
+ const maxRules = (_options && _options.iptablesMax) || 10000;
113
+ const limited = ips.length > maxRules ? ips.slice(0, maxRules) : ips;
114
+
115
+ if (ips.length > maxRules && _options.log) {
116
+ console.warn('[securenow] Firewall iptables: truncated to %d rules (max %d)', maxRules, maxRules);
117
+ }
118
+
119
+ try {
120
+ if (_useNft) nftSync(limited); else iptablesSync(limited);
121
+ } catch (e) {
122
+ if (_options && _options.log) {
123
+ console.warn('[securenow] Firewall iptables: sync failed:', e.message);
124
+ }
125
+ }
126
+ }
127
+
128
+ function shutdown() {
129
+ if (!_active) return;
130
+ try {
131
+ if (_useNft) nftCleanup(); else iptablesCleanup();
132
+ if (_options && _options.log) console.log('[securenow] Firewall iptables: chain %s cleaned up', CHAIN_NAME);
133
+ } catch (e) {
134
+ if (_options && _options.log) console.warn('[securenow] Firewall iptables: cleanup failed:', e.message);
135
+ }
136
+ _active = false;
137
+ }
138
+
139
+ module.exports = { init, sync, shutdown };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Standalone firewall preload — no OpenTelemetry, no tracing.
5
+ *
6
+ * Usage:
7
+ * node -r securenow/firewall-only app.js
8
+ * NODE_OPTIONS='-r securenow/firewall-only' next start
9
+ *
10
+ * Reads .env via dotenv (if installed), then initialises the HTTP-level
11
+ * firewall when SECURENOW_API_KEY is present.
12
+ */
13
+
14
+ try { require('dotenv').config(); } catch (_) {}
15
+
16
+ const env = (k) =>
17
+ process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
18
+
19
+ const firewallApiKey = env('SECURENOW_API_KEY');
20
+
21
+ if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
22
+ require('./firewall').init({
23
+ apiKey: firewallApiKey,
24
+ apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
25
+ versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
26
+ syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
27
+ failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
28
+ statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
29
+ log: env('SECURENOW_FIREWALL_LOG') !== '0',
30
+ tcp: env('SECURENOW_FIREWALL_TCP') === '1',
31
+ iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
32
+ cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
33
+ });
34
+
35
+ const shutdown = () => { try { require('./firewall').shutdown(); } catch (_) {} };
36
+ process.on('SIGINT', shutdown);
37
+ process.on('SIGTERM', shutdown);
38
+ }
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Layer 2: TCP-level blocking via net.Server connection event.
5
+ * Destroys the socket before HTTP parsing starts. Zero bytes sent back.
6
+ *
7
+ * Caveat: only sees socket.remoteAddress (direct connection IP).
8
+ * If behind a reverse proxy, the proxy IP is seen instead.
9
+ * Proxy IPs are skipped (let through to Layer 1 for proper header-based resolution).
10
+ */
11
+
12
+ const net = require('net');
13
+ const { resolveSocketIp, isFromTrustedProxy } = require('./resolve-ip');
14
+
15
+ let _getMatcher = null;
16
+ let _getAllowlistMatcher = null;
17
+ let _options = null;
18
+ let _patched = false;
19
+ const _origListen = net.Server.prototype.listen;
20
+
21
+ function onConnection(socket) {
22
+ const ip = resolveSocketIp(socket);
23
+
24
+ // Skip if the connection is from a trusted proxy — Layer 1 will handle it
25
+ // with proper X-Forwarded-For resolution
26
+ if (isFromTrustedProxy(ip) || isFromTrustedProxy('::ffff:' + ip)) return;
27
+
28
+ // Allowlist check: if active, only listed IPs pass
29
+ const allowlistMatcher = _getAllowlistMatcher ? _getAllowlistMatcher() : null;
30
+ if (allowlistMatcher && allowlistMatcher.stats().total > 0) {
31
+ if (!allowlistMatcher.isBlocked(ip)) {
32
+ if (_options && _options.log) {
33
+ console.log('[securenow] Firewall: blocked %s via TCP (not in allowlist)', ip);
34
+ }
35
+ socket.destroy();
36
+ return;
37
+ }
38
+ return; // on allowlist — allow
39
+ }
40
+
41
+ // Blocklist check
42
+ const matcher = _getMatcher();
43
+ if (!matcher) return;
44
+
45
+ if (matcher.isBlocked(ip)) {
46
+ if (_options && _options.log) {
47
+ console.log('[securenow] Firewall: blocked %s via TCP (socket destroyed)', ip);
48
+ }
49
+ socket.destroy();
50
+ }
51
+ }
52
+
53
+ function init(getMatcher, options, getAllowlistMatcher) {
54
+ _getMatcher = getMatcher;
55
+ _getAllowlistMatcher = getAllowlistMatcher || null;
56
+ _options = options;
57
+
58
+ if (_patched) return;
59
+ _patched = true;
60
+
61
+ net.Server.prototype.listen = function(...args) {
62
+ this.on('connection', onConnection);
63
+ return _origListen.apply(this, args);
64
+ };
65
+ }
66
+
67
+ function shutdown() {
68
+ if (_patched) {
69
+ net.Server.prototype.listen = _origListen;
70
+ _patched = false;
71
+ }
72
+ }
73
+
74
+ module.exports = { init, shutdown };