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.
- package/LICENSE +15 -0
- package/README.md +40 -243
- package/cli.js +455 -425
- package/console-instrumentation.js +136 -147
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-BODY-CAPTURE.md +1 -1
- package/docs/AUTO-SETUP.md +4 -4
- package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
- package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
- package/docs/CHANGELOG-NEXTJS.md +1 -1
- package/docs/CUSTOMER-GUIDE.md +16 -16
- package/docs/EASIEST-SETUP.md +5 -5
- package/docs/ENVIRONMENT-VARIABLES.md +652 -880
- package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
- package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
- package/docs/INDEX.md +4 -22
- package/docs/LOGGING-GUIDE.md +708 -701
- package/docs/LOGGING-QUICKSTART.md +239 -234
- package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
- package/docs/NEXTJS-GUIDE.md +14 -14
- package/docs/NEXTJS-QUICKSTART.md +1 -1
- package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
- package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
- package/docs/REDACTION-EXAMPLES.md +1 -1
- package/docs/REQUEST-BODY-CAPTURE.md +10 -19
- package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
- package/examples/README.md +6 -6
- package/examples/instrumentation-with-auto-capture.ts +1 -1
- package/examples/nextjs-env-example.txt +2 -2
- package/examples/nextjs-instrumentation.js +1 -1
- package/examples/nextjs-instrumentation.ts +1 -1
- package/examples/nextjs-with-logging-example.md +6 -6
- package/examples/nextjs-with-options.ts +1 -1
- package/examples/test-nextjs-setup.js +1 -1
- package/nextjs-auto-capture.js +207 -199
- package/nextjs-middleware.js +181 -186
- package/nextjs-webpack-config.js +53 -88
- package/nextjs-wrapper.js +158 -158
- package/nextjs.d.ts +1 -1
- package/nextjs.js +135 -190
- package/package.json +45 -67
- package/postinstall.js +6 -6
- package/register.d.ts +1 -1
- package/register.js +4 -39
- package/tracing.d.ts +1 -2
- package/tracing.js +22 -287
- package/web-vite.mjs +156 -239
- package/CONSUMING-APPS-GUIDE.md +0 -455
- package/NPM_README.md +0 -1958
- package/SKILL-API.md +0 -600
- package/SKILL-CLI.md +0 -419
- package/cidr.js +0 -83
- package/cli/apps.js +0 -585
- package/cli/auth.js +0 -280
- package/cli/client.js +0 -115
- package/cli/config.js +0 -173
- package/cli/firewall.js +0 -100
- package/cli/fp.js +0 -638
- package/cli/init.js +0 -201
- package/cli/monitor.js +0 -545
- package/cli/run.js +0 -133
- package/cli/security.js +0 -1064
- package/cli/ui.js +0 -386
- package/docs/API-KEYS-GUIDE.md +0 -233
- package/docs/AUTO-SETUP-SUMMARY.md +0 -331
- package/docs/BODY-CAPTURE-FIX.md +0 -261
- package/docs/COMPLETION-REPORT.md +0 -408
- package/docs/FINAL-SOLUTION.md +0 -335
- package/docs/FIREWALL-GUIDE.md +0 -426
- package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
- package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
- package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
- package/docs/NUXT-GUIDE.md +0 -166
- package/docs/SOLUTION-SUMMARY.md +0 -312
- package/firewall-cloud.js +0 -212
- package/firewall-iptables.js +0 -139
- package/firewall-only.js +0 -38
- package/firewall-tcp.js +0 -74
- package/firewall.js +0 -720
- package/free-trial-banner.js +0 -174
- package/nuxt-server-plugin.mjs +0 -423
- package/nuxt.d.ts +0 -60
- package/nuxt.mjs +0 -75
- package/resolve-ip.js +0 -77
package/docs/NUXT-GUIDE.md
DELETED
|
@@ -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
|
-
```
|
package/docs/SOLUTION-SUMMARY.md
DELETED
|
@@ -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 };
|