#38 toxic-flow matched trifecta-leg keywords with bare includes(), so substrings ('url' in 'curl', 'key' in 'monkey', 'auth' in 'author') fabricated CRITICAL trifectas on benign components; now word-boundary matched. #40 TRG-broad fired HIGH on a bare any/all/every anywhere ('fix any lint errors'); the universal-claim regex now requires genuine universal phrasing. #41 TRG-baiting substring-matched ('any file' in 'many files'); now boundary-anchored. #39 the broad-name list missed multi-char generic names (helper/assistant/auto/general/agent/tool); widened coherently so it does not reintroduce #40. #57 the '(recovered from obfuscation)' label compared raw against a lowercased normal form, firing on any uppercase char; now gated on an explicit decode-changed flag. #26 (same file) getPolicyValue used 'key in sectionObj' with no type guard, so a scalar section override in policy.json (e.g. {"injection":"block"}) threw an uncaught TypeError; now guarded to fall back to the default. Suite 1931/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
85 lines
3.1 KiB
JavaScript
85 lines
3.1 KiB
JavaScript
// toxic-flow-keyword-boundary.test.mjs — Regression tests for TFA keyword
|
|
// matching (v7.8.3, finding #38): trifecta legs were classified via plain
|
|
// substring matching, so benign words containing a keyword as a substring
|
|
// ('curl' > 'url', 'monkey'/'keyword' > 'key', 'author' > 'auth',
|
|
// 'rapidly' > 'api') fabricated all three legs and a CRITICAL direct
|
|
// trifecta on read-only components. Keywords must match on word boundaries.
|
|
|
|
import { describe, it, beforeEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { join } from 'node:path';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { resetCounter } from '../../scanners/lib/output.mjs';
|
|
import { scan } from '../../scanners/toxic-flow-analyzer.mjs';
|
|
|
|
/** Build a temp plugin dir with the given commands/*.md files. */
|
|
function makePlugin(files) {
|
|
const dir = mkdtempSync(join(tmpdir(), 'tfa-boundary-'));
|
|
mkdirSync(join(dir, 'commands'), { recursive: true });
|
|
for (const [name, content] of Object.entries(files)) {
|
|
writeFileSync(join(dir, 'commands', name), content);
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
describe('toxic-flow-analyzer: keyword word boundaries (#38)', () => {
|
|
beforeEach(() => {
|
|
resetCounter();
|
|
});
|
|
|
|
it('does not fabricate trifecta legs from substring-only matches (curly/monkey/author/rapidly)', async () => {
|
|
const dir = makePlugin({
|
|
'release-notes.md': [
|
|
'---',
|
|
'name: release-notes',
|
|
'description: Formats release notes written by the author using curly braces and monkey-patched templates.',
|
|
'---',
|
|
'',
|
|
'# release-notes',
|
|
'',
|
|
'The author reviews monkey-patched curly-brace templates rapidly.',
|
|
'No tools, read-nothing formatting helper.',
|
|
'',
|
|
].join('\n'),
|
|
});
|
|
try {
|
|
const result = await scan(dir, { files: [] }, {});
|
|
assert.equal(result.status, 'ok');
|
|
assert.equal(
|
|
result.findings.length, 0,
|
|
`Expected 0 findings for a benign component, got: ${result.findings.map(f => `${f.title} [${f.severity}]`).join('; ')}`,
|
|
);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('still detects a direct trifecta from genuine keywords (incl. plural forms)', async () => {
|
|
const dir = makePlugin({
|
|
'sync.md': [
|
|
'---',
|
|
'name: sync',
|
|
'description: Takes user input, reads stored credentials, and uploads results to a webhook endpoint.',
|
|
'---',
|
|
'',
|
|
'# sync',
|
|
'',
|
|
'Reads credentials and sends them to the configured webhook.',
|
|
'',
|
|
].join('\n'),
|
|
});
|
|
try {
|
|
const result = await scan(dir, { files: [] }, {});
|
|
assert.equal(result.status, 'ok');
|
|
const direct = result.findings.filter(f => f.title.startsWith('Lethal trifecta:'));
|
|
assert.ok(
|
|
direct.length >= 1,
|
|
`Expected a direct trifecta finding, got: ${result.findings.map(f => f.title).join('; ')}`,
|
|
);
|
|
assert.equal(direct[0].severity, 'critical', 'unguarded direct trifecta should be CRITICAL');
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|