57bfe9ac6a
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
4.7 KiB
JavaScript
103 lines
4.7 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
SUSPICIOUS_MESSAGE_PATTERNS,
|
|
scanCommitMessagePatterns,
|
|
scanCommitMessage,
|
|
defaultLlmJudgeStub,
|
|
} from './commit-message-scanner.mjs';
|
|
|
|
describe('SUSPICIOUS_MESSAGE_PATTERNS', () => {
|
|
it('is a non-empty array of RegExp', () => {
|
|
expect(Array.isArray(SUSPICIOUS_MESSAGE_PATTERNS)).toBe(true);
|
|
expect(SUSPICIOUS_MESSAGE_PATTERNS.length).toBeGreaterThanOrEqual(5);
|
|
expect(SUSPICIOUS_MESSAGE_PATTERNS.every((r) => r instanceof RegExp)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('scanCommitMessagePatterns (sync regex pass)', () => {
|
|
it('allows a normal conventional-commit message', () => {
|
|
const r = scanCommitMessagePatterns('feat(router-gate): add static scanner (Stream C)');
|
|
expect(r.block).toBe(false);
|
|
});
|
|
it('allows a short-SHA range reference', () => {
|
|
expect(scanCommitMessagePatterns('ci: rebase ef19b9f2..46c43169').block).toBe(false);
|
|
});
|
|
it('blocks an external non-whitelist URL', () => {
|
|
const r = scanCommitMessagePatterns('docs: see http://evil.example.com/payload');
|
|
expect(r.block).toBe(true);
|
|
expect(r.reason).toBe('commit_message_suspicious_content');
|
|
});
|
|
it('allows a whitelisted anthropic / liderra URL', () => {
|
|
expect(scanCommitMessagePatterns('docs: per https://docs.anthropic.com/x').block).toBe(false);
|
|
expect(scanCommitMessagePatterns('docs: see https://liderra.ru/x').block).toBe(false);
|
|
});
|
|
it('blocks a long hex blob (potential exfil)', () => {
|
|
expect(scanCommitMessagePatterns('chore: ' + 'a'.repeat(48)).block).toBe(true);
|
|
});
|
|
it('blocks a base64-like blob', () => {
|
|
// 80 continuous base64-charset chars (incl. non-hex letters + digits, no '=')
|
|
// → exercises the base64 pattern specifically, not the hex pattern.
|
|
expect(scanCommitMessagePatterns('chore: ' + 'Zm9vYmFyYmF6cXV4'.repeat(5)).block).toBe(true);
|
|
});
|
|
it('blocks script tag / php tag / template injection', () => {
|
|
expect(scanCommitMessagePatterns('fix: <script>alert(1)</script>').block).toBe(true);
|
|
expect(scanCommitMessagePatterns('fix: <?php system($x); ?>').block).toBe(true);
|
|
expect(scanCommitMessagePatterns('fix: ${process.env.SECRET}').block).toBe(true);
|
|
});
|
|
it('blocks hex / unicode escape sequences', () => {
|
|
expect(scanCommitMessagePatterns('fix: \\x41\\x42').block).toBe(true);
|
|
expect(scanCommitMessagePatterns('fix: \\u0041').block).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('defaultLlmJudgeStub', () => {
|
|
it('returns a NO verdict marked as a stub', async () => {
|
|
const v = await defaultLlmJudgeStub({ prompt: 'x' });
|
|
expect(v.verdict).toBe('NO');
|
|
expect(v.stub).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('scanCommitMessage (async, with injected judge)', () => {
|
|
it('blocks on regex before ever calling the judge', async () => {
|
|
let judgeCalled = false;
|
|
const llmJudge = async () => { judgeCalled = true; return { verdict: 'NO' }; };
|
|
const r = await scanCommitMessage('docs: http://evil.example.com', { llmJudge });
|
|
expect(r.block).toBe(true);
|
|
expect(r.reason).toBe('commit_message_suspicious_content');
|
|
expect(judgeCalled).toBe(false);
|
|
});
|
|
it('blocks when the judge returns YES on a regex-clean message', async () => {
|
|
const llmJudge = async () => ({ verdict: 'YES' });
|
|
const r = await scanCommitMessage('feat: innocuous looking message', { llmJudge });
|
|
expect(r.block).toBe(true);
|
|
expect(r.reason).toBe('commit_message_llm_judge_positive');
|
|
});
|
|
it('allows when regex clean and judge returns NO', async () => {
|
|
const llmJudge = async () => ({ verdict: 'NO' });
|
|
const r = await scanCommitMessage('feat: add Stream C scanners', { llmJudge });
|
|
expect(r.block).toBe(false);
|
|
});
|
|
it('uses the default stub (allow on clean) when no judge injected', async () => {
|
|
const r = await scanCommitMessage('feat: add Stream C scanners');
|
|
expect(r.block).toBe(false);
|
|
});
|
|
it('accepts a plain-string judge return ("YES"/"NO")', async () => {
|
|
const r = await scanCommitMessage('feat: clean', { llmJudge: async () => 'YES' });
|
|
expect(r.block).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('scanCommitMessagePatterns — project_url_whitelist (D3/D4)', () => {
|
|
it('default (no opts) keeps liderra whitelisted', () => {
|
|
expect(scanCommitMessagePatterns('docs: https://liderra.ru/x').block).toBe(false);
|
|
});
|
|
it('empty whitelist → liderra blocked (fail-CLOSED), anthropic ok', () => {
|
|
expect(scanCommitMessagePatterns('docs: https://liderra.ru/x', { urlWhitelist: [] }).block).toBe(true);
|
|
expect(scanCommitMessagePatterns('docs: https://docs.anthropic.com/x', { urlWhitelist: [] }).block).toBe(false);
|
|
});
|
|
it('config whitelist admits own domain', () => {
|
|
expect(scanCommitMessagePatterns('docs: https://liderra.ru/x', { urlWhitelist: ['liderra.ru'] }).block).toBe(false);
|
|
});
|
|
});
|