import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { __stripMetaTags, fingerprintAfter } from '../src/conversation-pool.js'; // stripMetaTags keeps the reuse fingerprint stable by deleting the // client-injected meta tags whose bodies change every turn (issue #24). // These tests pin the exact semantics of the old // /[^>]*>[\s\S]*?/g replace — verified byte-identical to the // regex on a 40-case matrix + 20k-string fuzz — while asserting the new // linear scanner kills the quadratic blowup (CWE-1333): the regex took // ~7s on the 256KB unclosed-opens bomb measured on this machine. describe('stripMetaTags semantics (regex-compatible)', () => { it('removes a known tag span including attrs and body', () => { assert.equal(__stripMetaTags('hello state world'), 'hello world'); assert.equal(__stripMetaTags('body'), ''); }); it('strips every tag name in META_TAG_NAMES', () => { for (const t of ['system-reminder', 'command-message', 'command-name', 'command-args', 'local-command-stdout', 'local-command-stderr', 'user-prompt-submit-hook', 'analysis', 'summary', 'example']) { assert.equal(__stripMetaTags(`x<${t}>bodyy`), 'xy', `<${t}> should strip`); } }); it('first closer closes the first open; trailing closer stays literal', () => { // — regex removes 0..first , leftover is a stray closer. assert.equal(__stripMetaTags(''), ''); // One closer removes from the outermost open. assert.equal(__stripMetaTags(''), ''); }); it('inner different-named tag is removed inside the outer span', () => { assert.equal(__stripMetaTags(''), ''); }); it('mismatched open/closer pair stays literal', () => { assert.equal(__stripMetaTags(''), ''); }); it('unclosed opens and stray closers stay literal', () => { assert.equal(__stripMetaTags('x'), 'x'); assert.equal(__stripMetaTags('keep unclosed'), 'keep unclosed'); assert.equal(__stripMetaTags('stray closer'), 'stray closer'); }); it('mixed closed and unclosed opens', () => { assert.equal( __stripMetaTags('a1b2cgoned'), 'abcd' ); // closed summary removed, unclosed analysis kept assert.equal( __stripMetaTags('x tail open'), 'tail open' ); }); it('attrs are [^>]* — embedded quotes, =, and even < do not end the open', () => { assert.equal(__stripMetaTags('body'), ''); // A '<' inside attrs is legal ([^>]* matches it); the open ends at the // first '>' — which here belongs to , and the name is analysis. assert.equal(__stripMetaTags('"x'), ''); // '>' inside an attr VALUE ends the open early: is body — the whole span still strips (regex has no quote rules). assert.equal(__stripMetaTags('body'), ''); }); it('tag name is a prefix match — is an open of `example`', () => { // [^>]* absorbs 'foo', so the required closer is , not . assert.equal(__stripMetaTags('x'), ''); assert.equal(__stripMetaTags('x'), 'x'); }); it('is case-sensitive and requires the name right after <', () => { assert.equal(__stripMetaTags('x'), 'x'); assert.equal(__stripMetaTags('x'), 'x'); assert.equal(__stripMetaTags('< analysis>x'), '< analysis>x'); assert.equal(__stripMetaTags('<-analysis>x'), '<-analysis>x'); }); it('requires the exact closer — attrs on the closer do not match', () => { assert.equal(__stripMetaTags('x'), 'x'); assert.equal(__stripMetaTags('xy'), 'xy'); }); it('does not match unknown tag names and strips known tags inside them', () => { assert.equal(__stripMetaTags('x'), 'x'); assert.equal(__stripMetaTags('x'), ''); }); it('crossed spans: first closes the a-open, orphan stays', () => { assert.equal(__stripMetaTags('a bc'), 'c'); }); it('applies whitespace cleanup after stripping', () => { assert.equal(__stripMetaTags('line1 \nline2\n\n\n\nline3'), 'line1\nline2\n\nline3'); assert.equal(__stripMetaTags(' padded '), 'padded'); assert.equal(__stripMetaTags('x\n\n\n\ntail'), 'tail'); }); it('returns non-string and empty input as-is', () => { assert.equal(__stripMetaTags(''), ''); assert.equal(__stripMetaTags(null), null); assert.equal(__stripMetaTags(undefined), undefined); assert.equal(__stripMetaTags(42), 42); }); it('caps scanned input at 262144 chars', () => { assert.equal(__stripMetaTags('x'.repeat(300000)).length, 262144); }); }); describe('stripMetaTags unknown-tag audit', () => { function auditLog(input) { const orig = console.error; const lines = []; console.error = (...a) => lines.push(a.join(' ')); try { __stripMetaTags(input); } finally { console.error = orig; } return lines.join('\n'); } it('logs unknown open tag names once, deduped in first-seen order', () => { const lines = []; const orig = console.error; console.error = (...a) => lines.push(a.join(' ')); try { __stripMetaTags('a x again'); } finally { console.error = orig; } assert.deepEqual(lines, ['[META_TAG_AUDIT] Unknown XML tags in user message: evil, badtag']); }); it('logs unclosed unknown opens (semantic change: more coverage, not less)', () => { // The old paired … audit needed a closer; open-only scan // still surfaces the name when the closer never arrives. (An open // still needs its '>' — `never closed'), /evil/); }); it('does not log known names or stray closers', () => { assert.equal(auditLog('unclosed'), ''); assert.equal(auditLog('stray only'), ''); }); }); describe('stripMetaTags ReDoS regression (CWE-1333)', () => { // Old impl measured ~7000ms on this input; new impl ~4ms. 500ms is ~30x // headroom for CI noise and still 14x under the old impl's time. const BUDGET_MS = 500; it('256KB of unclosed opens strips linearly', () => { const bomb = ''.repeat(Math.ceil(262144 / 17)); const t0 = performance.now(); const out = __stripMetaTags(bomb); const elapsed = performance.now() - t0; // No closers exist → nothing strips; input is unchanged (modulo cap). assert.equal(out, bomb.slice(0, 262144)); assert.ok(elapsed < BUDGET_MS, `unclosed-opens bomb took ${elapsed.toFixed(1)}ms (old impl ~7000ms)`); }); it('256KB of at all strips linearly', () => { const bomb = ' bomb took ${elapsed.toFixed(1)}ms`); }); it('256KB residual { // After the strip leaves this untouched, the audit must scan it — the // old paired regex spent 42s here; the suggested /]*>/ regex // would still be O(n²) with no '>' present. The manual scan is linear. const bomb = ' {}; const t0 = performance.now(); try { __stripMetaTags(bomb); } finally { console.error = orig; } const elapsed = performance.now() - t0; assert.ok(elapsed < BUDGET_MS, `audit bomb took ${elapsed.toFixed(1)}ms (old audit ~42000ms)`); }); it('fingerprinting stays correct on a bomb-shaped message', () => { const bomb = ''.repeat(2000); const msgs = [ { role: 'user', content: `u ${bomb}` }, { role: 'assistant', content: 'ok' }, { role: 'user', content: 'next' }, ]; const t0 = performance.now(); const fp = fingerprintAfter(msgs, 'm', 'c'); const elapsed = performance.now() - t0; assert.ok(typeof fp === 'string' && fp.length === 64); assert.ok(elapsed < BUDGET_MS, `fingerprintAfter took ${elapsed.toFixed(1)}ms`); }); }); // L1 meta-review F1: after a removed span the scanner must resume at the // NEXT literal '<' — `i = last` treated whatever char followed the closer as // a potential open char, so 'Xname' at that position was parsed as ' { it('text immediately after a removed span is literal, not an open', () => { // 'Xsummary' is NOT 'aXsummary b>c'), 'Xsummary b>c', ); assert.equal( __stripMetaTags('tXcommand-args b>c'), 'Xcommand-args b>c', ); }); it('stray closers with no removed span still pass through', () => { assert.equal(__stripMetaTags('keepvscommand-args x>ytail'), 'keepvscommand-args x>ytail'); }); it('consecutive real spans still strip correctly after the fix', () => { assert.equal(__stripMetaTags('axbyc'), 'abc'); }); });