@replayio-app-building/netlify-recorder 0.21.0 → 0.23.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/dist/index.d.ts +0 -18
- package/dist/index.js +43 -48
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -205,24 +205,6 @@ interface CreateRecordingRequestHandlerOptions extends FinishRequestOptions {
|
|
|
205
205
|
*/
|
|
206
206
|
declare function createRecordingRequestHandler(handler: (event: NetlifyEvent | NetlifyV2Request, context?: unknown) => Promise<HandlerResponse>, options: CreateRecordingRequestHandlerOptions): (event: NetlifyEvent | NetlifyV2Request, context?: unknown) => Promise<HandlerResponse>;
|
|
207
207
|
|
|
208
|
-
/**
|
|
209
|
-
* Redacts sensitive environment variable values from blob data.
|
|
210
|
-
*
|
|
211
|
-
* This function performs two passes:
|
|
212
|
-
*
|
|
213
|
-
* Pass 1 — Redact env reads:
|
|
214
|
-
* For each captured EnvRead whose value should be redacted,
|
|
215
|
-
* replace the value with a "*"-repeated string of the same length.
|
|
216
|
-
*
|
|
217
|
-
* Pass 2 — Scrub the rest of the blob:
|
|
218
|
-
* Any redacted value that appears elsewhere in the blob data
|
|
219
|
-
* (network call headers, bodies, request info headers/body) is
|
|
220
|
-
* also replaced with the same mask. This prevents secrets from
|
|
221
|
-
* leaking through, e.g., an Authorization header that embeds
|
|
222
|
-
* an API key captured via process.env.
|
|
223
|
-
*
|
|
224
|
-
* The function returns a new BlobData object; the input is not mutated.
|
|
225
|
-
*/
|
|
226
208
|
declare function redactBlobData(blobData: BlobData): BlobData;
|
|
227
209
|
|
|
228
210
|
interface RecordingResult {
|
package/dist/index.js
CHANGED
|
@@ -417,11 +417,8 @@ function shouldRedact(key, value) {
|
|
|
417
417
|
if (value === void 0 || value.length <= 8) return false;
|
|
418
418
|
return true;
|
|
419
419
|
}
|
|
420
|
-
function replaceAll(text, searchValue,
|
|
421
|
-
return text.split(searchValue).join(
|
|
422
|
-
}
|
|
423
|
-
function buildMask(value) {
|
|
424
|
-
return "*".repeat(value.length);
|
|
420
|
+
function replaceAll(text, searchValue, replacement) {
|
|
421
|
+
return text.split(searchValue).join(replacement);
|
|
425
422
|
}
|
|
426
423
|
function buildPlaceholder(key) {
|
|
427
424
|
if (key === "DATABASE_URL" || key.endsWith("_DATABASE_URL")) {
|
|
@@ -430,20 +427,18 @@ function buildPlaceholder(key) {
|
|
|
430
427
|
return `placeholder_${key.toLowerCase()}`;
|
|
431
428
|
}
|
|
432
429
|
function redactBlobData(blobData) {
|
|
433
|
-
const
|
|
434
|
-
const placeholders = /* @__PURE__ */ new Map();
|
|
430
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
435
431
|
const redactedEnvReads = blobData.capturedData.envReads.map(
|
|
436
432
|
(read) => {
|
|
437
433
|
if (shouldRedact(read.key, read.value) && read.value !== void 0) {
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return { ...read, value: mask };
|
|
434
|
+
const placeholder = buildPlaceholder(read.key);
|
|
435
|
+
replacements.set(read.value, placeholder);
|
|
436
|
+
return { ...read, value: placeholder };
|
|
442
437
|
}
|
|
443
438
|
return { ...read };
|
|
444
439
|
}
|
|
445
440
|
);
|
|
446
|
-
if (
|
|
441
|
+
if (replacements.size === 0) {
|
|
447
442
|
return {
|
|
448
443
|
...blobData,
|
|
449
444
|
capturedData: {
|
|
@@ -452,14 +447,14 @@ function redactBlobData(blobData) {
|
|
|
452
447
|
}
|
|
453
448
|
};
|
|
454
449
|
}
|
|
455
|
-
const
|
|
450
|
+
const sorted = [...replacements.entries()].sort(
|
|
456
451
|
(a, b) => b[0].length - a[0].length
|
|
457
452
|
);
|
|
458
453
|
function scrub(text) {
|
|
459
454
|
if (text === void 0) return void 0;
|
|
460
455
|
let result = text;
|
|
461
|
-
for (const [original,
|
|
462
|
-
result = replaceAll(result, original,
|
|
456
|
+
for (const [original, placeholder] of sorted) {
|
|
457
|
+
result = replaceAll(result, original, placeholder);
|
|
463
458
|
}
|
|
464
459
|
return result;
|
|
465
460
|
}
|
|
@@ -470,29 +465,11 @@ function redactBlobData(blobData) {
|
|
|
470
465
|
}
|
|
471
466
|
return out;
|
|
472
467
|
}
|
|
473
|
-
const sortedPlaceholders = [...placeholders.entries()].sort(
|
|
474
|
-
(a, b) => b[0].length - a[0].length
|
|
475
|
-
);
|
|
476
|
-
function scrubForRequest(text) {
|
|
477
|
-
if (text === void 0) return void 0;
|
|
478
|
-
let result = text;
|
|
479
|
-
for (const [original, placeholder] of sortedPlaceholders) {
|
|
480
|
-
result = replaceAll(result, original, placeholder);
|
|
481
|
-
}
|
|
482
|
-
return result;
|
|
483
|
-
}
|
|
484
|
-
function scrubHeadersForRequest(headers) {
|
|
485
|
-
const out = {};
|
|
486
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
487
|
-
out[k] = scrubForRequest(v) ?? v;
|
|
488
|
-
}
|
|
489
|
-
return out;
|
|
490
|
-
}
|
|
491
468
|
const redactedRequestInfo = {
|
|
492
469
|
...blobData.requestInfo,
|
|
493
|
-
url:
|
|
494
|
-
headers:
|
|
495
|
-
body:
|
|
470
|
+
url: scrub(blobData.requestInfo.url) ?? blobData.requestInfo.url,
|
|
471
|
+
headers: scrubHeaders(blobData.requestInfo.headers),
|
|
472
|
+
body: scrub(blobData.requestInfo.body)
|
|
496
473
|
};
|
|
497
474
|
const redactedNetworkCalls = blobData.capturedData.networkCalls.map(
|
|
498
475
|
(call) => ({
|
|
@@ -595,10 +572,37 @@ function createRecordingRequestHandler(handler, options) {
|
|
|
595
572
|
let response;
|
|
596
573
|
try {
|
|
597
574
|
response = await handler(event, context);
|
|
598
|
-
} catch (
|
|
575
|
+
} catch (handlerErr) {
|
|
576
|
+
const errorMessage = handlerErr instanceof Error ? handlerErr.message : String(handlerErr);
|
|
577
|
+
const errorResponse = {
|
|
578
|
+
statusCode: 500,
|
|
579
|
+
body: JSON.stringify({ error: errorMessage })
|
|
580
|
+
};
|
|
581
|
+
const finishOpts2 = { ...options, requestId };
|
|
582
|
+
const ctx2 = context;
|
|
583
|
+
if (ctx2 && typeof ctx2.waitUntil === "function") {
|
|
584
|
+
ctx2.waitUntil(
|
|
585
|
+
finishRequest(reqContext, options.callbacks, errorResponse, finishOpts2).catch(
|
|
586
|
+
(finishErr) => {
|
|
587
|
+
console.error(
|
|
588
|
+
`netlify-recorder: background finishRequest failed after handler error (handler: ${options.handlerPath ?? "unknown"})`,
|
|
589
|
+
finishErr
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
);
|
|
594
|
+
} else {
|
|
595
|
+
await finishRequest(reqContext, options.callbacks, errorResponse, finishOpts2).catch(
|
|
596
|
+
(finishErr) => {
|
|
597
|
+
console.error(
|
|
598
|
+
`netlify-recorder: finishRequest failed after handler error (handler: ${options.handlerPath ?? "unknown"})`,
|
|
599
|
+
finishErr
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
);
|
|
603
|
+
}
|
|
599
604
|
setCurrentRequestId(null);
|
|
600
|
-
|
|
601
|
-
throw err;
|
|
605
|
+
throw handlerErr;
|
|
602
606
|
}
|
|
603
607
|
reqContext.cleanup();
|
|
604
608
|
setCurrentRequestId(null);
|
|
@@ -643,15 +647,6 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
|
|
|
643
647
|
} else {
|
|
644
648
|
blobData = blobUrlOrData;
|
|
645
649
|
}
|
|
646
|
-
for (const read of blobData.capturedData.envReads) {
|
|
647
|
-
if (read.value && read.value.length > 8 && /^\*+$/.test(read.value)) {
|
|
648
|
-
if (read.key === "DATABASE_URL" || read.key.endsWith("_DATABASE_URL")) {
|
|
649
|
-
read.value = "postgresql://replay:replay@localhost:5432/replay";
|
|
650
|
-
} else {
|
|
651
|
-
read.value = `placeholder_${read.key.toLowerCase()}`;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
650
|
const networkHandle = installNetworkInterceptor(
|
|
656
651
|
"replay",
|
|
657
652
|
blobData.capturedData.networkCalls
|