fix(llm-security): misc scanner correctness — reflog FP, diff exact-pass, hex dedupe, SARIF version (#20,#22,#50,#54,#56)
#20 git-forensics' reflog force-push detector had a redundant bare 'reset' term subsuming 'reset:', so any commit subject containing 'reset' tripped it; removed. #22 diff-engine's per-current moved-fallback greedily consumed a baseline candidate a later byte-exact match needed (mislabeling unchanged as new/moved on duplicate fingerprints); a global exact pass now runs before the moved-fallback. #54 memory-poisoning double-reported a 64+ char hex token as both base64 and hex; the base64 check now skips pure-hex tokens. #56 SARIF output hardcoded driver.version 6.0.0 because the orchestrator called toSARIF() without a version; it now passes the real plugin version from package.json. #50 the VS Code known-malicious blocklist was empty with no explanation (unlike the JetBrains file's 'empty by design' note); added a matching blocklist_note. Suite 2004/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
This commit is contained in:
parent
8a59d616fb
commit
66e8ce2740
10 changed files with 340 additions and 9 deletions
72
tests/lib/diff-engine-exact-pass.test.mjs
Normal file
72
tests/lib/diff-engine-exact-pass.test.mjs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// diff-engine-exact-pass.test.mjs — Regression for #22 (LOW, v7.8.3).
|
||||
//
|
||||
// The moved-fallback ran per-current inside the main loop with no global
|
||||
// exact pass first, so an earlier unmatched current finding greedily consumed
|
||||
// (baseline.matched = true) a baseline candidate that a LATER current finding
|
||||
// needed for a byte-exact match. With duplicate fingerprints this mislabeled
|
||||
// unchanged findings as new/moved. The fix runs a global EXACT-match pass
|
||||
// first, then the moved-fallback on the remainder.
|
||||
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { diffFindings } from '../../scanners/lib/diff-engine.mjs';
|
||||
|
||||
/** Build a finding sharing the duplicate fingerprint at a given line. */
|
||||
function fpFinding(line) {
|
||||
return {
|
||||
fingerprint: 'dupfp0123456789a',
|
||||
scanner: 'ENT',
|
||||
severity: 'high',
|
||||
title: 'High-entropy string detected',
|
||||
file: 'src/config.mjs',
|
||||
line,
|
||||
evidence: 'AAAA... (fake)',
|
||||
owasp: 'LLM03',
|
||||
recommendation: 'Rotate the credential.',
|
||||
};
|
||||
}
|
||||
|
||||
describe('diff-engine: global exact pass before moved-fallback (#22)', () => {
|
||||
it('an earlier current finding does not steal the exact match of a later one', () => {
|
||||
// Baseline: ONE finding at line 100.
|
||||
// Current: TWO findings with the same fingerprint, at lines 50 and 100.
|
||||
// The line-100 current is byte-exact vs the baseline and must be unchanged;
|
||||
// the line-50 current has no remaining candidate and must be new.
|
||||
const baseline = [fpFinding(100)];
|
||||
const current = [fpFinding(50), fpFinding(100)];
|
||||
|
||||
const diff = diffFindings(baseline, current);
|
||||
|
||||
assert.equal(
|
||||
diff.unchanged.length, 1,
|
||||
`expected the exact line-100 match to be unchanged, got unchanged=${diff.unchanged.length} ` +
|
||||
`moved=${diff.moved.length} new=${diff.new.length}`,
|
||||
);
|
||||
assert.equal(diff.unchanged[0].line, 100, 'the unchanged finding should be the line-100 one');
|
||||
assert.equal(diff.moved.length, 0, 'nothing should be labeled moved');
|
||||
assert.equal(diff.new.length, 1, 'the line-50 finding should be new');
|
||||
assert.equal(diff.new[0].line, 50);
|
||||
assert.equal(diff.resolved.length, 0, 'the baseline finding was matched, nothing resolved');
|
||||
});
|
||||
|
||||
it('moved-fallback still applies when no exact match exists', () => {
|
||||
const baseline = [fpFinding(100)];
|
||||
const current = [fpFinding(500)]; // same fingerprint, drifted far
|
||||
const diff = diffFindings(baseline, current);
|
||||
assert.equal(diff.moved.length, 1, 'a lone drifted finding should be moved');
|
||||
assert.equal(diff.moved[0].previous_line, 100);
|
||||
assert.equal(diff.unchanged.length, 0);
|
||||
assert.equal(diff.new.length, 0);
|
||||
assert.equal(diff.resolved.length, 0);
|
||||
});
|
||||
|
||||
it('two exact duplicates both match two exact baseline duplicates', () => {
|
||||
const baseline = [fpFinding(50), fpFinding(100)];
|
||||
const current = [fpFinding(100), fpFinding(50)]; // order flipped
|
||||
const diff = diffFindings(baseline, current);
|
||||
assert.equal(diff.unchanged.length, 2, 'both exact duplicates should be unchanged');
|
||||
assert.equal(diff.moved.length, 0);
|
||||
assert.equal(diff.new.length, 0);
|
||||
assert.equal(diff.resolved.length, 0);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue