test(llm-security): add end-to-end JetBrains scan integration tests

This commit is contained in:
Kjell Tore Guttormsen 2026-04-18 10:51:48 +02:00
commit 2bc2f34fc4

View file

@ -3,7 +3,7 @@
// Uses fixture trees under tests/fixtures/ide-extensions/ to simulate
// real ~/.vscode/extensions/ layouts via rootsOverride injection.
import { describe, it, beforeEach } from 'node:test';
import { describe, it, before, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
@ -490,3 +490,135 @@ describe('scanOneExtension JetBrains dispatch', () => {
}
});
});
// ---------------------------------------------------------------------------
// JetBrains discovery + scan — full integration over root-jetbrains fixture
// ---------------------------------------------------------------------------
describe('JetBrains discovery + scan', () => {
const ROOT_JB = resolve(FIXTURES, 'root-jetbrains');
let env;
let allFindings;
const findingsById = new Map();
// Build jars from source/ trees once before the suite. The builder is
// idempotent + race-safe (atomic temp-then-rename, SHA-256 skip-if-match)
// so it is safe to run under node:test's parallel file execution.
before(async () => {
const { buildJetBrainsFixtures } = await import('../helpers/build-jetbrains-fixtures.mjs');
await buildJetBrainsFixtures({ fixtureRoot: ROOT_JB });
resetCounter();
env = await scan('all', { rootsOverride: [ROOT_JB], intellijOnly: true });
allFindings = env.extensions.flatMap(e => e.scanner_results.IDE?.findings || []);
for (const ext of env.extensions) {
findingsById.set(ext.id, ext.scanner_results.IDE?.findings || []);
}
});
it('discovers >= 8 JetBrains plugins (Fleet excluded)', () => {
assert.ok(
env.meta.extensions_discovered.jetbrains >= 8,
`expected >= 8 JB plugins, got ${env.meta.extensions_discovered.jetbrains}`,
);
});
it('includes every benign and adversarial IntelliJ fixture', () => {
for (const id of [
'com.example.benign',
'com.example.theme-with-code',
'com.example.broad-activation',
'com.example.premain',
'com.example.native-binary',
'com.example.depends-chain',
'com.intellij.jaba',
]) {
assert.ok(
env.extensions.some(e => e.id === id),
`expected extension ${id} in discovery, got: ${env.extensions.map(e => e.id).join(', ')}`,
);
}
});
it('includes the Android Studio fixture (path divergence test)', () => {
assert.ok(
env.extensions.some(e => e.id === 'com.google.example'),
`expected com.google.example under AndroidStudio base, got: ${env.extensions.map(e => e.id).join(', ')}`,
);
});
it('excludes Fleet plugins (different plugin model)', () => {
assert.ok(
env.extensions.every(e => !e.location.includes('Fleet')),
`Fleet plugin leaked in: ${env.extensions.filter(e => e.location.includes('Fleet')).map(e => e.id).join(', ')}`,
);
});
it('all JB extensions routed through JetBrains path (type=jetbrains)', () => {
for (const ext of env.extensions) {
assert.equal(ext.type, 'jetbrains', `${ext.id} had type=${ext.type}`);
}
});
it('theme-with-code fixture triggers checkThemeWithCodeJB', () => {
const findings = findingsById.get('com.example.theme-with-code') || [];
assert.ok(
findings.some(f => f.title.includes('checkThemeWithCodeJB')),
`expected theme-with-code finding; got: ${findings.map(f => f.title).join(' | ')}`,
);
});
it('broad-activation fixture triggers checkBroadActivationJB', () => {
const findings = findingsById.get('com.example.broad-activation') || [];
assert.ok(
findings.some(f => f.title.includes('checkBroadActivationJB')),
`expected broad-activation finding; got: ${findings.map(f => f.title).join(' | ')}`,
);
});
it('premain fixture triggers checkPremainClassJB (HIGH)', () => {
const findings = findingsById.get('com.example.premain') || [];
const premain = findings.filter(f => f.title.includes('checkPremainClassJB'));
assert.ok(premain.length >= 1, `expected premain finding; got: ${findings.map(f => f.title).join(' | ')}`);
assert.equal(premain[0].severity, 'high');
});
it('native-binary fixture triggers checkNativeBinariesJB', () => {
const findings = findingsById.get('com.example.native-binary') || [];
assert.ok(
findings.some(f => f.title.includes('checkNativeBinariesJB')),
`expected native-binary finding; got: ${findings.map(f => f.title).join(' | ')}`,
);
});
it('depends-chain fixture triggers checkDependsChainJB', () => {
const findings = findingsById.get('com.example.depends-chain') || [];
assert.ok(
findings.some(f => f.title.includes('checkDependsChainJB')),
`expected depends-chain finding; got: ${findings.map(f => f.title).join(' | ')}`,
);
});
it('typosquat fixture (com.intellij.jaba) triggers checkTyposquatJB', () => {
const findings = findingsById.get('com.intellij.jaba') || [];
assert.ok(
findings.some(f => f.title.includes('checkTyposquatJB')),
`expected typosquat finding; got: ${findings.map(f => f.title).join(' | ')}`,
);
});
it('benign fixture produces no HIGH/CRITICAL JB findings', () => {
const findings = findingsById.get('com.example.benign') || [];
const highs = findings.filter(f => f.severity === 'high' || f.severity === 'critical');
assert.equal(
highs.length, 0,
`expected benign plugin to be clean; got: ${highs.map(f => f.title).join(' | ')}`,
);
});
it('all JB findings carry DS-IDE- prefix', () => {
for (const f of allFindings) {
assert.ok(f.id.startsWith('DS-IDE-'), `expected DS-IDE- prefix, got ${f.id}`);
}
});
});