Step 1/17 of ultraplan-2026-04-17-jetbrains-ide-scan. - Populate top-jetbrains-plugins.json with 56 canonical xmlIds (bundled + popular third-party): com.intellij.java, org.jetbrains.kotlin, com.jetbrains.python.community, org.rust.lang, com.github.copilot, mobi.hsz.idea.gitignore, the legitimate-typo 'Lombook Plugin', etc. - Add loadJetBrainsBlocklist() export mirroring loadVSCodeBlocklist shape. Blocklist is empty by design — no public confirmed-malicious JetBrains Marketplace plugins as of 2026-04-17. - Add tests/scanners/ide-extension-data.test.mjs (9 tests, all pass). - Fix cache bug in loadTopJetBrains: map normalizeId on cache-hit path too (was previously unnormalized on second call). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
81 lines
2.6 KiB
JavaScript
81 lines
2.6 KiB
JavaScript
// ide-extension-data.mjs — Loads top-extensions + blocklist from knowledge files.
|
|
// Zero dependencies (Node.js builtins only).
|
|
// Used by ide-extension-scanner.mjs for typosquat + blocklist checks.
|
|
|
|
import { readFile } from 'node:fs/promises';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const KNOWLEDGE_DIR = join(__dirname, '..', '..', 'knowledge');
|
|
|
|
let _vscode = null;
|
|
let _jetbrains = null;
|
|
|
|
async function loadJson(path) {
|
|
try {
|
|
const raw = await readFile(path, 'utf8');
|
|
return JSON.parse(raw);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load top VS Code extension IDs.
|
|
* @returns {Promise<string[]>} Lowercased "publisher.name" strings.
|
|
*/
|
|
export async function loadTopVSCode() {
|
|
if (_vscode !== null) return _vscode.vscode || [];
|
|
_vscode = await loadJson(join(KNOWLEDGE_DIR, 'top-vscode-extensions.json')) || { vscode: [], blocklist: [] };
|
|
return (_vscode.vscode || []).map(normalizeId);
|
|
}
|
|
|
|
/**
|
|
* Load VS Code extension blocklist entries.
|
|
* @returns {Promise<string[]>} Entries of form "publisher.name@version" or "publisher.name@*".
|
|
*/
|
|
export async function loadVSCodeBlocklist() {
|
|
if (_vscode !== null) return _vscode.blocklist || [];
|
|
_vscode = await loadJson(join(KNOWLEDGE_DIR, 'top-vscode-extensions.json')) || { vscode: [], blocklist: [] };
|
|
return _vscode.blocklist || [];
|
|
}
|
|
|
|
/**
|
|
* Load top JetBrains plugin xmlIds (canonical corpus for typosquat detection).
|
|
* @returns {Promise<string[]>} Lowercased xmlIds.
|
|
*/
|
|
export async function loadTopJetBrains() {
|
|
if (_jetbrains !== null) return (_jetbrains.jetbrains || []).map(normalizeId);
|
|
_jetbrains = await loadJson(join(KNOWLEDGE_DIR, 'top-jetbrains-plugins.json')) || { jetbrains: [], blocklist: [] };
|
|
return (_jetbrains.jetbrains || []).map(normalizeId);
|
|
}
|
|
|
|
/**
|
|
* Load JetBrains plugin blocklist entries.
|
|
* Empty by design — no public confirmed-malicious JetBrains Marketplace plugins
|
|
* as of 2026-04-17. Enterprise policy.json can seed private entries.
|
|
* @returns {Promise<string[]>} Entries of form "xmlId@version" or "xmlId@*".
|
|
*/
|
|
export async function loadJetBrainsBlocklist() {
|
|
if (_jetbrains !== null) return _jetbrains.blocklist || [];
|
|
_jetbrains = await loadJson(join(KNOWLEDGE_DIR, 'top-jetbrains-plugins.json')) || { jetbrains: [], blocklist: [] };
|
|
return _jetbrains.blocklist || [];
|
|
}
|
|
|
|
/**
|
|
* Normalize extension ID for comparison.
|
|
* @param {string} id
|
|
* @returns {string}
|
|
*/
|
|
export function normalizeId(id) {
|
|
return String(id || '').toLowerCase().trim();
|
|
}
|
|
|
|
/**
|
|
* Reset cache (for tests).
|
|
*/
|
|
export function _resetCache() {
|
|
_vscode = null;
|
|
_jetbrains = null;
|
|
}
|