UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

169 lines (136 loc) 5.39 kB
'use strict' const crypto = require('crypto') const { defaults } = require('../../../config/defaults') const STRINGIFY_RANGE_KEY = 'DD_' + crypto.randomBytes(20).toString('hex') const STRINGIFY_SENSITIVE_KEY = STRINGIFY_RANGE_KEY + 'SENSITIVE' const STRINGIFY_SENSITIVE_NOT_STRING_KEY = STRINGIFY_SENSITIVE_KEY + 'NOTSTRING' // eslint-disable-next-line @stylistic/max-len const KEYS_REGEX_WITH_SENSITIVE_RANGES = new RegExp(String.raw`(?:"(${STRINGIFY_RANGE_KEY}_\d+_))|(?:"(${STRINGIFY_SENSITIVE_KEY}_\d+_(\d+)_))|("${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_\d+_([\s0-9.a-zA-Z]*)")`, 'gm') const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(String.raw`"(${STRINGIFY_RANGE_KEY}_\d+_)`, 'gm') const sensitiveValueRegex = new RegExp(/** @type {string} */ (defaults['iast.redactionValuePattern']), 'gmi') function iterateObject (target, fn, levelKeys = [], depth = 10, visited = new Set()) { for (const key of Object.keys(target)) { const nextLevelKeys = [...levelKeys, key] const val = target[key] if (typeof val !== 'object' || !visited.has(val)) { visited.add(val) fn(val, nextLevelKeys, target, key) if (val !== null && typeof val === 'object' && depth > 0) { iterateObject(val, fn, nextLevelKeys, depth - 1, visited) } } } } function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) { let value const ranges = [] const sensitiveRanges = [] objRanges = objRanges || {} if (objRanges || loadSensitiveRanges) { const cloneObj = Array.isArray(obj) ? [] : {} let counter = 0 const allRanges = {} const sensitiveKeysMapping = {} iterateObject(obj, (val, levelKeys, parent, key) => { let currentLevelClone = cloneObj for (let i = 0; i < levelKeys.length - 1; i++) { let levelKey = levelKeys[i] if (!currentLevelClone[levelKey]) { const sensitiveKey = sensitiveKeysMapping[levelKey] if (currentLevelClone[sensitiveKey]) { levelKey = sensitiveKey } } currentLevelClone = currentLevelClone[levelKey] } if (loadSensitiveRanges) { const sensitiveKey = sensitiveKeysMapping[key] if (sensitiveKey) { key = sensitiveKey } else { sensitiveValueRegex.lastIndex = 0 if (sensitiveValueRegex.test(key)) { const current = counter++ const id = `${STRINGIFY_SENSITIVE_KEY}_${current}_${key.length}_` key = `${id}${key}` } } } if (typeof val === 'string') { const ranges = objRanges[levelKeys.join('.')] if (ranges) { const current = counter++ const id = `${STRINGIFY_RANGE_KEY}_${current}_` allRanges[id] = ranges currentLevelClone[key] = `${id}${val}` } else { currentLevelClone[key] = val } if (loadSensitiveRanges) { const current = counter++ const id = `${STRINGIFY_SENSITIVE_KEY}_${current}_${val.length}_` currentLevelClone[key] = `${id}${currentLevelClone[key]}` } } else if (typeof val !== 'object' || val === null) { if (loadSensitiveRanges) { const current = counter++ const id = `${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_${current}_` // this is special, in the final string we should modify "key_value_[null|false|true]..." // by null|false|..... ignoring the beginning and ending quotes currentLevelClone[key] = id + val } else { currentLevelClone[key] = val } } else { currentLevelClone[key] = Array.isArray(val) ? [] : {} } }) value = JSON.stringify(cloneObj, null, 2) if (counter > 0) { const keysRegex = loadSensitiveRanges ? KEYS_REGEX_WITH_SENSITIVE_RANGES : KEYS_REGEX_WITHOUT_SENSITIVE_RANGES keysRegex.lastIndex = 0 let regexRes = keysRegex.exec(value) while (regexRes) { const offset = regexRes.index + 1 // +1 to increase the " char if (regexRes[1]) { // is a range const rangesId = regexRes[1] value = value.replace(rangesId, '') const updatedRanges = allRanges[rangesId].map(range => { return { ...range, start: range.start + offset, end: range.end + offset, } }) ranges.push(...updatedRanges) } else if (regexRes[2]) { // is a sensitive string literal const sensitiveId = regexRes[2] sensitiveRanges.push({ start: offset, end: offset + Number.parseInt(regexRes[3]), }) value = value.replace(sensitiveId, '') } else if (regexRes[4]) { // is a sensitive value (number, null, false, ...) const sensitiveId = regexRes[4] const originalValue = regexRes[5] sensitiveRanges.push({ start: regexRes.index, end: regexRes.index + originalValue.length, }) value = value.replace(sensitiveId, originalValue) } keysRegex.lastIndex = 0 regexRes = keysRegex.exec(value) } } } else { value = JSON.stringify(obj, null, 2) } return { value, ranges, sensitiveRanges } } module.exports = { stringifyWithRanges }