diff --git a/src/omniroute-combos.ts b/src/omniroute-combos.ts index 95e5bab..eb3f43a 100644 --- a/src/omniroute-combos.ts +++ b/src/omniroute-combos.ts @@ -10,8 +10,9 @@ import { REQUEST_TIMEOUT } from './constants.js'; import { warn, debug } from './logger.js'; export function sanitizeForLog(value: string): string { - // Remove all control characters except tab (0x09) - return value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); + // Remove all ASCII control characters except tab (0x09), + // plus Unicode line/paragraph separators (U+2028 / U+2029). + return value.replace(/[\x00-\x08\x0A-\x1F\x7F\u2028\u2029]/g, ''); } /** diff --git a/test/omniroute-combos.test.mjs b/test/omniroute-combos.test.mjs new file mode 100644 index 0000000..b3e3cc9 --- /dev/null +++ b/test/omniroute-combos.test.mjs @@ -0,0 +1,25 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { sanitizeForLog } from '../dist/src/omniroute-combos.js'; + +test('sanitizeForLog removes every ASCII control character except tab', () => { + for (let code = 0; code <= 0x1f; code += 1) { + const value = String.fromCharCode(code); + assert.equal( + sanitizeForLog(`before${value}after`), + code === 0x09 ? `before\tafter` : 'beforeafter', + `unexpected handling for control byte 0x${code.toString(16).padStart(2, '0')}`, + ); + } + + assert.equal(sanitizeForLog(`before${String.fromCharCode(0x7f)}after`), 'beforeafter'); +}); + +test('sanitizeForLog prevents CR/LF log-line injection and preserves printable Unicode', () => { + assert.equal( + sanitizeForLog('combo\n[ERROR] forged\rrewritten\u2028forged2\u2029rewritten2'), + 'combo[ERROR] forgedrewrittenforged2rewritten2', + ); + assert.equal(sanitizeForLog('Modell ✓ — 日本語'), 'Modell ✓ — 日本語'); +});