(value: string, ctx: RedactionContext)
| 311 | } |
| 312 | |
| 313 | function redactStringValue(value: string, ctx: RedactionContext): string { |
| 314 | let result = value |
| 315 | const { sensitiveKeysLower, combinedValueRegex } = ctx.parsed |
| 316 | |
| 317 | // Detect JSON strings and redact their contents by parsing → redacting → re-stringifying. |
| 318 | // Guarded by depth limit (nested JSON.stringify layers) and size limit (avoid parsing huge strings). |
| 319 | if (ctx.jsonStringDepth < MAX_JSON_STRING_DEPTH && result.length <= MAX_JSON_PARSE_LENGTH) { |
| 320 | const trimmed = result.trim() |
| 321 | if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) { |
| 322 | try { |
| 323 | const parsed = JSON.parse(result) |
| 324 | if (typeof parsed === "object" && parsed !== null) { |
| 325 | const innerCtx = makeContext(ctx.parsed, ctx.jsonStringDepth + 1) |
| 326 | return JSON.stringify(redactValue(parsed, innerCtx, "")) |
| 327 | } |
| 328 | } catch { |
| 329 | // Not valid JSON — fall through to other redaction strategies |
| 330 | } |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | // Redact URL query parameters whose names match sensitiveKeys |
| 335 | if (sensitiveKeysLower.size > 0) { |
| 336 | if (result.includes("?")) { |
| 337 | result = redactUrlQueryParams(result, sensitiveKeysLower) |
| 338 | } else if (looksLikeFormEncoded(result)) { |
| 339 | result = redactFormEncodedParams(result, sensitiveKeysLower) |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | if (combinedValueRegex) { |
| 344 | combinedValueRegex.lastIndex = 0 |
| 345 | result = result.replace(combinedValueRegex, REDACTED) |
| 346 | } |
| 347 | return result |
| 348 | } |
| 349 | |
| 350 | /** |
| 351 | * Detect a form-urlencoded body string (e.g. "user=alice&password=x"). The |
no test coverage detected