#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
83 lines
3.4 KiB
JavaScript
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 });
|
|
}
|
|
});
|
|
});
|