From 4712e0619ec37fff61ec584f894fbb3ab61aa6df Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Fri, 14 Aug 2026 16:36:29 +0500 Subject: [PATCH] fix: redact json output value by value so a url cannot corrupt it --- lib/utils/display.js | 16 +++++++++++++++- test/lib/utils/display.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/utils/display.js b/lib/utils/display.js index 4030c8e32b931..591461365bcdf 100644 --- a/lib/utils/display.js +++ b/lib/utils/display.js @@ -101,7 +101,21 @@ const getArrayOrObject = (items) => { return Object.assign({}, ...items.filter(o => isPlainObject(o))) } -const redactValue = (obj) => JSON.parse(redactLog(JSON.stringify(obj))) +const redactStrings = (obj) => { + if (typeof obj === 'string') { + return redactLog(obj) + } + if (Array.isArray(obj)) { + return obj.map(redactStrings) + } + if (isPlainObject(obj)) { + return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, redactStrings(v)])) + } + return obj +} + +// Redact one string at a time. Redacting the serialized document instead lets a url in one value match across the punctuation around it and corrupt the json +const redactValue = (obj) => redactStrings(JSON.parse(JSON.stringify(obj))) const getJsonBuffer = ({ [JSON_ERROR_KEY]: metaError }, buffer) => { const items = [] diff --git a/test/lib/utils/display.js b/test/lib/utils/display.js index b33ab69a36594..20854e13ac1a7 100644 --- a/test/lib/utils/display.js +++ b/test/lib/utils/display.js @@ -300,6 +300,36 @@ t.test('json output redacts by default', async t => { 'inline redact: false preserves uuid values') }) +t.test('json output with a url inside a value stays valid json', async t => { + const { META } = require('proc-log') + const { output, outputs } = await mockDisplay(t) + + output.buffer({ + dependencies: { + '@esbuild-kit/esm-loader': { + deprecated: 'Merged into tsx: https://tsx.hirok.io', + dev: true, + _id: '@esbuild-kit/esm-loader@2.6.5', + }, + }, + registry: 'https://user:hunter2@registry.npmjs.org/', + versions: ['2.6.5'], + before: new Date('2024-01-01'), + }) + output.flush({ [META]: true, json: true }) + + t.equal(outputs.length, 1, 'one output') + const parsed = JSON.parse(outputs[0]) + const dep = parsed.dependencies['@esbuild-kit/esm-loader'] + t.equal(dep.deprecated, 'Merged into tsx: https://tsx.hirok.io', + 'a url in one value does not swallow the values after it') + t.equal(dep._id, '@esbuild-kit/esm-loader@2.6.5', 'the following values are intact') + t.strictSame(parsed.versions, ['2.6.5'], 'arrays are walked too') + t.equal(parsed.registry, 'https://user:***@registry.npmjs.org/', + 'url passwords are still redacted') + t.equal(parsed.before, '2024-01-01T00:00:00.000Z', 'toJSON values are still serialized') +}) + t.test('prompt functionality', async t => { t.test('regular prompt completion works', async t => { const { input } = await mockDisplay(t)