securenow 8.23.0 → 8.25.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/NPM_README.md +730 -333
- package/README.md +91 -28
- package/SKILL-API.md +657 -530
- package/SKILL-CLI.md +293 -663
- package/app-config.js +90 -15
- package/cidr.js +139 -20
- package/cli/credentials.js +11 -0
- package/cli/init.js +121 -17
- package/cli/utils.js +26 -14
- package/cli.js +9 -6
- package/console-instrumentation.js +6 -0
- package/crash-capture.js +13 -1
- package/esm-detect.js +5 -1
- package/fastify-hooks.js +67 -0
- package/firewall-cloud.js +81 -32
- package/firewall-iptables.js +123 -62
- package/firewall-only.js +2 -0
- package/firewall-tcp.js +19 -5
- package/firewall.js +21 -1
- package/framework-routes.js +222 -0
- package/log-format.js +2 -0
- package/log-redaction.js +135 -0
- package/mcp/catalog.js +14 -9
- package/next-route.js +61 -0
- package/nextjs-auto-capture.d.ts +5 -2
- package/nextjs-auto-capture.js +77 -220
- package/nextjs-middleware.js +8 -4
- package/nextjs-wrapper.js +8 -4
- package/nextjs.d.ts +35 -67
- package/nextjs.js +75 -182
- package/nuxt-server-plugin.mjs +30 -478
- package/nuxt.mjs +15 -14
- package/package.json +8 -2
- package/pm2-env.js +47 -0
- package/register-vite.js +6 -2
- package/register.d.ts +27 -34
- package/request-capture.js +492 -0
- package/resolve-ip.js +23 -5
- package/sessions.d.ts +27 -0
- package/sessions.js +71 -10
- package/tracing.d.ts +40 -121
- package/tracing.js +39 -447
package/nextjs.js
CHANGED
|
@@ -7,24 +7,32 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 1. Add securenow to serverExternalPackages and standalone output tracing
|
|
9
9
|
* includes in next.config.js:
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* const nextConfig = {
|
|
12
12
|
* serverExternalPackages: ["securenow"],
|
|
13
13
|
* outputFileTracingIncludes: {
|
|
14
14
|
* "/*": ["<securenow package glob>"],
|
|
15
15
|
* },
|
|
16
16
|
* };
|
|
17
|
-
*
|
|
18
|
-
* 2. Create instrumentation.ts (or .js) in your project root:
|
|
19
|
-
*
|
|
17
|
+
*
|
|
18
|
+
* 2. Create instrumentation.ts (or .js) in your project root (`npx securenow init` writes it):
|
|
19
|
+
*
|
|
20
20
|
* export async function register() {
|
|
21
|
-
* if (process.env.NEXT_RUNTIME
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* if (process.env.NEXT_RUNTIME === "nodejs") {
|
|
22
|
+
* try {
|
|
23
|
+
* const securenowNext = await import("securenow/nextjs");
|
|
24
|
+
* const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
25
|
+
* registerSecureNow({ captureBody: true });
|
|
26
|
+
* await import("securenow/nextjs-auto-capture");
|
|
27
|
+
* } catch (error) {
|
|
28
|
+
* console.error("[securenow] SecureNow did not start; the app is running without it:", error);
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
26
31
|
* }
|
|
27
|
-
*
|
|
32
|
+
*
|
|
33
|
+
* The import stays inside the nodejs block, without webpackIgnore, so Next.js leaves it out
|
|
34
|
+
* of the Edge build and traces securenow and its dependencies into standalone output.
|
|
35
|
+
*
|
|
28
36
|
* 3. Run `npx securenow login` and `npx securenow init`.
|
|
29
37
|
* The SDK reads app identity, collector, firewall, capture, and
|
|
30
38
|
* deploymentEnvironment from .securenow/credentials.json.
|
|
@@ -34,7 +42,6 @@ const { randomUUID } = require('crypto');
|
|
|
34
42
|
const { nodeSdkDefaultTelemetryOptions } = require('./otel-defaults');
|
|
35
43
|
const appConfig = require('./app-config');
|
|
36
44
|
const { resolveClientIpWithDetails } = require('./resolve-ip');
|
|
37
|
-
const otelResources = require('@opentelemetry/resources');
|
|
38
45
|
|
|
39
46
|
const nodeSdkTelemetryOptions = nodeSdkDefaultTelemetryOptions();
|
|
40
47
|
|
|
@@ -50,6 +57,10 @@ function requireNodeBuiltin(name) {
|
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
function createResource(attributes) {
|
|
60
|
+
// Required here, not at the top of the file: a bundle that lacks the OpenTelemetry packages
|
|
61
|
+
// (an incomplete standalone image) must still load this module, so the firewall starts and
|
|
62
|
+
// the app keeps serving.
|
|
63
|
+
const otelResources = require('@opentelemetry/resources');
|
|
53
64
|
if (typeof otelResources.resourceFromAttributes === 'function') {
|
|
54
65
|
return otelResources.resourceFromAttributes(attributes);
|
|
55
66
|
}
|
|
@@ -59,158 +70,12 @@ function createResource(attributes) {
|
|
|
59
70
|
throw new Error('Unsupported @opentelemetry/resources version');
|
|
60
71
|
}
|
|
61
72
|
|
|
62
|
-
const {
|
|
63
|
-
|
|
64
|
-
// Default sensitive fields to redact from request bodies.
|
|
65
|
-
// OTP/code aliases use exact normalized matching; other fields are matched
|
|
66
|
-
// substring-wise against lowercased keys (see redactSensitiveData),
|
|
67
|
-
// so e.g. 'card' also catches 'creditCard' and 'account' also catches
|
|
68
|
-
// 'accountNumber'. Over-redaction here is intentional: a falsely-redacted
|
|
69
|
-
// telemetry value is always safer than a leaked secret. Entries are kept
|
|
70
|
-
// specific enough to avoid nuking broad benign keys (e.g. we use
|
|
71
|
-
// 'firstname'/'lastname'/'fullname', never bare 'name').
|
|
72
|
-
const DEFAULT_SENSITIVE_FIELDS = [
|
|
73
|
-
...OTP_SENSITIVE_FIELDS,
|
|
74
|
-
// credentials / auth
|
|
75
|
-
'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
|
|
76
|
-
'access_token', 'auth', 'authorization', 'bearer', 'credentials',
|
|
77
|
-
'mysql_pwd', 'otp', 'mfa', 'totp', 'sessionid', 'session_id',
|
|
78
|
-
'cookie', 'set-cookie',
|
|
79
|
-
// financial
|
|
80
|
-
'stripeToken', 'card', 'cardnumber', 'ccv', 'cvc', 'cvv',
|
|
81
|
-
'iban', 'account', 'accountnumber', 'routing', 'sortcode', 'taxid',
|
|
82
|
-
// PII
|
|
83
|
-
'ssn', 'pin', 'email', 'e_mail', 'phone', 'mobile', 'dob', 'birthdate',
|
|
84
|
-
'firstname', 'lastname', 'fullname', 'address', 'postcode', 'zip',
|
|
85
|
-
'passport', 'license',
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
// Conservative value-shape redactors. Key-name matching misses secrets that
|
|
89
|
-
// land in free-form string values (GraphQL bodies, message fields, etc.), so
|
|
90
|
-
// as a second layer we scrub string VALUES that *look like* a secret/PII.
|
|
91
|
-
// These are intentionally precise/bounded so they don't garble normal prose,
|
|
92
|
-
// and they only ever transform captured telemetry strings (read-only) — never
|
|
93
|
-
// the actual request/response stream. Compiled once at module load.
|
|
94
|
-
const VALUE_REDACTORS = [
|
|
95
|
-
// JWT: three base64url segments. Anchored to the eyJ header so it won't
|
|
96
|
-
// match arbitrary dotted tokens.
|
|
97
|
-
{ name: 'jwt', re: /eyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g },
|
|
98
|
-
// Bearer/Basic auth header value embedded in a body string.
|
|
99
|
-
{ name: 'bearer', re: /\b(?:Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi },
|
|
100
|
-
// Stripe-style / SecureNow live+test API keys.
|
|
101
|
-
{ name: 'apikey', re: /\b(?:sk|pk|rk|snk)_(?:live|test)_[A-Za-z0-9]{8,}/g },
|
|
102
|
-
// Email addresses (bounded local/domain parts).
|
|
103
|
-
{ name: 'email', re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,24}\b/g },
|
|
104
|
-
// Credit-card-like: 13–19 digit runs that pass a Luhn check, allowing
|
|
105
|
-
// space/dash grouping. Luhn-gated to avoid clobbering ordinary long numbers.
|
|
106
|
-
{ name: 'card', re: /\b(?:\d[ -]?){13,19}\b/g, luhn: true },
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
// Skip the value-shape scan on very large strings to bound per-body cost.
|
|
110
|
-
const MAX_VALUE_SCAN_LENGTH = 16384;
|
|
111
|
-
|
|
112
|
-
function luhnValid(digits) {
|
|
113
|
-
let sum = 0;
|
|
114
|
-
let alt = false;
|
|
115
|
-
for (let i = digits.length - 1; i >= 0; i--) {
|
|
116
|
-
let d = digits.charCodeAt(i) - 48;
|
|
117
|
-
if (d < 0 || d > 9) return false;
|
|
118
|
-
if (alt) { d *= 2; if (d > 9) d -= 9; }
|
|
119
|
-
sum += d;
|
|
120
|
-
alt = !alt;
|
|
121
|
-
}
|
|
122
|
-
return sum % 10 === 0;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Redact obvious secret/PII shapes inside a captured string VALUE.
|
|
127
|
-
* Returns the input unchanged when nothing matches (cheap common case).
|
|
128
|
-
*/
|
|
129
|
-
function redactSensitiveValue(value) {
|
|
130
|
-
if (typeof value !== 'string' || value.length === 0) return value;
|
|
131
|
-
// Credential assignments in GraphQL/query strings need field-aware
|
|
132
|
-
// redaction even though a short numeric OTP has no unique value shape.
|
|
133
|
-
value = redactOtpQuery(redactOtpGraphQL(value));
|
|
134
|
-
if (value.length > MAX_VALUE_SCAN_LENGTH) return value; // bound shape scans
|
|
135
|
-
let out = value;
|
|
136
|
-
for (const r of VALUE_REDACTORS) {
|
|
137
|
-
r.re.lastIndex = 0;
|
|
138
|
-
if (r.luhn) {
|
|
139
|
-
out = out.replace(r.re, (m) => {
|
|
140
|
-
const digits = m.replace(/[ -]/g, '');
|
|
141
|
-
if (digits.length < 13 || digits.length > 19) return m;
|
|
142
|
-
return luhnValid(digits) ? '[REDACTED]' : m;
|
|
143
|
-
});
|
|
144
|
-
} else {
|
|
145
|
-
out = out.replace(r.re, '[REDACTED]');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return out;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Redact sensitive fields from an object
|
|
153
|
-
*/
|
|
154
|
-
function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
155
|
-
if (!obj || typeof obj !== 'object') return obj;
|
|
156
|
-
|
|
157
|
-
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
158
|
-
|
|
159
|
-
for (const key of Object.keys(redacted)) {
|
|
160
|
-
|
|
161
|
-
if (matchesSensitiveField(key, sensitiveFields)) {
|
|
162
|
-
redacted[key] = '[REDACTED]';
|
|
163
|
-
} else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
|
|
164
|
-
// Recursively redact nested objects
|
|
165
|
-
redacted[key] = redactSensitiveData(redacted[key], sensitiveFields);
|
|
166
|
-
} else if (typeof redacted[key] === 'string') {
|
|
167
|
-
// Second layer: even if the key name looks benign, scrub values that
|
|
168
|
-
// *look like* a secret/PII (JWT, bearer token, API key, email, card).
|
|
169
|
-
redacted[key] = redactSensitiveValue(redacted[key]);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return redacted;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function escapeRegex(str) {
|
|
177
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Redact sensitive data from GraphQL query strings
|
|
182
|
-
*/
|
|
183
|
-
function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
184
|
-
if (!query || typeof query !== 'string') return query;
|
|
185
|
-
|
|
186
|
-
let redacted = query;
|
|
187
|
-
|
|
188
|
-
// Redact sensitive fields in GraphQL arguments and variables
|
|
189
|
-
// Matches patterns like: password: "value" or password:"value" or password:'value'
|
|
190
|
-
sensitiveFields.filter(field => field === 'otp' || !isOtpCodeField(field)).forEach(field => {
|
|
191
|
-
const escaped = escapeRegex(field);
|
|
192
|
-
const patterns = [
|
|
193
|
-
new RegExp(`(${escaped}\\s*:\\s*["'])([^"']+)(["'])`, 'gi'),
|
|
194
|
-
new RegExp(`(${escaped}\\s*:\\s*)([^\\s,})\n]+)`, 'gi'),
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
patterns.forEach(pattern => {
|
|
198
|
-
redacted = redacted.replace(pattern, (match, prefix, value, suffix) => {
|
|
199
|
-
if (suffix) {
|
|
200
|
-
return `${prefix}[REDACTED]${suffix}`;
|
|
201
|
-
} else {
|
|
202
|
-
return `${prefix}[REDACTED]`;
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
});
|
|
73
|
+
const { redactOtpQuery, withOtpRedactionExporter } = require('./otp-redaction');
|
|
74
|
+
const { createRequestCapture } = require('./request-capture');
|
|
207
75
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return redacted;
|
|
213
|
-
}
|
|
76
|
+
// Set once the HTTP layer has captured a request in this process: nextjs-auto-capture then
|
|
77
|
+
// leaves bodies to it instead of attaching a second copy to Next.js's own span.
|
|
78
|
+
const HTTP_CAPTURE_ACTIVE = Symbol.for('securenow.nextjs.httpCaptureActive');
|
|
214
79
|
|
|
215
80
|
/**
|
|
216
81
|
* Register SecureNow OpenTelemetry for Next.js using @vercel/otel
|
|
@@ -275,15 +140,19 @@ function registerSecureNow(options = {}) {
|
|
|
275
140
|
const otelLogLevel = String(appConfig.configValue('otel.logLevel', '') || '').toLowerCase();
|
|
276
141
|
const isDevelopmentRuntime = process.env.NODE_ENV === 'development';
|
|
277
142
|
|
|
278
|
-
console.log('[securenow] Next.js App -> service.name=%s', serviceName
|
|
143
|
+
console.log('[securenow] Next.js App -> service.name=%s apiKey=%s credentials=%s', serviceName,
|
|
144
|
+
appConfig.resolveApiKey() ? 'set' : 'missing', appConfig.describeCredentialsSource());
|
|
279
145
|
console.log('[securenow] Environment: %s', deploymentEnvironment);
|
|
280
146
|
|
|
281
147
|
// -------- Body Capture Configuration --------
|
|
282
148
|
// Opt-out default: set config.capture.body=false (or options.captureBody=false) to disable.
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
149
|
+
const requestCapture = createRequestCapture({
|
|
150
|
+
captureBody: options.captureBody ?? appConfig.boolConfig('capture.body', true),
|
|
151
|
+
captureMultipart: appConfig.boolConfig('capture.multipart', true),
|
|
152
|
+
maxBodySize: appConfig.numberConfig('capture.maxBodySize', 10240, 1024),
|
|
153
|
+
customSensitiveFields: appConfig.listConfig('capture.sensitiveFields'),
|
|
154
|
+
});
|
|
155
|
+
const { captureBody } = requestCapture;
|
|
287
156
|
|
|
288
157
|
// -------- Log environment detection --------
|
|
289
158
|
if (!isVercel) {
|
|
@@ -432,11 +301,13 @@ function registerSecureNow(options = {}) {
|
|
|
432
301
|
);
|
|
433
302
|
}
|
|
434
303
|
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
304
|
+
// Headers (redacted) and body, the same attributes as every other SecureNow integration.
|
|
305
|
+
// The body is observed through the request's own emit('data') while Next.js reads it:
|
|
306
|
+
// no listener is added and nothing is read on Next.js's behalf.
|
|
307
|
+
requestCapture.captureHeaders(span, request);
|
|
308
|
+
requestCapture.observeBody(span, request);
|
|
309
|
+
globalThis[HTTP_CAPTURE_ACTIVE] = true;
|
|
310
|
+
|
|
440
311
|
} catch (error) {
|
|
441
312
|
// Silently fail to not break the request
|
|
442
313
|
if (otelLogLevel === 'debug') {
|
|
@@ -519,7 +390,10 @@ function registerSecureNow(options = {}) {
|
|
|
519
390
|
let _crashLoggerProvider = null;
|
|
520
391
|
require('./crash-capture').installCrashCapture({
|
|
521
392
|
getLogger: () => (_crashLoggerProvider ? _crashLoggerProvider.getLogger('securenow.crash', '1.0.0') : null),
|
|
522
|
-
flush: () =>
|
|
393
|
+
flush: () => Promise.all([
|
|
394
|
+
_crashLoggerProvider ? _crashLoggerProvider.forceFlush() : undefined,
|
|
395
|
+
require('./crash-capture').flushTracerProvider(),
|
|
396
|
+
]),
|
|
523
397
|
isSdkError: (e) => _isOtlpTransientError(e) && (_looksLikeOtlpStack(e) || _looksLikeConfiguredOtlpEndpoint(e)),
|
|
524
398
|
onSdkError: _reportSuppressedOtlpError,
|
|
525
399
|
});
|
|
@@ -565,10 +439,16 @@ function registerSecureNow(options = {}) {
|
|
|
565
439
|
headers
|
|
566
440
|
}));
|
|
567
441
|
|
|
442
|
+
// The batch exporter NodeSDK would build from traceExporter, plus the processor that puts Next.js's
|
|
443
|
+
// route on the request's HTTP span (see next-route.js).
|
|
444
|
+
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
|
|
568
445
|
const sdk = new NodeSDK({
|
|
569
446
|
...nodeSdkTelemetryOptions,
|
|
570
447
|
serviceName: serviceName,
|
|
571
|
-
|
|
448
|
+
spanProcessors: [
|
|
449
|
+
new BatchSpanProcessor(traceExporter),
|
|
450
|
+
require('./next-route').createNextRouteProcessor(),
|
|
451
|
+
],
|
|
572
452
|
instrumentations: [httpInstrumentation],
|
|
573
453
|
resource: createResource({
|
|
574
454
|
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
@@ -599,16 +479,21 @@ function registerSecureNow(options = {}) {
|
|
|
599
479
|
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: deploymentEnvironment,
|
|
600
480
|
}),
|
|
601
481
|
// sdk-logs >= 0.220 takes a single options object ({ exporter }).
|
|
602
|
-
|
|
482
|
+
// Secret shapes in log lines are redacted before export (log-redaction.js).
|
|
483
|
+
processors: require('./log-redaction').logProcessors(appConfig, new BatchLogRecordProcessor({ exporter: logExporter })),
|
|
603
484
|
});
|
|
604
485
|
_crashLoggerProvider = loggerProvider;
|
|
605
486
|
|
|
606
|
-
// Patch console to forward logs as OTLP log records
|
|
487
|
+
// Patch console to forward logs as OTLP log records, formatted like the Node preload's
|
|
488
|
+
// (printf placeholders, JSON objects, errors with their stack).
|
|
607
489
|
const logger = loggerProvider.getLogger('console', '1.0.0');
|
|
608
|
-
const SeverityNumber = { INFO: 9, WARN: 13, ERROR: 17 };
|
|
490
|
+
const SeverityNumber = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
|
|
609
491
|
const origLog = console.log;
|
|
492
|
+
const origInfo = console.info;
|
|
610
493
|
const origWarn = console.warn;
|
|
611
494
|
const origError = console.error;
|
|
495
|
+
const origDebug = console.debug;
|
|
496
|
+
const { formatLogArgs } = require('./log-format');
|
|
612
497
|
|
|
613
498
|
const { context: otelContext, trace: otelTrace } = require('@opentelemetry/api');
|
|
614
499
|
function _emitLog(sn, st, args) {
|
|
@@ -618,7 +503,8 @@ function registerSecureNow(options = {}) {
|
|
|
618
503
|
logger.emit({
|
|
619
504
|
severityNumber: sn,
|
|
620
505
|
severityText: st,
|
|
621
|
-
body: args
|
|
506
|
+
body: formatLogArgs(args),
|
|
507
|
+
attributes: { 'log.source': 'console', 'log.method': st.toLowerCase() },
|
|
622
508
|
...(spanCtx && { context: activeCtx }),
|
|
623
509
|
});
|
|
624
510
|
} catch (_) {}
|
|
@@ -627,6 +513,10 @@ function registerSecureNow(options = {}) {
|
|
|
627
513
|
origLog.apply(console, args);
|
|
628
514
|
_emitLog(SeverityNumber.INFO, 'INFO', args);
|
|
629
515
|
};
|
|
516
|
+
console.info = (...args) => {
|
|
517
|
+
origInfo.apply(console, args);
|
|
518
|
+
_emitLog(SeverityNumber.INFO, 'INFO', args);
|
|
519
|
+
};
|
|
630
520
|
console.warn = (...args) => {
|
|
631
521
|
origWarn.apply(console, args);
|
|
632
522
|
_emitLog(SeverityNumber.WARN, 'WARN', args);
|
|
@@ -635,6 +525,10 @@ function registerSecureNow(options = {}) {
|
|
|
635
525
|
origError.apply(console, args);
|
|
636
526
|
_emitLog(SeverityNumber.ERROR, 'ERROR', args);
|
|
637
527
|
};
|
|
528
|
+
console.debug = (...args) => {
|
|
529
|
+
origDebug.apply(console, args);
|
|
530
|
+
_emitLog(SeverityNumber.DEBUG, 'DEBUG', args);
|
|
531
|
+
};
|
|
638
532
|
|
|
639
533
|
console.log('[securenow] Logging: ENABLED -> %s', logsUrl);
|
|
640
534
|
|
|
@@ -708,10 +602,7 @@ function registerSecureNow(options = {}) {
|
|
|
708
602
|
console.log('[securenow] - Geographic data (Vercel/Cloudflare)');
|
|
709
603
|
console.log('[securenow] - Request IDs, CSRF tokens, Auth presence');
|
|
710
604
|
console.log('[securenow] - Response status, content-type, content-length');
|
|
711
|
-
console.log('[securenow]
|
|
712
|
-
if (captureBody) {
|
|
713
|
-
console.log('[securenow] For body capture in Next.js, use: import "securenow/nextjs-auto-capture"');
|
|
714
|
-
}
|
|
605
|
+
console.log('[securenow] - Request headers (redacted)%s', captureBody ? ' and JSON/form/GraphQL bodies (redacted)' : '');
|
|
715
606
|
|
|
716
607
|
// Optional test span
|
|
717
608
|
if (appConfig.boolConfig('runtime.testSpan', false)) {
|
|
@@ -757,6 +648,8 @@ function registerSecureNow(options = {}) {
|
|
|
757
648
|
aws: firewallOptions.aws,
|
|
758
649
|
gcp: firewallOptions.gcp,
|
|
759
650
|
});
|
|
651
|
+
// Zero-code session revocation / session-theft capture (config.sessions.guard / .capture).
|
|
652
|
+
requireRuntimeModule('./sessions').installHttpGuard(appConfig.resolveSessionsOptions());
|
|
760
653
|
} catch (e) {
|
|
761
654
|
console.warn('[securenow] Firewall init failed:', e.message);
|
|
762
655
|
}
|