// ide-extension-null-manifest.test.mjs — Regression tests for unparseable // JetBrains plugins. // // parseIntelliJPlugin signals "could not parse" as { manifest: null, warnings } // — a TRUTHY object — while parseVSCodeExtension signals it as a bare null. // scanOneExtension only guarded the bare-null form, so a JetBrains plugin with // no lib/ directory, an unreadable lib/, no jars, or no extractable jar reached // `manifest.hasSignature` and threw a TypeError. mapConcurrent had no per-item // isolation, so that one plugin aborted the entire ide-scan. import { describe, it, after } from 'node:test'; import assert from 'node:assert/strict'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { __testing } from '../../scanners/ide-extension-scanner.mjs'; const { scanOneExtension } = __testing; const created = []; after(async () => { for (const dir of created) await rm(dir, { recursive: true, force: true }); }); async function makePluginRoot(name) { // Prefix must NOT start with `llmsec-jb-`: jetbrains-parser.test.mjs asserts // that no `llmsec-jb-*` directory survives anywhere in tmpdir, and that // assertion is global, so a shared prefix collides depending on test order. const root = await mkdtemp(join(tmpdir(), 'llmsec-nullmanifest-')); created.push(root); const dir = join(root, name); await mkdir(dir, { recursive: true }); return dir; } function jbExt(location, id) { return { id, version: '1.0.0', type: 'jetbrains', location, publisher: 'unknown', source: 'test', isBuiltin: false, signed: false, }; } describe('ide-extension-scanner — unparseable JetBrains plugin', () => { it('does not throw when lib/ is missing entirely', async () => { const dir = await makePluginRoot('NoLibPlugin'); const result = await scanOneExtension(jbExt(dir, 'NoLibPlugin'), { targetBase: dir }); assert.ok(result, 'expected a result object, not a throw'); assert.equal(result.id, 'NoLibPlugin'); assert.ok( result.warnings.some(w => /lib/i.test(w)), `expected a parse warning, got: ${JSON.stringify(result.warnings)}` ); assert.equal(result.aggregate.verdict, 'ALLOW'); }); it('does not throw when lib/ exists but holds no jars', async () => { const dir = await makePluginRoot('EmptyLibPlugin'); await mkdir(join(dir, 'lib'), { recursive: true }); await writeFile(join(dir, 'lib', 'notes.txt'), 'not a jar', 'utf8'); const result = await scanOneExtension(jbExt(dir, 'EmptyLibPlugin'), { targetBase: dir }); assert.ok(result, 'expected a result object, not a throw'); assert.ok(result.warnings.length >= 1); assert.equal(result.aggregate.verdict, 'ALLOW'); }); it('does not throw when lib/ is a file rather than a directory', async () => { const dir = await makePluginRoot('LibIsFilePlugin'); await writeFile(join(dir, 'lib'), 'this is not a directory', 'utf8'); const result = await scanOneExtension(jbExt(dir, 'LibIsFilePlugin'), { targetBase: dir }); assert.ok(result, 'expected a result object, not a throw'); assert.ok(result.warnings.length >= 1); }); it('does not throw when the only jar is corrupt (unextractable)', async () => { const dir = await makePluginRoot('CorruptJarPlugin'); await mkdir(join(dir, 'lib'), { recursive: true }); await writeFile(join(dir, 'lib', 'broken.jar'), 'PK truncated garbage', 'utf8'); const result = await scanOneExtension(jbExt(dir, 'CorruptJarPlugin'), { targetBase: dir }); assert.ok(result, 'expected a result object, not a throw'); assert.ok(result.warnings.length >= 1); }); it('reports a parse failure without inventing findings', async () => { const dir = await makePluginRoot('QuietPlugin'); const result = await scanOneExtension(jbExt(dir, 'QuietPlugin'), { targetBase: dir }); const counts = result.aggregate.counts; assert.equal(counts.critical + counts.high + counts.medium, 0); }); });