mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🔐 fix: Redact Metadata in debugTraverse for Warn and Error
Relaxing the debug-only gate in debugTraverse (in commit59371be0) routed warn/error records through the traversal path, which emits leaf string values verbatim (via truncateLongStrings only). Because DEBUG_LOGGING defaults to true, those records are also written to the rotating debug log file — which means payloads like `{ auth: 'Bearer ...' }` or `{ openaiKey: 'sk-...' }` were persisted unredacted once my earlier change took effect. Apply redactMessage to the final formatted string when the level is warn or error. Debug-level behavior is unchanged (matching prior art). Includes regression tests covering error/warn redaction and debug-level preservation. Reviewed-by: Codex (P1 finding on PR #12737, commite288f7fd).
This commit is contained in:
parent
e288f7fda7
commit
c09d293d47
2 changed files with 55 additions and 10 deletions
|
|
@ -1,4 +1,7 @@
|
||||||
const { formatConsoleMeta, redactMessage } = jest.requireActual('../parsers');
|
jest.unmock('winston');
|
||||||
|
|
||||||
|
const { formatConsoleMeta, redactMessage, debugTraverse } = jest.requireActual('../parsers');
|
||||||
|
const SPLAT_SYMBOL = Symbol.for('splat');
|
||||||
|
|
||||||
describe('formatConsoleMeta', () => {
|
describe('formatConsoleMeta', () => {
|
||||||
it('returns empty string when there is no user metadata', () => {
|
it('returns empty string when there is no user metadata', () => {
|
||||||
|
|
@ -134,3 +137,44 @@ describe('redactMessage', () => {
|
||||||
expect(redactMessage(undefined)).toBe('');
|
expect(redactMessage(undefined)).toBe('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('debugTraverse', () => {
|
||||||
|
const runFormatter = (info) => {
|
||||||
|
const transformed = debugTraverse.transform(info);
|
||||||
|
const MESSAGE = Symbol.for('message');
|
||||||
|
if (transformed && typeof transformed === 'object') {
|
||||||
|
return transformed[MESSAGE] ?? String(transformed);
|
||||||
|
}
|
||||||
|
return String(transformed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildInfo = (level, meta) => {
|
||||||
|
const info = {
|
||||||
|
level,
|
||||||
|
message: 'test',
|
||||||
|
timestamp: 'ts',
|
||||||
|
...meta,
|
||||||
|
};
|
||||||
|
info[SPLAT_SYMBOL] = [meta];
|
||||||
|
return info;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('redacts sensitive strings in metadata for error level', () => {
|
||||||
|
const out = runFormatter(buildInfo('error', { auth: 'Bearer eyJabc123', openai: 'sk-abc123' }));
|
||||||
|
expect(out).not.toContain('eyJabc123');
|
||||||
|
expect(out).not.toContain('sk-abc123');
|
||||||
|
expect(out).toContain('Bearer [REDACTED]');
|
||||||
|
expect(out).toContain('sk-[REDACTED]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redacts sensitive strings in metadata for warn level', () => {
|
||||||
|
const out = runFormatter(buildInfo('warn', { header: 'Bearer supersecrettoken' }));
|
||||||
|
expect(out).not.toContain('supersecrettoken');
|
||||||
|
expect(out).toContain('Bearer [REDACTED]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves debug-level metadata unmodified (existing behavior)', () => {
|
||||||
|
const out = runFormatter(buildInfo('debug', { someField: 'not-sensitive' }));
|
||||||
|
expect(out).toContain('not-sensitive');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -164,30 +164,31 @@ const debugTraverse = winston.format.printf(({ level, message, timestamp, ...met
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg = `${timestamp} ${level}: ${truncateLongStrings(message?.trim(), DEBUG_MESSAGE_LENGTH)}`;
|
let msg = `${timestamp} ${level}: ${truncateLongStrings(message?.trim(), DEBUG_MESSAGE_LENGTH)}`;
|
||||||
|
const levelStr = typeof level === 'string' ? level : String(level);
|
||||||
|
const isErrorOrWarn = levelStr.includes('error') || levelStr.includes('warn');
|
||||||
|
const finalize = (text) => (isErrorOrWarn ? redactMessage(text) : text);
|
||||||
try {
|
try {
|
||||||
const levelStr = typeof level === 'string' ? level : String(level);
|
|
||||||
const isErrorOrWarn = levelStr.includes('error') || levelStr.includes('warn');
|
|
||||||
if (level !== 'debug' && !isErrorOrWarn) {
|
if (level !== 'debug' && !isErrorOrWarn) {
|
||||||
return msg;
|
return finalize(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
return msg;
|
return finalize(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const debugValue = metadata[SPLAT_SYMBOL]?.[0] ?? extractMetaObject(metadata);
|
const debugValue = metadata[SPLAT_SYMBOL]?.[0] ?? extractMetaObject(metadata);
|
||||||
|
|
||||||
if (!debugValue) {
|
if (!debugValue) {
|
||||||
return msg;
|
return finalize(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debugValue && Array.isArray(debugValue)) {
|
if (debugValue && Array.isArray(debugValue)) {
|
||||||
msg += `\n${JSON.stringify(debugValue.map(condenseArray))}`;
|
msg += `\n${JSON.stringify(debugValue.map(condenseArray))}`;
|
||||||
return msg;
|
return finalize(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof debugValue !== 'object') {
|
if (typeof debugValue !== 'object') {
|
||||||
return (msg += ` ${debugValue}`);
|
return finalize((msg += ` ${debugValue}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
msg += '\n{';
|
msg += '\n{';
|
||||||
|
|
@ -228,9 +229,9 @@ const debugTraverse = winston.format.printf(({ level, message, timestamp, ...met
|
||||||
});
|
});
|
||||||
|
|
||||||
msg += '\n}';
|
msg += '\n}';
|
||||||
return msg;
|
return finalize(msg);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return (msg += `\n[LOGGER PARSING ERROR] ${e.message}`);
|
return finalize((msg += `\n[LOGGER PARSING ERROR] ${e.message}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue