securenow 5.17.1 → 6.0.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 (85) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +40 -243
  3. package/cli.js +455 -425
  4. package/console-instrumentation.js +136 -147
  5. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
  6. package/docs/ARCHITECTURE.md +3 -3
  7. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  8. package/docs/AUTO-SETUP.md +4 -4
  9. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  10. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  11. package/docs/CHANGELOG-NEXTJS.md +1 -1
  12. package/docs/CUSTOMER-GUIDE.md +16 -16
  13. package/docs/EASIEST-SETUP.md +5 -5
  14. package/docs/ENVIRONMENT-VARIABLES.md +652 -880
  15. package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
  16. package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
  17. package/docs/INDEX.md +4 -22
  18. package/docs/LOGGING-GUIDE.md +708 -701
  19. package/docs/LOGGING-QUICKSTART.md +239 -234
  20. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  21. package/docs/NEXTJS-GUIDE.md +14 -14
  22. package/docs/NEXTJS-QUICKSTART.md +1 -1
  23. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  24. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  25. package/docs/REDACTION-EXAMPLES.md +1 -1
  26. package/docs/REQUEST-BODY-CAPTURE.md +10 -19
  27. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  28. package/examples/README.md +6 -6
  29. package/examples/instrumentation-with-auto-capture.ts +1 -1
  30. package/examples/nextjs-env-example.txt +2 -2
  31. package/examples/nextjs-instrumentation.js +1 -1
  32. package/examples/nextjs-instrumentation.ts +1 -1
  33. package/examples/nextjs-with-logging-example.md +6 -6
  34. package/examples/nextjs-with-options.ts +1 -1
  35. package/examples/test-nextjs-setup.js +1 -1
  36. package/nextjs-auto-capture.js +207 -199
  37. package/nextjs-middleware.js +181 -186
  38. package/nextjs-webpack-config.js +53 -88
  39. package/nextjs-wrapper.js +158 -158
  40. package/nextjs.d.ts +1 -1
  41. package/nextjs.js +135 -190
  42. package/package.json +45 -67
  43. package/postinstall.js +6 -6
  44. package/register.d.ts +1 -1
  45. package/register.js +4 -39
  46. package/tracing.d.ts +1 -2
  47. package/tracing.js +22 -287
  48. package/web-vite.mjs +156 -239
  49. package/CONSUMING-APPS-GUIDE.md +0 -455
  50. package/NPM_README.md +0 -1958
  51. package/SKILL-API.md +0 -600
  52. package/SKILL-CLI.md +0 -419
  53. package/cidr.js +0 -83
  54. package/cli/apps.js +0 -585
  55. package/cli/auth.js +0 -280
  56. package/cli/client.js +0 -115
  57. package/cli/config.js +0 -173
  58. package/cli/firewall.js +0 -100
  59. package/cli/fp.js +0 -638
  60. package/cli/init.js +0 -201
  61. package/cli/monitor.js +0 -545
  62. package/cli/run.js +0 -133
  63. package/cli/security.js +0 -1064
  64. package/cli/ui.js +0 -386
  65. package/docs/API-KEYS-GUIDE.md +0 -233
  66. package/docs/AUTO-SETUP-SUMMARY.md +0 -331
  67. package/docs/BODY-CAPTURE-FIX.md +0 -261
  68. package/docs/COMPLETION-REPORT.md +0 -408
  69. package/docs/FINAL-SOLUTION.md +0 -335
  70. package/docs/FIREWALL-GUIDE.md +0 -426
  71. package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
  72. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
  73. package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
  74. package/docs/NUXT-GUIDE.md +0 -166
  75. package/docs/SOLUTION-SUMMARY.md +0 -312
  76. package/firewall-cloud.js +0 -212
  77. package/firewall-iptables.js +0 -139
  78. package/firewall-only.js +0 -38
  79. package/firewall-tcp.js +0 -74
  80. package/firewall.js +0 -720
  81. package/free-trial-banner.js +0 -174
  82. package/nuxt-server-plugin.mjs +0 -423
  83. package/nuxt.d.ts +0 -60
  84. package/nuxt.mjs +0 -75
  85. package/resolve-ip.js +0 -77
@@ -1,166 +0,0 @@
1
- # SecureNow — Nuxt 3 Setup Guide
2
-
3
- ## Quick Start (1 minute)
4
-
5
- ### 1. Install
6
-
7
- ```bash
8
- npm install securenow
9
- ```
10
-
11
- ### 2. Add the module to `nuxt.config.ts`
12
-
13
- ```ts
14
- export default defineNuxtConfig({
15
- modules: ['securenow/nuxt'],
16
- });
17
- ```
18
-
19
- ### 3. Set environment variables
20
-
21
- Create a `.env` file in your project root:
22
-
23
- ```env
24
- SECURENOW_APPID=my-nuxt-app
25
- SECURENOW_INSTANCE=https://freetrial.securenow.ai:4318
26
- ```
27
-
28
- ### 4. Start your app
29
-
30
- ```bash
31
- nuxt dev
32
- ```
33
-
34
- You should see in the console:
35
-
36
- ```
37
- [securenow] Nuxt module loaded — server plugin registered
38
- [securenow] 🚀 Nuxt OTel SDK started → https://freetrial.securenow.ai:4318/v1/traces
39
- [securenow] service.name=my-nuxt-app instance.id=my-nuxt-app-...
40
- ```
41
-
42
- That's it — all server-side requests are now traced.
43
-
44
- ---
45
-
46
- ## Configuration
47
-
48
- ### Module options in `nuxt.config.ts`
49
-
50
- ```ts
51
- export default defineNuxtConfig({
52
- modules: ['securenow/nuxt'],
53
- securenow: {
54
- serviceName: 'my-nuxt-app', // overrides SECURENOW_APPID
55
- endpoint: 'http://host:4318', // overrides SECURENOW_INSTANCE
56
- noUuid: true, // single service.name (no UUID suffix)
57
- captureBody: true, // capture POST/PUT/PATCH bodies
58
- logging: true, // forward console.* as OTLP logs
59
- },
60
- });
61
- ```
62
-
63
- ### Environment variables
64
-
65
- All standard SecureNow env vars are supported:
66
-
67
- | Variable | Description | Default |
68
- |----------|-------------|---------|
69
- | `SECURENOW_APPID` | Service name | `nuxt-app-<uuid>` |
70
- | `SECURENOW_INSTANCE` | OTLP base URL | `https://freetrial.securenow.ai:4318` |
71
- | `SECURENOW_NO_UUID` | Don't append UUID to service name | `false` |
72
- | `SECURENOW_LOGGING_ENABLED` | Forward console logs as OTLP | `false` |
73
- | `SECURENOW_CAPTURE_BODY` | Capture request bodies | `false` |
74
- | `SECURENOW_MAX_BODY_SIZE` | Max body size to capture (bytes) | `10240` |
75
- | `SECURENOW_SENSITIVE_FIELDS` | Extra fields to redact (CSV) | _(built-in list)_ |
76
- | `OTEL_EXPORTER_OTLP_ENDPOINT` | Alternative OTLP base URL | — |
77
- | `OTEL_EXPORTER_OTLP_HEADERS` | OTLP headers (k=v,k2=v2) | — |
78
-
79
- ---
80
-
81
- ## What gets traced
82
-
83
- ### Automatic (out of the box)
84
-
85
- - All Nitro server handler requests (API routes, SSR pages, middleware)
86
- - HTTP method, path, status code, duration
87
- - Client IP address (with proxy-aware resolution)
88
- - User-Agent, Referer, Origin, Host
89
- - Security header presence (auth, cookies, CSRF)
90
- - Request IDs and correlation headers
91
-
92
- ### With `captureBody: true`
93
-
94
- - POST/PUT/PATCH request bodies (JSON, form-urlencoded, GraphQL)
95
- - Sensitive fields auto-redacted (passwords, tokens, etc.)
96
- - Bodies larger than `SECURENOW_MAX_BODY_SIZE` are skipped
97
-
98
- ### With `logging: true`
99
-
100
- - All `console.log/info/warn/error/debug` calls forwarded as OTLP log records
101
- - Logs correlated with active trace spans
102
-
103
- ---
104
-
105
- ## Comparison with Next.js integration
106
-
107
- | Feature | Nuxt (`securenow/nuxt`) | Next.js (`securenow/nextjs`) |
108
- |---------|-------------------------|------------------------------|
109
- | Setup | Add to `modules` array | Create `instrumentation.ts` |
110
- | Config | `nuxt.config.ts` | `.env.local` + `next.config.js` |
111
- | Server tracing | Nitro hooks | HTTP instrumentation |
112
- | Edge runtime | Not supported | Skipped gracefully |
113
- | Vercel support | Via env vars | `@vercel/otel` integration |
114
- | Body capture | HTTP instrumentation | Middleware + `Request.clone()` |
115
- | Logging | Console patching | Console patching |
116
-
117
- ---
118
-
119
- ## Deployment
120
-
121
- ### Node.js server (PM2, Docker, etc.)
122
-
123
- Works out of the box with `nuxt build && node .output/server/index.mjs`.
124
-
125
- ### Vercel / Netlify / Cloudflare
126
-
127
- Set env vars in the platform dashboard:
128
-
129
- ```
130
- SECURENOW_APPID=my-nuxt-app
131
- SECURENOW_INSTANCE=https://your-otlp-backend:4318
132
- ```
133
-
134
- > Note: On edge runtimes (Cloudflare Workers, Vercel Edge), some Node.js-specific
135
- > instrumentations may not be available. Server-handler tracing via Nitro hooks
136
- > still works.
137
-
138
- ---
139
-
140
- ## Troubleshooting
141
-
142
- ### No traces appearing
143
-
144
- 1. Check that `SECURENOW_APPID` and `SECURENOW_INSTANCE` are set
145
- 2. Look for `[securenow] 🚀 Nuxt OTel SDK started` in the console
146
- 3. Verify the OTLP endpoint is reachable from your server
147
-
148
- ### Module not loading
149
-
150
- Make sure you're using Nuxt 3 (`nuxt: ">=3.0.0"`) and the module is listed
151
- in the `modules` array (not `buildModules`).
152
-
153
- ### OpenTelemetry packages bundled by Nitro
154
-
155
- The module automatically externalizes OTel packages. If you see bundling errors,
156
- manually add to `nuxt.config.ts`:
157
-
158
- ```ts
159
- export default defineNuxtConfig({
160
- nitro: {
161
- externals: {
162
- external: ['securenow', '@opentelemetry/api', '@opentelemetry/sdk-node'],
163
- },
164
- },
165
- });
166
- ```
@@ -1,312 +0,0 @@
1
- # ✅ Self-Sufficient Body Capture Solution - Complete!
2
-
3
- ## 🎯 The Challenge
4
-
5
- **Problem:** Next.js request streams can only be read once. Reading them at the HTTP instrumentation level locks the stream and causes:
6
- ```
7
- TypeError: Response body object should not be disturbed or locked
8
- ```
9
-
10
- **Solution:** Use Next.js middleware that:
11
- - Clones the request before reading (doesn't lock original)
12
- - Reads body safely
13
- - All logic is in the package (self-sufficient!)
14
-
15
- ---
16
-
17
- ## 🚀 How It Works (Self-Sufficient!)
18
-
19
- ### For Your Customers - Only 2 Steps!
20
-
21
- **Step 1: During Installation**
22
-
23
- When they run `npm install securenow`, the installer asks:
24
-
25
- ```
26
- Would you like to automatically create instrumentation file? (Y/n) Y
27
- ✅ Created instrumentation.ts
28
-
29
- Would you like to enable request body capture? (y/N) y
30
- ✅ Created middleware.ts
31
- → Captures JSON, GraphQL, Form bodies with auto-redaction
32
- ```
33
-
34
- **Step 2: Configure**
35
-
36
- Edit `.env.local` (already created by installer):
37
- ```bash
38
- SECURENOW_APPID=my-app
39
- SECURENOW_INSTANCE=http://otel-collector:4318
40
- SECURENOW_CAPTURE_BODY=1 # Enable body capture
41
- ```
42
-
43
- **That's IT!** 🎉 No code to write!
44
-
45
- ---
46
-
47
- ## 📦 What's in the Package (Self-Sufficient!)
48
-
49
- ### 1. nextjs-middleware.js
50
-
51
- **Exports ready-to-use middleware:**
52
- ```javascript
53
- export { middleware } from 'securenow/nextjs-middleware';
54
- ```
55
-
56
- **Customers just re-export it!** No code to write:
57
- ```typescript
58
- // middleware.ts (created by installer)
59
- export { middleware } from 'securenow/nextjs-middleware';
60
-
61
- export const config = {
62
- matcher: '/api/:path*',
63
- };
64
- ```
65
-
66
- ### 2. All Logic is in the Package
67
-
68
- **The middleware handles:**
69
- - ✅ Request cloning (doesn't lock stream)
70
- - ✅ Body parsing (JSON, GraphQL, Form)
71
- - ✅ Sensitive field redaction (20+ fields)
72
- - ✅ Size limits
73
- - ✅ Error handling
74
- - ✅ Span attribution
75
-
76
- **Customer writes: 0 lines of logic!**
77
-
78
- ---
79
-
80
- ## 🔧 Technical Solution
81
-
82
- ### The Key: request.clone()
83
-
84
- ```javascript
85
- // In nextjs-middleware.js (part of package)
86
- async function middleware(request) {
87
- // Clone request so original is not consumed
88
- const clonedRequest = request.clone();
89
- const bodyText = await clonedRequest.text();
90
-
91
- // Original request is untouched!
92
- // Next.js can still read it normally
93
-
94
- // Parse and redact body
95
- const redacted = redactSensitiveData(JSON.parse(bodyText));
96
-
97
- // Add to span
98
- span.setAttribute('http.request.body', JSON.stringify(redacted));
99
-
100
- // Continue to Next.js
101
- return NextResponse.next();
102
- }
103
- ```
104
-
105
- **Why this works:**
106
- - `request.clone()` creates a copy
107
- - Clone can be read without affecting original
108
- - Next.js reads the original stream normally
109
- - No locking errors!
110
-
111
- ---
112
-
113
- ## 📊 Comparison
114
-
115
- ### ❌ Previous Approach (Broken)
116
-
117
- ```javascript
118
- // In requestHook - DOESN'T WORK
119
- request.on('data', (chunk) => {
120
- chunks.push(chunk); // Consumes stream
121
- });
122
- // → Next.js can't read stream → ERROR
123
- ```
124
-
125
- ### ✅ New Approach (Works!)
126
-
127
- ```javascript
128
- // In Next.js middleware - WORKS
129
- const cloned = request.clone();
130
- const body = await cloned.text(); // Read clone
131
- // → Original stream is untouched → No error!
132
- ```
133
-
134
- ---
135
-
136
- ## 🎯 Customer Journey (Fully Automated!)
137
-
138
- ### Installation Experience
139
-
140
- ```bash
141
- $ npm install securenow
142
-
143
- ┌─────────────────────────────────────────────────┐
144
- │ 🎉 SecureNow installed successfully! │
145
- │ Next.js project detected │
146
- └─────────────────────────────────────────────────┘
147
-
148
- Would you like to automatically create instrumentation file? (Y/n) Y
149
- ✅ Created instrumentation.ts
150
-
151
- Would you like to enable request body capture? (y/N) y
152
- ✅ Created middleware.ts
153
- → Captures JSON, GraphQL, Form bodies with auto-redaction
154
-
155
- ✅ Created .env.local template
156
-
157
- ┌─────────────────────────────────────────────────┐
158
- │ 🚀 Next Steps: │
159
- │ │
160
- │ 1. Edit .env.local and set: │
161
- │ SECURENOW_APPID=your-app-name │
162
- │ SECURENOW_INSTANCE=http://otel-collector:4318 │
163
- │ SECURENOW_CAPTURE_BODY=1 │
164
- │ │
165
- │ 2. Run your app: npm run dev │
166
- │ │
167
- │ 3. Check SecureNow for traces! │
168
- │ │
169
- │ 📝 Body capture enabled with auto-redaction │
170
- └─────────────────────────────────────────────────┘
171
- ```
172
-
173
- **Total customer code written: 0 lines!**
174
-
175
- ---
176
-
177
- ## ✨ Self-Sufficient Features
178
-
179
- ### What the Package Provides
180
-
181
- 1. **nextjs-middleware.js**
182
- - Complete middleware implementation
183
- - All parsing logic
184
- - All redaction logic
185
- - Error handling
186
- - Span attribution
187
-
188
- 2. **Postinstall Script**
189
- - Auto-detects Next.js
190
- - Offers to create files
191
- - Creates middleware.ts with correct import
192
- - Updates .env.local template
193
-
194
- 3. **Examples**
195
- - `examples/nextjs-middleware.ts`
196
- - `examples/nextjs-middleware.js`
197
- - Ready to copy if needed
198
-
199
- 4. **Documentation**
200
- - `NEXTJS-BODY-CAPTURE.md` - Complete guide
201
- - Shows the one-line import
202
-
203
- ---
204
-
205
- ## 🔒 Security (Built Into Package!)
206
-
207
- **All in the package:**
208
- - ✅ 20+ sensitive fields redacted
209
- - ✅ Recursive redaction
210
- - ✅ GraphQL pattern matching
211
- - ✅ Size limits
212
- - ✅ Type detection
213
-
214
- **Customer configuration:**
215
- ```bash
216
- # Optional: add custom fields
217
- SECURENOW_SENSITIVE_FIELDS=email,phone
218
- ```
219
-
220
- **Customer code: 0 lines!**
221
-
222
- ---
223
-
224
- ## 📝 Files Created for Customer
225
-
226
- ### By Installer
227
-
228
- 1. **instrumentation.ts** (or .js)
229
- ```typescript
230
- export { middleware } from 'securenow/nextjs-middleware';
231
- ```
232
- *Just a re-export!*
233
-
234
- 2. **middleware.ts** (or .js) - If they choose body capture
235
- ```typescript
236
- export { middleware } from 'securenow/nextjs-middleware';
237
- export const config = { matcher: '/api/:path*' };
238
- ```
239
- *Just a re-export + config!*
240
-
241
- 3. **.env.local**
242
- ```bash
243
- SECURENOW_APPID=my-app
244
- SECURENOW_INSTANCE=http://otel-collector:4318
245
- SECURENOW_CAPTURE_BODY=1
246
- ```
247
- *Just configuration!*
248
-
249
- **Total logic written by customer: 0 lines!**
250
-
251
- ---
252
-
253
- ## 🎉 Result
254
-
255
- ### For Next.js Users
256
-
257
- **Before (broken):**
258
- - Install package
259
- - Enable body capture
260
- - → Get stream locking error
261
- - → App breaks
262
-
263
- **After (self-sufficient):**
264
- - Install package
265
- - Answer "Y" twice
266
- - Edit config values
267
- - → Everything works
268
- - → Bodies captured
269
- - → Sensitive data redacted
270
- - → Zero code to write
271
-
272
- ### For You
273
-
274
- **Self-sufficient package:**
275
- - ✅ Customers write 0 lines of code
276
- - ✅ Just import from package
277
- - ✅ All logic in package
278
- - ✅ No stream locking errors
279
- - ✅ Works perfectly with Next.js
280
- - ✅ Automatic setup via installer
281
-
282
- ---
283
-
284
- ## ✅ Checklist
285
-
286
- - [x] Fixed stream locking error
287
- - [x] Created nextjs-middleware.js with all logic
288
- - [x] Updated package.json exports
289
- - [x] Enhanced postinstall to offer middleware creation
290
- - [x] Created example files
291
- - [x] Updated documentation
292
- - [x] Zero customer code required
293
- - [x] Tested - no linter errors
294
-
295
- ---
296
-
297
- ## 🚀 Ready to Ship!
298
-
299
- **The error is fixed and the solution is self-sufficient!**
300
-
301
- Customers get:
302
- - ✅ Automatic file creation (installer)
303
- - ✅ One-line imports (re-export from package)
304
- - ✅ All logic in package (no code to write)
305
- - ✅ Automatic redaction (built-in)
306
- - ✅ No stream errors (uses clone)
307
-
308
- **Status: Production Ready!** 🎯
309
-
310
-
311
-
312
-
package/firewall-cloud.js DELETED
@@ -1,212 +0,0 @@
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 };