llm-security/tests/scanners/git-reflog-reset.test.mjs
Kjell Tore Guttormsen 66e8ce2740 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
2026-07-18 10:36:51 +02:00

83 lines
3.4 KiB
JavaScript

// git-reflog-reset.test.mjs — Regression for #20 (LOW, v7.8.3).
//
// The force-push detector filtered reflog lines with
// `l.includes('reset:') || l.includes('reset')` — the second term subsumes the
// first, so any commit whose SUBJECT merely contains the word "reset" tripped
// the detector (the reflog %gs for a commit is `commit: <subject>`). Only the
// reflog ACTION `reset:` (from `git reset`) is force-push evidence.
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { resetCounter } from '../../scanners/lib/output.mjs';
import { scan } from '../../scanners/git-forensics.mjs';
const gitAvailable = spawnSync('git', ['--version'], { encoding: 'utf-8' }).status === 0;
const RESET_TITLE = 'Force push signal: reflog contains reset entries';
/** Initialise a hermetic git repo (no global hooks/signing) at `dir`. */
function gitInit(dir) {
const run = (...args) =>
spawnSync('git', args, { cwd: dir, encoding: 'utf-8', env: { ...process.env, GIT_CONFIG_NOSYSTEM: '1' } });
run('init', '-q');
run('config', 'user.email', 'test@example.com');
run('config', 'user.name', 'Test');
run('config', 'commit.gpgsign', 'false');
run('config', 'core.hooksPath', '/dev/null');
return run;
}
describe('git-forensics: reflog reset detection (#20)', () => {
it('a commit subject containing the word "reset" does not trip the detector', { skip: !gitAvailable }, async () => {
const repo = mkdtempSync(join(tmpdir(), 'git-reflog-reset-'));
try {
const run = gitInit(repo);
writeFileSync(join(repo, 'README.md'), '# fixture\n');
run('add', '-A');
run('commit', '-q', '-m', 'reset onboarding flow');
resetCounter();
const result = await scan(repo, {});
assert.equal(result.status, 'ok', `expected ok, got '${result.status}'`);
const resetFindings = result.findings.filter(f => f.title === RESET_TITLE);
assert.equal(
resetFindings.length, 0,
`commit subject "reset onboarding flow" must not trip the force-push detector, ` +
`got: ${resetFindings.map(f => f.evidence).join('; ')}`,
);
} finally {
rmSync(repo, { recursive: true, force: true });
}
});
it('a real `reset:` reflog action still trips the detector', { skip: !gitAvailable }, async () => {
const repo = mkdtempSync(join(tmpdir(), 'git-reflog-reset-'));
try {
const run = gitInit(repo);
writeFileSync(join(repo, 'README.md'), '# fixture\n');
run('add', '-A');
run('commit', '-q', '-m', 'initial');
writeFileSync(join(repo, 'README.md'), '# fixture v2\n');
run('add', '-A');
run('commit', '-q', '-m', 'second');
// Rewrites history: reflog gains a `reset: moving to HEAD~1` action entry.
run('reset', '-q', '--hard', 'HEAD~1');
resetCounter();
const result = await scan(repo, {});
assert.equal(result.status, 'ok', `expected ok, got '${result.status}'`);
const resetFindings = result.findings.filter(f => f.title === RESET_TITLE);
assert.ok(
resetFindings.length >= 1,
`expected the reset: reflog action to trip the detector, ` +
`got findings: ${result.findings.map(f => f.title).join('; ')}`,
);
} finally {
rmSync(repo, { recursive: true, force: true });
}
});
});