@runtime-digital-twin/sdk 1.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/README.md +214 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/db-wrapper.d.ts +258 -0
- package/dist/db-wrapper.d.ts.map +1 -0
- package/dist/db-wrapper.js +636 -0
- package/dist/event-envelope.d.ts +35 -0
- package/dist/event-envelope.d.ts.map +1 -0
- package/dist/event-envelope.js +101 -0
- package/dist/fastify-plugin.d.ts +29 -0
- package/dist/fastify-plugin.d.ts.map +1 -0
- package/dist/fastify-plugin.js +243 -0
- package/dist/http-sentinels.d.ts +39 -0
- package/dist/http-sentinels.d.ts.map +1 -0
- package/dist/http-sentinels.js +169 -0
- package/dist/http-wrapper.d.ts +25 -0
- package/dist/http-wrapper.d.ts.map +1 -0
- package/dist/http-wrapper.js +477 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/invariants.d.ts +58 -0
- package/dist/invariants.d.ts.map +1 -0
- package/dist/invariants.js +192 -0
- package/dist/multi-service-edge-builder.d.ts +80 -0
- package/dist/multi-service-edge-builder.d.ts.map +1 -0
- package/dist/multi-service-edge-builder.js +107 -0
- package/dist/outbound-matcher.d.ts +192 -0
- package/dist/outbound-matcher.d.ts.map +1 -0
- package/dist/outbound-matcher.js +457 -0
- package/dist/peer-service-resolver.d.ts +22 -0
- package/dist/peer-service-resolver.d.ts.map +1 -0
- package/dist/peer-service-resolver.js +85 -0
- package/dist/redaction.d.ts +111 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +487 -0
- package/dist/replay-logger.d.ts +438 -0
- package/dist/replay-logger.d.ts.map +1 -0
- package/dist/replay-logger.js +434 -0
- package/dist/root-cause-analyzer.d.ts +45 -0
- package/dist/root-cause-analyzer.d.ts.map +1 -0
- package/dist/root-cause-analyzer.js +606 -0
- package/dist/shape-digest-utils.d.ts +45 -0
- package/dist/shape-digest-utils.d.ts.map +1 -0
- package/dist/shape-digest-utils.js +154 -0
- package/dist/trace-bundle-writer.d.ts +52 -0
- package/dist/trace-bundle-writer.d.ts.map +1 -0
- package/dist/trace-bundle-writer.js +267 -0
- package/dist/trace-loader.d.ts +69 -0
- package/dist/trace-loader.d.ts.map +1 -0
- package/dist/trace-loader.js +146 -0
- package/dist/trace-uploader.d.ts +25 -0
- package/dist/trace-uploader.d.ts.map +1 -0
- package/dist/trace-uploader.js +132 -0
- package/package.json +63 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Redaction Module
|
|
4
|
+
*
|
|
5
|
+
* Provides privacy guardrails for trace capture by:
|
|
6
|
+
* - Masking sensitive headers (auth, tokens, cookies)
|
|
7
|
+
* - Redacting passwords and secrets from request/response bodies
|
|
8
|
+
* - Supporting custom redaction rules via config file
|
|
9
|
+
* - Using deterministic hashing for redacted values (stable across runs)
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.SENSITIVE_BODY_FIELDS = exports.SENSITIVE_QUERY_PARAMS = exports.SENSITIVE_HEADERS = void 0;
|
|
13
|
+
exports.computeRedactionHash = computeRedactionHash;
|
|
14
|
+
exports.createRedactedValue = createRedactedValue;
|
|
15
|
+
exports.loadRedactionConfig = loadRedactionConfig;
|
|
16
|
+
exports.setRedactionConfig = setRedactionConfig;
|
|
17
|
+
exports.getRedactionConfig = getRedactionConfig;
|
|
18
|
+
exports.resetRedactionConfig = resetRedactionConfig;
|
|
19
|
+
exports.shouldRedactHeader = shouldRedactHeader;
|
|
20
|
+
exports.shouldRedactQueryParam = shouldRedactQueryParam;
|
|
21
|
+
exports.redactHeaders = redactHeaders;
|
|
22
|
+
exports.redactQueryParams = redactQueryParams;
|
|
23
|
+
exports.redactBodyObject = redactBodyObject;
|
|
24
|
+
exports.redactBodyPatterns = redactBodyPatterns;
|
|
25
|
+
exports.redactBody = redactBody;
|
|
26
|
+
exports.redactUrl = redactUrl;
|
|
27
|
+
exports.containsSensitivePatterns = containsSensitivePatterns;
|
|
28
|
+
const crypto_1 = require("crypto");
|
|
29
|
+
const fs_1 = require("fs");
|
|
30
|
+
const path_1 = require("path");
|
|
31
|
+
/**
|
|
32
|
+
* Default sensitive header names (case-insensitive)
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_SENSITIVE_HEADERS = [
|
|
35
|
+
"authorization",
|
|
36
|
+
"x-api-key",
|
|
37
|
+
"x-auth-token",
|
|
38
|
+
"x-access-token",
|
|
39
|
+
"x-refresh-token",
|
|
40
|
+
"cookie",
|
|
41
|
+
"set-cookie",
|
|
42
|
+
"x-csrf-token",
|
|
43
|
+
"x-xsrf-token",
|
|
44
|
+
"proxy-authorization",
|
|
45
|
+
"www-authenticate",
|
|
46
|
+
"x-amz-security-token",
|
|
47
|
+
"x-api-secret",
|
|
48
|
+
"x-client-secret",
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Default sensitive query parameter names (case-insensitive)
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_SENSITIVE_QUERY_PARAMS = [
|
|
54
|
+
"token",
|
|
55
|
+
"access_token",
|
|
56
|
+
"refresh_token",
|
|
57
|
+
"api_key",
|
|
58
|
+
"apikey",
|
|
59
|
+
"secret",
|
|
60
|
+
"password",
|
|
61
|
+
"pwd",
|
|
62
|
+
"auth",
|
|
63
|
+
"key",
|
|
64
|
+
"session",
|
|
65
|
+
"sid",
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* Default sensitive body field names (case-insensitive)
|
|
69
|
+
*/
|
|
70
|
+
const DEFAULT_SENSITIVE_BODY_FIELDS = [
|
|
71
|
+
"password",
|
|
72
|
+
"passwd",
|
|
73
|
+
"pwd",
|
|
74
|
+
"secret",
|
|
75
|
+
"token",
|
|
76
|
+
"access_token",
|
|
77
|
+
"refresh_token",
|
|
78
|
+
"api_key",
|
|
79
|
+
"apiKey",
|
|
80
|
+
"apikey",
|
|
81
|
+
"private_key",
|
|
82
|
+
"privateKey",
|
|
83
|
+
"secret_key",
|
|
84
|
+
"secretKey",
|
|
85
|
+
"client_secret",
|
|
86
|
+
"clientSecret",
|
|
87
|
+
"ssn",
|
|
88
|
+
"social_security",
|
|
89
|
+
"credit_card",
|
|
90
|
+
"creditCard",
|
|
91
|
+
"card_number",
|
|
92
|
+
"cardNumber",
|
|
93
|
+
"cvv",
|
|
94
|
+
"cvc",
|
|
95
|
+
"pin",
|
|
96
|
+
];
|
|
97
|
+
/**
|
|
98
|
+
* Default body patterns to redact (regex)
|
|
99
|
+
*/
|
|
100
|
+
const DEFAULT_SENSITIVE_BODY_PATTERNS = [
|
|
101
|
+
// Bearer tokens
|
|
102
|
+
/Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*/g,
|
|
103
|
+
// JWT tokens
|
|
104
|
+
/eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*/g,
|
|
105
|
+
// API keys (common formats)
|
|
106
|
+
/(?:api[_-]?key|apikey)[=:]["']?[A-Za-z0-9\-_]{20,}["']?/gi,
|
|
107
|
+
// AWS access keys
|
|
108
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
109
|
+
// Credit card numbers (basic pattern)
|
|
110
|
+
/\b(?:\d{4}[- ]?){3}\d{4}\b/g,
|
|
111
|
+
];
|
|
112
|
+
/**
|
|
113
|
+
* Build default redaction rules
|
|
114
|
+
*/
|
|
115
|
+
function buildDefaultRules() {
|
|
116
|
+
const rules = [];
|
|
117
|
+
// Header rules
|
|
118
|
+
for (const header of DEFAULT_SENSITIVE_HEADERS) {
|
|
119
|
+
rules.push({
|
|
120
|
+
type: "header",
|
|
121
|
+
name: header,
|
|
122
|
+
hash: true,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Query param rules
|
|
126
|
+
for (const param of DEFAULT_SENSITIVE_QUERY_PARAMS) {
|
|
127
|
+
rules.push({
|
|
128
|
+
type: "query_param",
|
|
129
|
+
name: param,
|
|
130
|
+
hash: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Body field rules
|
|
134
|
+
for (const field of DEFAULT_SENSITIVE_BODY_FIELDS) {
|
|
135
|
+
rules.push({
|
|
136
|
+
type: "body_field",
|
|
137
|
+
name: field,
|
|
138
|
+
hash: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Body pattern rules
|
|
142
|
+
for (const pattern of DEFAULT_SENSITIVE_BODY_PATTERNS) {
|
|
143
|
+
rules.push({
|
|
144
|
+
type: "body_pattern",
|
|
145
|
+
pattern: pattern.source,
|
|
146
|
+
hash: true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return rules;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Default redaction configuration
|
|
153
|
+
*/
|
|
154
|
+
const DEFAULT_CONFIG = {
|
|
155
|
+
enabled: true,
|
|
156
|
+
rules: buildDefaultRules(),
|
|
157
|
+
hashSalt: "rdt-redaction-salt-v1",
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Global redaction config (can be overridden)
|
|
161
|
+
*/
|
|
162
|
+
let globalConfig = { ...DEFAULT_CONFIG };
|
|
163
|
+
/**
|
|
164
|
+
* Compute a deterministic hash for a redacted value
|
|
165
|
+
* The hash is stable across runs given the same input and salt
|
|
166
|
+
*/
|
|
167
|
+
function computeRedactionHash(value, salt = globalConfig.hashSalt || "") {
|
|
168
|
+
const hash = (0, crypto_1.createHash)("sha256")
|
|
169
|
+
.update(salt + value)
|
|
170
|
+
.digest("hex")
|
|
171
|
+
.substring(0, 8);
|
|
172
|
+
return hash;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create a redacted placeholder
|
|
176
|
+
*/
|
|
177
|
+
function createRedactedValue(originalValue, includeHash = true, salt) {
|
|
178
|
+
if (!includeHash) {
|
|
179
|
+
return "[REDACTED]";
|
|
180
|
+
}
|
|
181
|
+
const hash = computeRedactionHash(originalValue, salt);
|
|
182
|
+
return `[REDACTED:${hash}]`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Load redaction config from file
|
|
186
|
+
*/
|
|
187
|
+
function loadRedactionConfig(configPath) {
|
|
188
|
+
// Try to find config file
|
|
189
|
+
const searchPaths = configPath
|
|
190
|
+
? [configPath]
|
|
191
|
+
: [
|
|
192
|
+
(0, path_1.join)(process.cwd(), ".rdt.json"),
|
|
193
|
+
(0, path_1.join)(process.cwd(), ".rdt/config.json"),
|
|
194
|
+
(0, path_1.join)(process.cwd(), "rdt.config.json"),
|
|
195
|
+
];
|
|
196
|
+
for (const path of searchPaths) {
|
|
197
|
+
if ((0, fs_1.existsSync)(path)) {
|
|
198
|
+
try {
|
|
199
|
+
const content = (0, fs_1.readFileSync)(path, "utf8");
|
|
200
|
+
const config = JSON.parse(content);
|
|
201
|
+
if (config.redaction) {
|
|
202
|
+
const userConfig = config.redaction;
|
|
203
|
+
// Merge with defaults
|
|
204
|
+
const mergedConfig = {
|
|
205
|
+
enabled: userConfig.enabled ?? true,
|
|
206
|
+
rules: userConfig.overrideDefaults
|
|
207
|
+
? userConfig.rules || []
|
|
208
|
+
: [...buildDefaultRules(), ...(userConfig.rules || [])],
|
|
209
|
+
overrideDefaults: userConfig.overrideDefaults,
|
|
210
|
+
hashSalt: userConfig.hashSalt || DEFAULT_CONFIG.hashSalt,
|
|
211
|
+
};
|
|
212
|
+
return mergedConfig;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.warn(`[Redaction] Failed to load config from ${path}: ${error}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { ...DEFAULT_CONFIG };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Set global redaction config
|
|
224
|
+
*/
|
|
225
|
+
function setRedactionConfig(config) {
|
|
226
|
+
globalConfig = {
|
|
227
|
+
...DEFAULT_CONFIG,
|
|
228
|
+
...config,
|
|
229
|
+
rules: config.overrideDefaults
|
|
230
|
+
? config.rules || []
|
|
231
|
+
: [...buildDefaultRules(), ...(config.rules || [])],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get current redaction config
|
|
236
|
+
*/
|
|
237
|
+
function getRedactionConfig() {
|
|
238
|
+
return { ...globalConfig };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Reset redaction config to defaults
|
|
242
|
+
*/
|
|
243
|
+
function resetRedactionConfig() {
|
|
244
|
+
globalConfig = { ...DEFAULT_CONFIG, rules: buildDefaultRules() };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Check if a header name should be redacted
|
|
248
|
+
*/
|
|
249
|
+
function shouldRedactHeader(headerName) {
|
|
250
|
+
if (!globalConfig.enabled)
|
|
251
|
+
return false;
|
|
252
|
+
const lowerName = headerName.toLowerCase();
|
|
253
|
+
for (const rule of globalConfig.rules) {
|
|
254
|
+
if (rule.type !== "header")
|
|
255
|
+
continue;
|
|
256
|
+
if (rule.regex && rule.name) {
|
|
257
|
+
const regex = new RegExp(rule.name, "i");
|
|
258
|
+
if (regex.test(lowerName))
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
else if (rule.name) {
|
|
262
|
+
if (rule.name.toLowerCase() === lowerName)
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if a query param should be redacted
|
|
270
|
+
*/
|
|
271
|
+
function shouldRedactQueryParam(paramName) {
|
|
272
|
+
if (!globalConfig.enabled)
|
|
273
|
+
return false;
|
|
274
|
+
const lowerName = paramName.toLowerCase();
|
|
275
|
+
for (const rule of globalConfig.rules) {
|
|
276
|
+
if (rule.type !== "query_param")
|
|
277
|
+
continue;
|
|
278
|
+
if (rule.regex && rule.name) {
|
|
279
|
+
const regex = new RegExp(rule.name, "i");
|
|
280
|
+
if (regex.test(lowerName))
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
else if (rule.name) {
|
|
284
|
+
if (rule.name.toLowerCase() === lowerName)
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Redact headers in a headers object
|
|
292
|
+
*/
|
|
293
|
+
function redactHeaders(headers) {
|
|
294
|
+
if (!globalConfig.enabled)
|
|
295
|
+
return headers;
|
|
296
|
+
const redacted = {};
|
|
297
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
298
|
+
if (value === undefined) {
|
|
299
|
+
redacted[key] = value;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (shouldRedactHeader(key)) {
|
|
303
|
+
const originalValue = Array.isArray(value) ? value.join(", ") : value;
|
|
304
|
+
redacted[key] = createRedactedValue(originalValue, true, globalConfig.hashSalt);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
redacted[key] = value;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return redacted;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Redact query parameters
|
|
314
|
+
*/
|
|
315
|
+
function redactQueryParams(query) {
|
|
316
|
+
if (!globalConfig.enabled)
|
|
317
|
+
return query;
|
|
318
|
+
const redacted = {};
|
|
319
|
+
for (const [key, value] of Object.entries(query)) {
|
|
320
|
+
if (shouldRedactQueryParam(key)) {
|
|
321
|
+
const originalValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
322
|
+
redacted[key] = createRedactedValue(originalValue, true, globalConfig.hashSalt);
|
|
323
|
+
}
|
|
324
|
+
else if (typeof value === "object" && value !== null) {
|
|
325
|
+
redacted[key] = redactQueryParams(value);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
redacted[key] = value;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return redacted;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Check if a body field name should be redacted
|
|
335
|
+
*/
|
|
336
|
+
function shouldRedactBodyField(fieldName) {
|
|
337
|
+
const lowerName = fieldName.toLowerCase();
|
|
338
|
+
for (const rule of globalConfig.rules) {
|
|
339
|
+
if (rule.type !== "body_field")
|
|
340
|
+
continue;
|
|
341
|
+
if (rule.regex && rule.name) {
|
|
342
|
+
const regex = new RegExp(rule.name, "i");
|
|
343
|
+
if (regex.test(lowerName))
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
else if (rule.name) {
|
|
347
|
+
if (rule.name.toLowerCase() === lowerName)
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Redact sensitive fields from a body object (recursive)
|
|
355
|
+
*/
|
|
356
|
+
function redactBodyObject(body) {
|
|
357
|
+
if (!globalConfig.enabled)
|
|
358
|
+
return body;
|
|
359
|
+
if (body === null || body === undefined) {
|
|
360
|
+
return body;
|
|
361
|
+
}
|
|
362
|
+
if (typeof body !== "object") {
|
|
363
|
+
return body;
|
|
364
|
+
}
|
|
365
|
+
if (Array.isArray(body)) {
|
|
366
|
+
return body.map((item) => redactBodyObject(item));
|
|
367
|
+
}
|
|
368
|
+
const redacted = {};
|
|
369
|
+
for (const [key, value] of Object.entries(body)) {
|
|
370
|
+
if (shouldRedactBodyField(key)) {
|
|
371
|
+
const originalValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
372
|
+
redacted[key] = createRedactedValue(originalValue, true, globalConfig.hashSalt);
|
|
373
|
+
}
|
|
374
|
+
else if (typeof value === "object" && value !== null) {
|
|
375
|
+
redacted[key] = redactBodyObject(value);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
redacted[key] = value;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return redacted;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Redact patterns from a body string
|
|
385
|
+
*/
|
|
386
|
+
function redactBodyPatterns(body) {
|
|
387
|
+
if (!globalConfig.enabled)
|
|
388
|
+
return body;
|
|
389
|
+
let result = body;
|
|
390
|
+
for (const rule of globalConfig.rules) {
|
|
391
|
+
if (rule.type !== "body_pattern" || !rule.pattern)
|
|
392
|
+
continue;
|
|
393
|
+
try {
|
|
394
|
+
const regex = new RegExp(rule.pattern, "g");
|
|
395
|
+
result = result.replace(regex, (match) => {
|
|
396
|
+
return createRedactedValue(match, rule.hash ?? true, globalConfig.hashSalt);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
// Invalid regex, skip
|
|
401
|
+
console.warn(`[Redaction] Invalid pattern: ${rule.pattern}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Redact a body (handles both string and object)
|
|
408
|
+
*/
|
|
409
|
+
function redactBody(body) {
|
|
410
|
+
if (!globalConfig.enabled)
|
|
411
|
+
return body;
|
|
412
|
+
if (body === null || body === undefined) {
|
|
413
|
+
return body;
|
|
414
|
+
}
|
|
415
|
+
if (typeof body === "string") {
|
|
416
|
+
// Try to parse as JSON
|
|
417
|
+
try {
|
|
418
|
+
const parsed = JSON.parse(body);
|
|
419
|
+
const redacted = redactBodyObject(parsed);
|
|
420
|
+
let result = JSON.stringify(redacted);
|
|
421
|
+
// Also apply pattern matching
|
|
422
|
+
result = redactBodyPatterns(result);
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Not JSON, apply pattern matching only
|
|
427
|
+
return redactBodyPatterns(body);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (typeof body === "object") {
|
|
431
|
+
const redacted = redactBodyObject(body);
|
|
432
|
+
// Also check for patterns in string values
|
|
433
|
+
return JSON.parse(redactBodyPatterns(JSON.stringify(redacted)));
|
|
434
|
+
}
|
|
435
|
+
return body;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Redact a URL (query params)
|
|
439
|
+
*/
|
|
440
|
+
function redactUrl(url) {
|
|
441
|
+
if (!globalConfig.enabled)
|
|
442
|
+
return url;
|
|
443
|
+
try {
|
|
444
|
+
const urlObj = new URL(url, "http://placeholder");
|
|
445
|
+
const redactedParams = new URLSearchParams();
|
|
446
|
+
for (const [key, value] of urlObj.searchParams) {
|
|
447
|
+
if (shouldRedactQueryParam(key)) {
|
|
448
|
+
redactedParams.set(key, createRedactedValue(value, true, globalConfig.hashSalt));
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
redactedParams.set(key, value);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
urlObj.search = redactedParams.toString();
|
|
455
|
+
// Return the full URL if it was absolute, or just path+query if relative
|
|
456
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
457
|
+
return urlObj.toString();
|
|
458
|
+
}
|
|
459
|
+
return urlObj.pathname + (urlObj.search ? urlObj.search : "");
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// Invalid URL, return as-is
|
|
463
|
+
return url;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Check if a value looks like it contains sensitive data
|
|
468
|
+
* (heuristic-based, for additional protection)
|
|
469
|
+
*/
|
|
470
|
+
function containsSensitivePatterns(value) {
|
|
471
|
+
// Check for common sensitive patterns
|
|
472
|
+
const sensitivePatterns = [
|
|
473
|
+
/Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/,
|
|
474
|
+
/eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/,
|
|
475
|
+
/password[=:]/i,
|
|
476
|
+
/secret[=:]/i,
|
|
477
|
+
/token[=:]/i,
|
|
478
|
+
/api[_-]?key[=:]/i,
|
|
479
|
+
];
|
|
480
|
+
return sensitivePatterns.some((pattern) => pattern.test(value));
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Export default sensitive lists for testing
|
|
484
|
+
*/
|
|
485
|
+
exports.SENSITIVE_HEADERS = DEFAULT_SENSITIVE_HEADERS;
|
|
486
|
+
exports.SENSITIVE_QUERY_PARAMS = DEFAULT_SENSITIVE_QUERY_PARAMS;
|
|
487
|
+
exports.SENSITIVE_BODY_FIELDS = DEFAULT_SENSITIVE_BODY_FIELDS;
|