@redthreadlabs/tracelog 1.7.0 → 1.8.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.
|
@@ -10,6 +10,10 @@ const querystring = require('querystring');
|
|
|
10
10
|
const HEADER_FORM_URLENCODED = 'application/x-www-form-urlencoded';
|
|
11
11
|
const REDACTED = require('../constants').REDACTED;
|
|
12
12
|
|
|
13
|
+
// Depth guard for deep JSON redaction. Far deeper than any sane request
|
|
14
|
+
// body; protects against adversarial nesting.
|
|
15
|
+
const MAX_REDACT_DEPTH = 32;
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
18
|
* Handles req.body as object or string
|
|
15
19
|
*
|
|
@@ -42,6 +46,80 @@ function redactKeysFromPostedFormVariables(body, requestHeaders, regexes) {
|
|
|
42
46
|
return body;
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Redact sensitive fields from a captured request body of any supported
|
|
51
|
+
* content type:
|
|
52
|
+
*
|
|
53
|
+
* - `application/x-www-form-urlencoded` — top-level form fields (the
|
|
54
|
+
* historical Elastic APM behavior).
|
|
55
|
+
* - JSON content types (`application/json`, `application/*+json`, with or
|
|
56
|
+
* without a charset suffix) — **deep** redaction: any key at any depth
|
|
57
|
+
* matching the sanitizeFieldNames patterns is replaced, recursing through
|
|
58
|
+
* nested objects and arrays. Stringified JSON bodies are parsed, redacted,
|
|
59
|
+
* and re-stringified; strings that fail to parse are returned untouched.
|
|
60
|
+
* - anything else — returned as-is.
|
|
61
|
+
*
|
|
62
|
+
* @param {Object | String} body
|
|
63
|
+
* @param {Object} requestHeaders
|
|
64
|
+
* @param {Array<RegExp>} regexes
|
|
65
|
+
* @returns {Object | String} a copy of the body with redacted fields
|
|
66
|
+
*/
|
|
67
|
+
function redactKeysFromBody(body, requestHeaders, regexes) {
|
|
68
|
+
const contentType = String(requestHeaders['content-type'] || '');
|
|
69
|
+
if (!isJsonContentType(contentType)) {
|
|
70
|
+
return redactKeysFromPostedFormVariables(body, requestHeaders, regexes);
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(regexes)) {
|
|
73
|
+
return body;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (body !== null && !Buffer.isBuffer(body) && typeof body === 'object') {
|
|
77
|
+
return redactDeep(body, regexes, 0, new WeakSet());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof body === 'string') {
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(body);
|
|
84
|
+
} catch (_err) {
|
|
85
|
+
return body; // claimed JSON but isn't; leave it alone
|
|
86
|
+
}
|
|
87
|
+
return JSON.stringify(redactDeep(parsed, regexes, 0, new WeakSet()));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return body;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isJsonContentType(contentType) {
|
|
94
|
+
// 'application/json', 'application/json; charset=utf-8',
|
|
95
|
+
// 'application/vnd.api+json', …
|
|
96
|
+
const mime = contentType.split(';')[0].trim().toLowerCase();
|
|
97
|
+
return mime === 'application/json' || mime.endsWith('+json');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function redactDeep(value, regexes, depth, seen) {
|
|
101
|
+
if (value === null || typeof value !== 'object') {
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
if (depth >= MAX_REDACT_DEPTH || seen.has(value)) {
|
|
105
|
+
return REDACTED; // too deep / circular: redact rather than risk leaking
|
|
106
|
+
}
|
|
107
|
+
seen.add(value);
|
|
108
|
+
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
return value.map((item) => redactDeep(item, regexes, depth + 1, seen));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result = {};
|
|
114
|
+
for (const key of Object.keys(value)) {
|
|
115
|
+
const shouldRedact = regexes.some((regex) => regex.test(key));
|
|
116
|
+
result[key] = shouldRedact
|
|
117
|
+
? REDACTED
|
|
118
|
+
: redactDeep(value[key], regexes, depth + 1, seen);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
45
123
|
/**
|
|
46
124
|
* Returns a copy of the provided object. Each entry of the copy will have
|
|
47
125
|
* its value REDACTEd if the key matches any of the regexes
|
|
@@ -66,4 +144,5 @@ function redactKeysFromObject(obj, regexes, redactedStr = REDACTED) {
|
|
|
66
144
|
module.exports = {
|
|
67
145
|
redactKeysFromObject,
|
|
68
146
|
redactKeysFromPostedFormVariables,
|
|
147
|
+
redactKeysFromBody,
|
|
69
148
|
};
|
package/lib/parsers.js
CHANGED
|
@@ -17,7 +17,7 @@ const stringify = require('fast-safe-stringify');
|
|
|
17
17
|
const REDACTED = require('./constants').REDACTED;
|
|
18
18
|
const {
|
|
19
19
|
redactKeysFromObject,
|
|
20
|
-
|
|
20
|
+
redactKeysFromBody,
|
|
21
21
|
} = require('./filters/sanitize-field-names');
|
|
22
22
|
|
|
23
23
|
// When redacting individual cookie field values, this string is used instead
|
|
@@ -105,7 +105,7 @@ function getContextFromRequest(req, conf, type) {
|
|
|
105
105
|
if (typeof body === 'string' && req.bodyIsBase64Encoded === true) {
|
|
106
106
|
body = Buffer.from(body, 'base64').toString('utf8');
|
|
107
107
|
}
|
|
108
|
-
body =
|
|
108
|
+
body = redactKeysFromBody(
|
|
109
109
|
body,
|
|
110
110
|
req.headers,
|
|
111
111
|
conf.sanitizeFieldNamesRegExp,
|