securenow 7.7.11 → 7.7.13
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/cli/init.js +33 -4
- package/free-trial-banner.js +2 -1
- package/nextjs.js +14 -5
- package/package.json +1 -1
- package/tracing.js +146 -102
package/cli/init.js
CHANGED
|
@@ -137,6 +137,9 @@ async function initNextJs(dir, project, flags) {
|
|
|
137
137
|
fs.writeFileSync(newConfigPath, `/** @type {import('next').NextConfig} */
|
|
138
138
|
const nextConfig = {
|
|
139
139
|
serverExternalPackages: ['securenow'],
|
|
140
|
+
outputFileTracingIncludes: {
|
|
141
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
142
|
+
},
|
|
140
143
|
};
|
|
141
144
|
|
|
142
145
|
export default nextConfig;
|
|
@@ -149,21 +152,47 @@ function patchNextConfig(configPath, major) {
|
|
|
149
152
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
150
153
|
const serverExternalWithSecureNow = /serverExternalPackages\s*:\s*\[[\s\S]*?['"]securenow['"][\s\S]*?\]/m.test(content);
|
|
151
154
|
const serverComponentsWithSecureNow = /serverComponentsExternalPackages\s*:\s*\[[\s\S]*?['"]securenow['"][\s\S]*?\]/m.test(content);
|
|
152
|
-
|
|
155
|
+
const traceIncludeWithSecureNow = /outputFileTracingIncludes\s*:\s*{[\s\S]*?node_modules\/securenow\/\*\*/m.test(content);
|
|
156
|
+
if ((serverExternalWithSecureNow || serverComponentsWithSecureNow || content.includes('withSecureNow(')) && traceIncludeWithSecureNow) {
|
|
153
157
|
return 'already';
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
if (major < 15) return 'manual';
|
|
157
161
|
|
|
162
|
+
function addTraceInclude(nextContent) {
|
|
163
|
+
if (traceIncludeWithSecureNow || /outputFileTracingIncludes\s*:/.test(nextContent)) return nextContent;
|
|
164
|
+
const insert = ` outputFileTracingIncludes: {\n '/*': ['./node_modules/securenow/**/*'],\n },\n`;
|
|
165
|
+
const patterns = [
|
|
166
|
+
/(const\s+nextConfig\s*=\s*{\s*\r?\n)/,
|
|
167
|
+
/(export\s+default\s+{\s*\r?\n)/,
|
|
168
|
+
/(module\.exports\s*=\s*{\s*\r?\n)/,
|
|
169
|
+
];
|
|
170
|
+
for (const pattern of patterns) {
|
|
171
|
+
if (pattern.test(nextContent)) return nextContent.replace(pattern, `$1${insert}`);
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (serverExternalWithSecureNow || serverComponentsWithSecureNow || content.includes('withSecureNow(')) {
|
|
177
|
+
const withTraceInclude = addTraceInclude(content);
|
|
178
|
+
if (withTraceInclude && withTraceInclude !== content) {
|
|
179
|
+
fs.writeFileSync(configPath, withTraceInclude, 'utf8');
|
|
180
|
+
return 'patched';
|
|
181
|
+
}
|
|
182
|
+
return 'manual';
|
|
183
|
+
}
|
|
184
|
+
|
|
158
185
|
const existingServerExternal = content.match(/serverExternalPackages\s*:\s*\[([\s\S]*?)\]/m);
|
|
159
186
|
if (existingServerExternal) {
|
|
160
187
|
const current = existingServerExternal[1].trim().replace(/,\s*$/, '');
|
|
161
188
|
const replacement = `serverExternalPackages: [${current ? `${current}, ` : ''}'securenow']`;
|
|
162
|
-
|
|
189
|
+
let nextContent = content.replace(existingServerExternal[0], replacement);
|
|
190
|
+
nextContent = addTraceInclude(nextContent) || nextContent;
|
|
191
|
+
fs.writeFileSync(configPath, nextContent, 'utf8');
|
|
163
192
|
return 'patched';
|
|
164
193
|
}
|
|
165
194
|
|
|
166
|
-
const insert = ` serverExternalPackages: ['securenow'],\n`;
|
|
195
|
+
const insert = ` serverExternalPackages: ['securenow'],\n outputFileTracingIncludes: {\n '/*': ['./node_modules/securenow/**/*'],\n },\n`;
|
|
167
196
|
const patterns = [
|
|
168
197
|
/(const\s+nextConfig\s*=\s*{\s*\r?\n)/,
|
|
169
198
|
/(export\s+default\s+{\s*\r?\n)/,
|
|
@@ -212,7 +241,7 @@ function printAgentPrompt(kind, filename, major, project) {
|
|
|
212
241
|
? `Merge this into ${filename}: in register(), return unless process.env.NEXT_RUNTIME === "nodejs"; then dynamically import "securenow/nextjs" and "securenow/nextjs-auto-capture" with /* webpackIgnore: true */ so Next does not bundle OpenTelemetry internals. Preserve all existing instrumentation.`
|
|
213
242
|
: null,
|
|
214
243
|
kind === 'next-config' && major >= 15
|
|
215
|
-
? `Update ${filename} while preserving existing config: add securenow to serverExternalPackages, e.g. serverExternalPackages: [...(existing || []), "securenow"].`
|
|
244
|
+
? `Update ${filename} while preserving existing config: add securenow to serverExternalPackages, e.g. serverExternalPackages: [...(existing || []), "securenow"], and include SecureNow runtime files for standalone output with outputFileTracingIncludes: { "/*": ["./node_modules/securenow/**/*"] }.`
|
|
216
245
|
: null,
|
|
217
246
|
kind === 'next-config' && major < 15
|
|
218
247
|
? `Update ${filename} while preserving existing config: enable experimental.instrumentationHook and add securenow to experimental.serverComponentsExternalPackages.`
|
package/free-trial-banner.js
CHANGED
|
@@ -97,7 +97,8 @@ var BANNER_SCRIPT =
|
|
|
97
97
|
*/
|
|
98
98
|
function patchHttpForBanner() {
|
|
99
99
|
try {
|
|
100
|
-
var
|
|
100
|
+
var nodeRequire = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : eval('require');
|
|
101
|
+
var http = nodeRequire('node:http');
|
|
101
102
|
var _origWrite = http.ServerResponse.prototype.write;
|
|
102
103
|
var _origEnd = http.ServerResponse.prototype.end;
|
|
103
104
|
|
package/nextjs.js
CHANGED
|
@@ -34,6 +34,15 @@ const env = appConfig.env;
|
|
|
34
34
|
|
|
35
35
|
let isRegistered = false;
|
|
36
36
|
|
|
37
|
+
function requireRuntimeModule(name) {
|
|
38
|
+
const nodeRequire = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : eval('require');
|
|
39
|
+
return nodeRequire(name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function requireNodeBuiltin(name) {
|
|
43
|
+
return requireRuntimeModule(name);
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
function createResource(attributes) {
|
|
38
47
|
if (typeof otelResources.resourceFromAttributes === 'function') {
|
|
39
48
|
return otelResources.resourceFromAttributes(attributes);
|
|
@@ -501,7 +510,7 @@ function registerSecureNow(options = {}) {
|
|
|
501
510
|
|
|
502
511
|
// Auto-log every incoming HTTP request/response
|
|
503
512
|
try {
|
|
504
|
-
const http =
|
|
513
|
+
const http = requireNodeBuiltin('node:http');
|
|
505
514
|
const originalEmit = http.Server.prototype.emit;
|
|
506
515
|
http.Server.prototype.emit = function (event, req, res) {
|
|
507
516
|
if (event === 'request' && req && res) {
|
|
@@ -549,8 +558,8 @@ function registerSecureNow(options = {}) {
|
|
|
549
558
|
} catch (_) {}
|
|
550
559
|
|
|
551
560
|
// Graceful shutdown for logs
|
|
552
|
-
process.on('SIGTERM', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try {
|
|
553
|
-
process.on('SIGINT', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try {
|
|
561
|
+
process.on('SIGTERM', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { requireRuntimeModule('./firewall').shutdown(); } catch (_) {} });
|
|
562
|
+
process.on('SIGINT', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { requireRuntimeModule('./firewall').shutdown(); } catch (_) {} });
|
|
554
563
|
} catch (e) {
|
|
555
564
|
console.warn('[securenow] ⚠️ Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
|
|
556
565
|
}
|
|
@@ -563,7 +572,7 @@ function registerSecureNow(options = {}) {
|
|
|
563
572
|
|
|
564
573
|
// Free trial banner (optional — may not be bundled in standalone builds)
|
|
565
574
|
try {
|
|
566
|
-
const { isFreeTrial, patchHttpForBanner } =
|
|
575
|
+
const { isFreeTrial, patchHttpForBanner } = requireRuntimeModule('./free-trial-banner');
|
|
567
576
|
if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
|
|
568
577
|
patchHttpForBanner();
|
|
569
578
|
}
|
|
@@ -607,7 +616,7 @@ function registerSecureNow(options = {}) {
|
|
|
607
616
|
const firewallOptions = appConfig.resolveFirewallOptions();
|
|
608
617
|
if (firewallOptions.apiKey && firewallOptions.enabled) {
|
|
609
618
|
try {
|
|
610
|
-
|
|
619
|
+
requireRuntimeModule('./firewall').init({
|
|
611
620
|
apiKey: firewallOptions.apiKey,
|
|
612
621
|
appKey: firewallOptions.appKey,
|
|
613
622
|
environment: deploymentEnvironment || firewallOptions.environment,
|
package/package.json
CHANGED
package/tracing.js
CHANGED
|
@@ -124,9 +124,9 @@ function extractBoundary(contentType) {
|
|
|
124
124
|
return match ? (match[1] || match[2]) : null;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function
|
|
127
|
+
function createMultipartMetaCollector(contentType, sensitiveFields, maxTextFieldSize, onComplete) {
|
|
128
128
|
const boundary = extractBoundary(contentType);
|
|
129
|
-
if (!boundary) { onComplete({ error: 'BOUNDARY_NOT_FOUND' }); return; }
|
|
129
|
+
if (!boundary) { onComplete({ error: 'BOUNDARY_NOT_FOUND' }); return null; }
|
|
130
130
|
|
|
131
131
|
const result = { fields: Object.create(null), files: [] };
|
|
132
132
|
let totalSize = 0;
|
|
@@ -219,13 +219,14 @@ function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFiel
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
function onData(chunk) {
|
|
223
|
+
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
224
|
+
totalSize += data.length;
|
|
225
|
+
buf = Buffer.concat([buf, data]);
|
|
225
226
|
drain();
|
|
226
|
-
}
|
|
227
|
+
}
|
|
227
228
|
|
|
228
|
-
|
|
229
|
+
function onEnd() {
|
|
229
230
|
try {
|
|
230
231
|
if (!inHeaders && fldName) {
|
|
231
232
|
bodyBytes += buf.length;
|
|
@@ -236,7 +237,16 @@ function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFiel
|
|
|
236
237
|
} catch (e) {
|
|
237
238
|
onComplete({ error: 'PARSE_ERROR' });
|
|
238
239
|
}
|
|
239
|
-
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { onData, onEnd };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function collectMultipartMeta(request, contentType, sensitiveFields, maxTextFieldSize, onComplete) {
|
|
246
|
+
const collector = createMultipartMetaCollector(contentType, sensitiveFields, maxTextFieldSize, onComplete);
|
|
247
|
+
if (!collector) return;
|
|
248
|
+
request.on('data', collector.onData);
|
|
249
|
+
request.on('end', collector.onEnd);
|
|
240
250
|
}
|
|
241
251
|
|
|
242
252
|
// -------- ESM detection --------
|
|
@@ -342,6 +352,133 @@ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveField
|
|
|
342
352
|
|
|
343
353
|
const captureMultipart = !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_MULTIPART') ?? ''));
|
|
344
354
|
|
|
355
|
+
const BODY_CAPTURE_PATCH = Symbol.for('securenow.bodyCapture.emitPatch');
|
|
356
|
+
|
|
357
|
+
function installRequestBodyObserver(span, request, contentType) {
|
|
358
|
+
if (!request || request[BODY_CAPTURE_PATCH]) return;
|
|
359
|
+
|
|
360
|
+
const normalizedContentType = String(contentType || '').toLowerCase();
|
|
361
|
+
const isStructuredBody = captureBody && (
|
|
362
|
+
normalizedContentType.includes('application/json') ||
|
|
363
|
+
normalizedContentType.includes('application/graphql') ||
|
|
364
|
+
normalizedContentType.includes('application/x-www-form-urlencoded')
|
|
365
|
+
);
|
|
366
|
+
const isMultipartBody = normalizedContentType.includes('multipart/form-data');
|
|
367
|
+
|
|
368
|
+
if (!isStructuredBody && !isMultipartBody) return;
|
|
369
|
+
|
|
370
|
+
if (isMultipartBody && !captureMultipart) {
|
|
371
|
+
span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
|
|
372
|
+
span.setAttribute('http.request.body.type', 'multipart');
|
|
373
|
+
span.setAttribute('http.request.body.note', 'Multipart capture disabled (SECURENOW_CAPTURE_MULTIPART=0)');
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let chunks = [];
|
|
378
|
+
let size = 0;
|
|
379
|
+
let structuredDone = false;
|
|
380
|
+
|
|
381
|
+
const multipartCollector = isMultipartBody && captureMultipart
|
|
382
|
+
? createMultipartMetaCollector(normalizedContentType, allSensitiveFields, 1000, ({ error, parsed, totalSize }) => {
|
|
383
|
+
try {
|
|
384
|
+
if (error === 'BOUNDARY_NOT_FOUND') {
|
|
385
|
+
span.setAttribute('http.request.body', '[MULTIPART - BOUNDARY NOT FOUND]');
|
|
386
|
+
span.setAttribute('http.request.body.type', 'multipart');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (error) {
|
|
390
|
+
span.setAttribute('http.request.body', '[MULTIPART - PARSE ERROR]');
|
|
391
|
+
span.setAttribute('http.request.body.type', 'multipart');
|
|
392
|
+
span.setAttribute('http.request.body.parse_error', true);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
span.setAttributes({
|
|
396
|
+
'http.request.body': JSON.stringify(parsed).substring(0, maxBodySize),
|
|
397
|
+
'http.request.body.type': 'multipart',
|
|
398
|
+
'http.request.body.size': totalSize,
|
|
399
|
+
'http.request.body.fields_count': Object.keys(parsed.fields).length,
|
|
400
|
+
'http.request.body.files_count': parsed.files.length,
|
|
401
|
+
});
|
|
402
|
+
} catch (e) {
|
|
403
|
+
span.setAttribute('http.request.body', '[MULTIPART - PARSE ERROR]');
|
|
404
|
+
span.setAttribute('http.request.body.type', 'multipart');
|
|
405
|
+
span.setAttribute('http.request.body.parse_error', true);
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
: null;
|
|
409
|
+
|
|
410
|
+
if (isMultipartBody && captureMultipart && !multipartCollector) return;
|
|
411
|
+
|
|
412
|
+
function finishStructuredCapture() {
|
|
413
|
+
if (!isStructuredBody || structuredDone) return;
|
|
414
|
+
structuredDone = true;
|
|
415
|
+
|
|
416
|
+
if (size > maxBodySize) {
|
|
417
|
+
span.setAttribute('http.request.body', `[TOO LARGE: ${size} bytes]`);
|
|
418
|
+
span.setAttribute('http.request.body.size', size);
|
|
419
|
+
chunks = [];
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (chunks.length === 0) return;
|
|
424
|
+
|
|
425
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
426
|
+
chunks = [];
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
if (normalizedContentType.includes('application/graphql')) {
|
|
430
|
+
const redacted = redactGraphQLQuery(body, allSensitiveFields);
|
|
431
|
+
span.setAttributes({
|
|
432
|
+
'http.request.body': redacted.substring(0, maxBodySize),
|
|
433
|
+
'http.request.body.type': 'graphql',
|
|
434
|
+
'http.request.body.size': size,
|
|
435
|
+
});
|
|
436
|
+
} else if (normalizedContentType.includes('application/json')) {
|
|
437
|
+
const parsed = JSON.parse(body);
|
|
438
|
+
const redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
439
|
+
span.setAttributes({
|
|
440
|
+
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
441
|
+
'http.request.body.type': 'json',
|
|
442
|
+
'http.request.body.size': size,
|
|
443
|
+
});
|
|
444
|
+
} else if (normalizedContentType.includes('application/x-www-form-urlencoded')) {
|
|
445
|
+
const parsed = Object.fromEntries(new URLSearchParams(body));
|
|
446
|
+
const redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
447
|
+
span.setAttributes({
|
|
448
|
+
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
449
|
+
'http.request.body.type': 'form',
|
|
450
|
+
'http.request.body.size': size,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
} catch (e) {
|
|
454
|
+
span.setAttribute('http.request.body', '[UNPARSEABLE - REDACTED FOR SAFETY]');
|
|
455
|
+
span.setAttribute('http.request.body.parse_error', true);
|
|
456
|
+
span.setAttribute('http.request.body.size', size);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const originalEmit = request.emit;
|
|
461
|
+
request[BODY_CAPTURE_PATCH] = true;
|
|
462
|
+
request.emit = function securenowObservedEmit(event, ...args) {
|
|
463
|
+
try {
|
|
464
|
+
if (event === 'data' && args.length > 0) {
|
|
465
|
+
const chunk = Buffer.isBuffer(args[0]) ? args[0] : Buffer.from(args[0]);
|
|
466
|
+
if (isStructuredBody) {
|
|
467
|
+
size += chunk.length;
|
|
468
|
+
if (size <= maxBodySize) chunks.push(chunk);
|
|
469
|
+
}
|
|
470
|
+
if (multipartCollector) multipartCollector.onData(chunk);
|
|
471
|
+
} else if (event === 'end') {
|
|
472
|
+
finishStructuredCapture();
|
|
473
|
+
if (multipartCollector) multipartCollector.onEnd();
|
|
474
|
+
}
|
|
475
|
+
} catch (_) {
|
|
476
|
+
// Body capture must never interfere with the application request stream.
|
|
477
|
+
}
|
|
478
|
+
return originalEmit.apply(this, [event, ...args]);
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
345
482
|
// -------- Trusted proxy IP resolution --------
|
|
346
483
|
const { resolveClientIpWithDetails } = require('./resolve-ip');
|
|
347
484
|
|
|
@@ -391,100 +528,7 @@ const httpInstrumentation = new HttpInstrumentation({
|
|
|
391
528
|
|
|
392
529
|
if ((captureBody || captureMultipart) && request.method && ['POST', 'PUT', 'PATCH'].includes(request.method)) {
|
|
393
530
|
const contentType = request.headers['content-type'] || '';
|
|
394
|
-
|
|
395
|
-
if (captureBody && (contentType.includes('application/json') ||
|
|
396
|
-
contentType.includes('application/graphql') ||
|
|
397
|
-
contentType.includes('application/x-www-form-urlencoded'))) {
|
|
398
|
-
|
|
399
|
-
let body = '';
|
|
400
|
-
const chunks = [];
|
|
401
|
-
let size = 0;
|
|
402
|
-
|
|
403
|
-
request.on('data', (chunk) => {
|
|
404
|
-
size += chunk.length;
|
|
405
|
-
if (size <= maxBodySize) {
|
|
406
|
-
chunks.push(chunk);
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
request.on('end', () => {
|
|
411
|
-
if (size <= maxBodySize && chunks.length > 0) {
|
|
412
|
-
body = Buffer.concat(chunks).toString('utf8');
|
|
413
|
-
|
|
414
|
-
try {
|
|
415
|
-
let redacted;
|
|
416
|
-
|
|
417
|
-
if (contentType.includes('application/graphql')) {
|
|
418
|
-
// GraphQL: redact query string
|
|
419
|
-
redacted = redactGraphQLQuery(body, allSensitiveFields);
|
|
420
|
-
span.setAttributes({
|
|
421
|
-
'http.request.body': redacted.substring(0, maxBodySize),
|
|
422
|
-
'http.request.body.type': 'graphql',
|
|
423
|
-
'http.request.body.size': size,
|
|
424
|
-
});
|
|
425
|
-
} else if (contentType.includes('application/json')) {
|
|
426
|
-
// JSON: parse and redact object
|
|
427
|
-
const parsed = JSON.parse(body);
|
|
428
|
-
redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
429
|
-
span.setAttributes({
|
|
430
|
-
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
431
|
-
'http.request.body.type': 'json',
|
|
432
|
-
'http.request.body.size': size,
|
|
433
|
-
});
|
|
434
|
-
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
435
|
-
// Form: parse and redact
|
|
436
|
-
const parsed = Object.fromEntries(new URLSearchParams(body));
|
|
437
|
-
redacted = redactSensitiveData(parsed, allSensitiveFields);
|
|
438
|
-
span.setAttributes({
|
|
439
|
-
'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
|
|
440
|
-
'http.request.body.type': 'form',
|
|
441
|
-
'http.request.body.size': size,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
} catch (e) {
|
|
445
|
-
span.setAttribute('http.request.body', '[UNPARSEABLE - REDACTED FOR SAFETY]');
|
|
446
|
-
span.setAttribute('http.request.body.parse_error', true);
|
|
447
|
-
span.setAttribute('http.request.body.size', size);
|
|
448
|
-
}
|
|
449
|
-
} else if (size > maxBodySize) {
|
|
450
|
-
span.setAttribute('http.request.body', `[TOO LARGE: ${size} bytes]`);
|
|
451
|
-
span.setAttribute('http.request.body.size', size);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
} else if (contentType.includes('multipart/form-data')) {
|
|
455
|
-
if (captureMultipart) {
|
|
456
|
-
collectMultipartMeta(request, contentType, allSensitiveFields, 1000, ({ error, parsed, totalSize }) => {
|
|
457
|
-
try {
|
|
458
|
-
if (error === 'BOUNDARY_NOT_FOUND') {
|
|
459
|
-
span.setAttribute('http.request.body', '[MULTIPART - BOUNDARY NOT FOUND]');
|
|
460
|
-
span.setAttribute('http.request.body.type', 'multipart');
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
if (error) {
|
|
464
|
-
span.setAttribute('http.request.body', '[MULTIPART - PARSE ERROR]');
|
|
465
|
-
span.setAttribute('http.request.body.type', 'multipart');
|
|
466
|
-
span.setAttribute('http.request.body.parse_error', true);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
span.setAttributes({
|
|
470
|
-
'http.request.body': JSON.stringify(parsed).substring(0, maxBodySize),
|
|
471
|
-
'http.request.body.type': 'multipart',
|
|
472
|
-
'http.request.body.size': totalSize,
|
|
473
|
-
'http.request.body.fields_count': Object.keys(parsed.fields).length,
|
|
474
|
-
'http.request.body.files_count': parsed.files.length,
|
|
475
|
-
});
|
|
476
|
-
} catch (e) {
|
|
477
|
-
span.setAttribute('http.request.body', '[MULTIPART - PARSE ERROR]');
|
|
478
|
-
span.setAttribute('http.request.body.type', 'multipart');
|
|
479
|
-
span.setAttribute('http.request.body.parse_error', true);
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
} else {
|
|
483
|
-
span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
|
|
484
|
-
span.setAttribute('http.request.body.type', 'multipart');
|
|
485
|
-
span.setAttribute('http.request.body.note', 'Multipart capture disabled (SECURENOW_CAPTURE_MULTIPART=0)');
|
|
486
|
-
}
|
|
487
|
-
}
|
|
531
|
+
installRequestBodyObserver(span, request, contentType);
|
|
488
532
|
}
|
|
489
533
|
} catch (error) {
|
|
490
534
|
// Silently fail
|