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.
- package/CONSUMING-APPS-GUIDE.md +455 -0
- package/NPM_README.md +2029 -0
- package/README.md +297 -40
- package/SKILL-API.md +634 -0
- package/SKILL-CLI.md +454 -0
- package/cidr.js +83 -0
- package/cli/apps.js +585 -0
- package/cli/auth.js +280 -0
- package/cli/client.js +115 -0
- package/cli/config.js +173 -0
- package/cli/diagnostics.js +387 -0
- package/cli/firewall.js +100 -0
- package/cli/fp.js +638 -0
- package/cli/init.js +201 -0
- package/cli/monitor.js +440 -0
- package/cli/run.js +148 -0
- package/cli/security.js +980 -0
- package/cli/ui.js +386 -0
- package/cli/utils.js +127 -0
- package/cli.js +466 -455
- package/console-instrumentation.js +147 -136
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +1377 -455
- package/docs/API-KEYS-GUIDE.md +233 -0
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-BODY-CAPTURE.md +1 -1
- package/docs/AUTO-SETUP-SUMMARY.md +331 -0
- package/docs/AUTO-SETUP.md +4 -4
- package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
- package/docs/BODY-CAPTURE-FIX.md +261 -0
- package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
- package/docs/CHANGELOG-NEXTJS.md +1 -35
- package/docs/COMPLETION-REPORT.md +408 -0
- package/docs/CUSTOMER-GUIDE.md +16 -16
- package/docs/EASIEST-SETUP.md +5 -5
- package/docs/ENVIRONMENT-VARIABLES.md +880 -652
- package/docs/EXPRESS-BODY-CAPTURE.md +13 -12
- package/docs/EXPRESS-SETUP-GUIDE.md +719 -720
- package/docs/FINAL-SOLUTION.md +335 -0
- package/docs/FIREWALL-GUIDE.md +426 -0
- package/docs/IMPLEMENTATION-SUMMARY.md +410 -0
- package/docs/INDEX.md +22 -4
- package/docs/LOGGING-GUIDE.md +701 -708
- package/docs/LOGGING-QUICKSTART.md +234 -255
- package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +323 -0
- 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-SETUP-COMPLETE.md +795 -0
- package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
- package/docs/NUXT-GUIDE.md +166 -0
- package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
- package/docs/REDACTION-EXAMPLES.md +1 -1
- package/docs/REQUEST-BODY-CAPTURE.md +19 -10
- package/docs/SOLUTION-SUMMARY.md +312 -0
- 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/firewall-cloud.js +212 -0
- package/firewall-iptables.js +139 -0
- package/firewall-only.js +38 -0
- package/firewall-tcp.js +74 -0
- package/firewall.js +720 -0
- package/free-trial-banner.js +174 -0
- package/nextjs-auto-capture.js +199 -207
- package/nextjs-middleware.js +186 -181
- package/nextjs-webpack-config.js +88 -53
- package/nextjs-wrapper.js +158 -158
- package/nextjs.d.ts +1 -1
- package/nextjs.js +639 -647
- package/nuxt-server-plugin.mjs +423 -0
- package/nuxt.d.ts +60 -0
- package/nuxt.mjs +75 -0
- package/package.json +186 -164
- package/postinstall.js +6 -6
- package/register.d.ts +1 -1
- package/register.js +39 -4
- package/resolve-ip.js +77 -0
- package/tracing.d.ts +2 -1
- package/tracing.js +295 -34
- package/web-vite.mjs +239 -156
- package/LICENSE +0 -15
package/nextjs-wrapper.js
CHANGED
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SecureNow Next.js API Route Wrapper for Body Capture
|
|
3
|
-
*
|
|
4
|
-
* This approach is NON-INVASIVE and runs INSIDE your handler,
|
|
5
|
-
* so it never blocks or interferes with middleware or routing.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
*
|
|
9
|
-
* import { withSecureNow } from 'securenow/nextjs-wrapper';
|
|
10
|
-
*
|
|
11
|
-
* export const POST = withSecureNow(async (request) => {
|
|
12
|
-
* // Your handler code - request.body is available as parsed JSON
|
|
13
|
-
* const data = await request.json();
|
|
14
|
-
* return Response.json({ success: true });
|
|
15
|
-
* });
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const { trace } = require('@opentelemetry/api');
|
|
19
|
-
|
|
20
|
-
// Default sensitive fields to redact
|
|
21
|
-
const DEFAULT_SENSITIVE_FIELDS = [
|
|
22
|
-
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
23
|
-
'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
|
|
24
|
-
'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Redact sensitive fields from an object
|
|
29
|
-
*/
|
|
30
|
-
function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
31
|
-
if (!obj || typeof obj !== 'object') return obj;
|
|
32
|
-
|
|
33
|
-
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
34
|
-
|
|
35
|
-
for (const key
|
|
36
|
-
const lowerKey = key.toLowerCase();
|
|
37
|
-
|
|
38
|
-
if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
|
|
39
|
-
redacted[key] = '[REDACTED]';
|
|
40
|
-
} else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
|
|
41
|
-
redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return redacted;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Capture body from Request object (clone to avoid consuming)
|
|
50
|
-
*/
|
|
51
|
-
async function captureRequestBody(request) {
|
|
52
|
-
const captureBody = String(process.env.SECURENOW_CAPTURE_BODY) === '1' ||
|
|
53
|
-
String(process.env.SECURENOW_CAPTURE_BODY).toLowerCase() === 'true';
|
|
54
|
-
|
|
55
|
-
if (!captureBody) return;
|
|
56
|
-
if (!['POST', 'PUT', 'PATCH'].includes(request.method)) return;
|
|
57
|
-
|
|
58
|
-
const span = trace.getActiveSpan();
|
|
59
|
-
if (!span) return;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const contentType = request.headers.get('content-type') || '';
|
|
63
|
-
const maxBodySize = parseInt(process.env.SECURENOW_MAX_BODY_SIZE ||
|
|
64
|
-
const customSensitiveFields = (process.env.SECURENOW_SENSITIVE_FIELDS || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
65
|
-
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
66
|
-
|
|
67
|
-
// Only for supported types
|
|
68
|
-
if (!contentType.includes('application/json') &&
|
|
69
|
-
!contentType.includes('application/graphql') &&
|
|
70
|
-
!contentType.includes('application/x-www-form-urlencoded')) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Clone to avoid consuming the original
|
|
75
|
-
const cloned = request.clone();
|
|
76
|
-
const bodyText = await cloned.text();
|
|
77
|
-
|
|
78
|
-
if (bodyText.length > maxBodySize) {
|
|
79
|
-
span.setAttribute('http.request.body', `[TOO LARGE: ${bodyText.length} bytes]`);
|
|
80
|
-
span.setAttribute('http.request.body.size', bodyText.length);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Parse and redact based on type
|
|
85
|
-
let redacted;
|
|
86
|
-
if (contentType.includes('application/json') || contentType.includes('application/graphql')) {
|
|
87
|
-
try {
|
|
88
|
-
const parsed = JSON.parse(bodyText);
|
|
89
|
-
redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
90
|
-
span.setAttributes({
|
|
91
|
-
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
92
|
-
'http.request.body.type': contentType.includes('graphql') ? 'graphql' : 'json',
|
|
93
|
-
'http.request.body.size': bodyText.length,
|
|
94
|
-
});
|
|
95
|
-
} catch (e) {
|
|
96
|
-
span.setAttribute('http.request.body', '[INVALID JSON]');
|
|
97
|
-
}
|
|
98
|
-
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
99
|
-
const params = new URLSearchParams(bodyText);
|
|
100
|
-
const parsed = Object.fromEntries(params);
|
|
101
|
-
redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
102
|
-
span.setAttributes({
|
|
103
|
-
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
104
|
-
'http.request.body.type': 'form',
|
|
105
|
-
'http.request.body.size': bodyText.length,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
// Silently fail - never block the request
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Wrap a Next.js API route handler to capture body
|
|
115
|
-
* This is OPTIONAL and NON-INVASIVE - only use on routes where you want body capture
|
|
116
|
-
*/
|
|
117
|
-
function withSecureNow(handler) {
|
|
118
|
-
return async function wrappedHandler(request, context) {
|
|
119
|
-
// Capture body asynchronously (doesn't block handler)
|
|
120
|
-
captureRequestBody(request).catch(() => {
|
|
121
|
-
// Ignore errors silently
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Call original handler immediately - no blocking!
|
|
125
|
-
return handler(request, context);
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Alternative: Auto-capture wrapper that tries to capture AFTER handler runs
|
|
131
|
-
* This is even safer as it never interferes with the handler logic
|
|
132
|
-
*/
|
|
133
|
-
function withSecureNowAsync(handler) {
|
|
134
|
-
return async function wrappedHandler(request, context) {
|
|
135
|
-
// Try to capture body in background (non-blocking)
|
|
136
|
-
const capturePromise = captureRequestBody(request);
|
|
137
|
-
|
|
138
|
-
// Run handler
|
|
139
|
-
const response = await handler(request, context);
|
|
140
|
-
|
|
141
|
-
// Wait for capture to finish (but don't fail if it doesn't)
|
|
142
|
-
await capturePromise.catch(() => {});
|
|
143
|
-
|
|
144
|
-
return response;
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
module.exports = {
|
|
149
|
-
withSecureNow,
|
|
150
|
-
withSecureNowAsync,
|
|
151
|
-
captureRequestBody,
|
|
152
|
-
redactSensitiveData,
|
|
153
|
-
DEFAULT_SENSITIVE_FIELDS,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1
|
+
/**
|
|
2
|
+
* SecureNow Next.js API Route Wrapper for Body Capture
|
|
3
|
+
*
|
|
4
|
+
* This approach is NON-INVASIVE and runs INSIDE your handler,
|
|
5
|
+
* so it never blocks or interferes with middleware or routing.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
*
|
|
9
|
+
* import { withSecureNow } from 'securenow/nextjs-wrapper';
|
|
10
|
+
*
|
|
11
|
+
* export const POST = withSecureNow(async (request) => {
|
|
12
|
+
* // Your handler code - request.body is available as parsed JSON
|
|
13
|
+
* const data = await request.json();
|
|
14
|
+
* return Response.json({ success: true });
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { trace } = require('@opentelemetry/api');
|
|
19
|
+
|
|
20
|
+
// Default sensitive fields to redact
|
|
21
|
+
const DEFAULT_SENSITIVE_FIELDS = [
|
|
22
|
+
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
23
|
+
'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
|
|
24
|
+
'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Redact sensitive fields from an object
|
|
29
|
+
*/
|
|
30
|
+
function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
31
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
32
|
+
|
|
33
|
+
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
34
|
+
|
|
35
|
+
for (const key of Object.keys(redacted)) {
|
|
36
|
+
const lowerKey = key.toLowerCase();
|
|
37
|
+
|
|
38
|
+
if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) {
|
|
39
|
+
redacted[key] = '[REDACTED]';
|
|
40
|
+
} else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
|
|
41
|
+
redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return redacted;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Capture body from Request object (clone to avoid consuming)
|
|
50
|
+
*/
|
|
51
|
+
async function captureRequestBody(request) {
|
|
52
|
+
const captureBody = String(process.env.SECURENOW_CAPTURE_BODY) === '1' ||
|
|
53
|
+
String(process.env.SECURENOW_CAPTURE_BODY).toLowerCase() === 'true';
|
|
54
|
+
|
|
55
|
+
if (!captureBody) return;
|
|
56
|
+
if (!['POST', 'PUT', 'PATCH'].includes(request.method)) return;
|
|
57
|
+
|
|
58
|
+
const span = trace.getActiveSpan();
|
|
59
|
+
if (!span) return;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const contentType = request.headers.get('content-type') || '';
|
|
63
|
+
const maxBodySize = Math.max(1024, parseInt(process.env.SECURENOW_MAX_BODY_SIZE, 10) || 10240);
|
|
64
|
+
const customSensitiveFields = (process.env.SECURENOW_SENSITIVE_FIELDS || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
65
|
+
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
66
|
+
|
|
67
|
+
// Only for supported types
|
|
68
|
+
if (!contentType.includes('application/json') &&
|
|
69
|
+
!contentType.includes('application/graphql') &&
|
|
70
|
+
!contentType.includes('application/x-www-form-urlencoded')) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Clone to avoid consuming the original
|
|
75
|
+
const cloned = request.clone();
|
|
76
|
+
const bodyText = await cloned.text();
|
|
77
|
+
|
|
78
|
+
if (bodyText.length > maxBodySize) {
|
|
79
|
+
span.setAttribute('http.request.body', `[TOO LARGE: ${bodyText.length} bytes]`);
|
|
80
|
+
span.setAttribute('http.request.body.size', bodyText.length);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Parse and redact based on type
|
|
85
|
+
let redacted;
|
|
86
|
+
if (contentType.includes('application/json') || contentType.includes('application/graphql')) {
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(bodyText);
|
|
89
|
+
redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
90
|
+
span.setAttributes({
|
|
91
|
+
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
92
|
+
'http.request.body.type': contentType.includes('graphql') ? 'graphql' : 'json',
|
|
93
|
+
'http.request.body.size': bodyText.length,
|
|
94
|
+
});
|
|
95
|
+
} catch (e) {
|
|
96
|
+
span.setAttribute('http.request.body', '[INVALID JSON]');
|
|
97
|
+
}
|
|
98
|
+
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
99
|
+
const params = new URLSearchParams(bodyText);
|
|
100
|
+
const parsed = Object.fromEntries(params);
|
|
101
|
+
redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
102
|
+
span.setAttributes({
|
|
103
|
+
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
104
|
+
'http.request.body.type': 'form',
|
|
105
|
+
'http.request.body.size': bodyText.length,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
// Silently fail - never block the request
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Wrap a Next.js API route handler to capture body
|
|
115
|
+
* This is OPTIONAL and NON-INVASIVE - only use on routes where you want body capture
|
|
116
|
+
*/
|
|
117
|
+
function withSecureNow(handler) {
|
|
118
|
+
return async function wrappedHandler(request, context) {
|
|
119
|
+
// Capture body asynchronously (doesn't block handler)
|
|
120
|
+
captureRequestBody(request).catch(() => {
|
|
121
|
+
// Ignore errors silently
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Call original handler immediately - no blocking!
|
|
125
|
+
return handler(request, context);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Alternative: Auto-capture wrapper that tries to capture AFTER handler runs
|
|
131
|
+
* This is even safer as it never interferes with the handler logic
|
|
132
|
+
*/
|
|
133
|
+
function withSecureNowAsync(handler) {
|
|
134
|
+
return async function wrappedHandler(request, context) {
|
|
135
|
+
// Try to capture body in background (non-blocking)
|
|
136
|
+
const capturePromise = captureRequestBody(request);
|
|
137
|
+
|
|
138
|
+
// Run handler
|
|
139
|
+
const response = await handler(request, context);
|
|
140
|
+
|
|
141
|
+
// Wait for capture to finish (but don't fail if it doesn't)
|
|
142
|
+
await capturePromise.catch(() => {});
|
|
143
|
+
|
|
144
|
+
return response;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
withSecureNow,
|
|
150
|
+
withSecureNowAsync,
|
|
151
|
+
captureRequestBody,
|
|
152
|
+
redactSensitiveData,
|
|
153
|
+
DEFAULT_SENSITIVE_FIELDS,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
package/nextjs.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface RegisterOptions {
|
|
|
51
51
|
* export function register() {
|
|
52
52
|
* registerSecureNow({
|
|
53
53
|
* serviceName: 'my-nextjs-app',
|
|
54
|
-
* endpoint: 'http://
|
|
54
|
+
* endpoint: 'http://your-otlp-backend.example.com:4318',
|
|
55
55
|
* noUuid: true,
|
|
56
56
|
* });
|
|
57
57
|
* }
|