Harden diagnostic response redaction
Redact every diagnostic response at the agent boundary, fail closed when sanitization cannot complete, and remove Windows, UNC, and slash-UNC paths from returned data. Preserve benign text while removing complete configured secret values, including nested JSON escapes and quoted HTML credential fields. Add focused regression coverage for collector errors, successful responses, support bundles, path variants, and punctuation secrets.
This commit is contained in:
@@ -117,6 +117,42 @@ test('redactLogText masks a one-character configured secret only as a complete s
|
||||
assert.ok(out.includes(`marker=${REDACTED}`));
|
||||
});
|
||||
|
||||
test('redactLogText replaces configured secrets only as complete values', () => {
|
||||
const out = redactLogText([
|
||||
'password=orange',
|
||||
'configured token orange accepted',
|
||||
'file=orangejuice',
|
||||
'file=orange.mkv',
|
||||
'password=.',
|
||||
'version=2.1.20',
|
||||
'sentence finished.'
|
||||
].join('\n'), ['orange', '.']);
|
||||
assert.ok(!out.includes('password=orange'));
|
||||
assert.ok(!out.includes('token orange'));
|
||||
assert.ok(!out.includes('password=.'));
|
||||
assert.ok(out.includes('file=orangejuice'));
|
||||
assert.ok(out.includes('file=orange.mkv'));
|
||||
assert.ok(out.includes('version=2.1.20'));
|
||||
assert.ok(out.includes('sentence finished.'));
|
||||
});
|
||||
|
||||
test('redactLogText removes JSON-escaped configured secrets and quoted HTML credential values', () => {
|
||||
const jsonSecret = 'alpha"beta\\gamma';
|
||||
const password = 'abc>secret';
|
||||
const token = 'token>quoted';
|
||||
const input = [
|
||||
JSON.stringify({ note: jsonSecret, token: jsonSecret }),
|
||||
`<input type="password" value="${password}">`,
|
||||
`<input value='${token}' name='api_token' type='text'>`
|
||||
].join('\n');
|
||||
const out = redactLogText(input, [jsonSecret]);
|
||||
assert.ok(!out.includes(jsonSecret));
|
||||
assert.ok(!out.includes('alpha\\"beta\\\\gamma'));
|
||||
assert.ok(!out.includes(password));
|
||||
assert.ok(!out.includes(token));
|
||||
assert.ok((out.match(/<redacted>/g) || []).length >= 3);
|
||||
});
|
||||
|
||||
test('redactLogText removes complete local paths from structured and free-form log text', () => {
|
||||
const profilePath = ['C:', 'Users', 'ProfileFixture', 'Private Folder', 'episode.mkv'].join('\\');
|
||||
const drivePath = ['D:', 'Archive', 'Private Folder', 'source.mkv'].join('\\');
|
||||
@@ -135,6 +171,23 @@ test('redactLogText removes complete local paths from structured and free-form l
|
||||
assert.ok((out.match(/<redacted-path>/g) || []).length >= 4);
|
||||
});
|
||||
|
||||
test('redactLogText removes extended UNC, extended drive, UNC and slash-UNC paths', () => {
|
||||
const extendedUnc = '\\\\?\\UNC\\private-server\\secret-share\\hidden.log';
|
||||
const extendedDrive = '\\\\?\\C:\\Users\\PrivateProfile\\hidden.log';
|
||||
const unc = '\\\\private-server\\secret-share\\hidden.log';
|
||||
const slashUnc = '//private-server/secret-share/hidden.log';
|
||||
const out = redactLogText([
|
||||
`extended UNC failure: ${extendedUnc}`,
|
||||
`extended drive failure: ${extendedDrive}`,
|
||||
`UNC failure: ${unc}`,
|
||||
`slash UNC failure: ${slashUnc}`
|
||||
].join('\n'), []);
|
||||
for (const fragment of ['private-server', 'secret-share', 'PrivateProfile', 'hidden.log']) {
|
||||
assert.ok(!out.includes(fragment), `private path fragment survived: ${fragment}`);
|
||||
}
|
||||
assert.equal((out.match(/<redacted-path>/g) || []).length, 4);
|
||||
});
|
||||
|
||||
test('sanitizeConfig does not mutate input', () => {
|
||||
const input = { hosters: { 'voe.sx': [{ password: 'secret' }] } };
|
||||
const clone = JSON.parse(JSON.stringify(input));
|
||||
@@ -285,3 +338,32 @@ test('buildSupportBundleText removes a one-character configured secret', () => {
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('buildSupportBundleText contains no escaped secrets, credential HTML or absolute path variants', () => {
|
||||
const tmp = path.join(os.tmpdir(), `mhu-bundle-hard-redaction-${Date.now()}.log`);
|
||||
const secret = 'alpha"beta\\gamma';
|
||||
const paths = [
|
||||
'\\\\?\\UNC\\private-server\\secret-share\\hidden.log',
|
||||
'\\\\private-server\\secret-share\\hidden.log',
|
||||
'//private-server/secret-share/hidden.log',
|
||||
'C:\\Users\\PrivateProfile\\hidden.log'
|
||||
];
|
||||
fs.writeFileSync(tmp, [
|
||||
JSON.stringify({ token: secret, path: paths[0] }),
|
||||
'<input type="password" value="abc>secret">',
|
||||
...paths
|
||||
].join('\n'));
|
||||
try {
|
||||
const text = buildSupportBundleText({
|
||||
header: { Source: paths[3] },
|
||||
sanitizedConfig: { marker: JSON.stringify(secret), path: paths[1] },
|
||||
secrets: [secret],
|
||||
files: [{ label: paths[2], path: tmp }]
|
||||
});
|
||||
for (const value of ['alpha', 'beta', 'gamma', 'abc>secret', 'private-server', 'secret-share', 'PrivateProfile', 'hidden.log', tmp]) {
|
||||
assert.ok(!text.includes(value), `support bundle leak survived: ${value}`);
|
||||
}
|
||||
} finally {
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user