mirror of
https://github.com/0xMarcio/cve.git
synced 2026-10-27 16:51:03 +00:00
347 lines
18 KiB
JavaScript
347 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
const vm = require('node:vm');
|
|
const { MessageChannel } = require('node:worker_threads');
|
|
|
|
const script = fs.readFileSync(path.join(__dirname, '../docs/logic.js'), 'utf8');
|
|
|
|
function startPage(t, { search = '', trending, kev = {}, repositories = {}, indexError = false, cvssError = false, entries }) {
|
|
const elements = new Map();
|
|
const calls = [];
|
|
const document = {
|
|
addEventListener() {},
|
|
querySelectorAll: () => [],
|
|
querySelector(selector) {
|
|
if (!elements.has(selector)) elements.set(selector, {
|
|
value: '',
|
|
innerHTML: selector === '[data-trend-rows]' ? '<a href="/CVE-2026-1234">Prerendered PoC</a>' : '',
|
|
addEventListener() {}, setAttribute() {}, querySelectorAll: () => []
|
|
});
|
|
return elements.get(selector);
|
|
}
|
|
};
|
|
const context = vm.createContext({
|
|
console: { warn() {} }, document, performance, MessageChannel, URLSearchParams, URL,
|
|
location: { pathname: '/', search },
|
|
window: { matchMedia: () => ({ matches: false }) },
|
|
setTimeout() {}, clearTimeout() {},
|
|
async fetch(url) {
|
|
calls.push(url);
|
|
if (url === '/CVE_list.json' && indexError) throw new Error('Index unavailable');
|
|
if (url === '/cvss.json' && cvssError) throw new Error('CVSS unavailable');
|
|
const data = url === '/trending_poc.json' ? await trending
|
|
: url === '/kev.json' ? await kev
|
|
: url === '/repo_meta.json' ? await repositories
|
|
: url === '/CVE_list.json' ? entries || [{ cve: 'CVE-2026-1234', desc: 'widget', poc: ['https://github.com/owner/poc'] }]
|
|
: {};
|
|
return { ok: true, json: async () => data };
|
|
}
|
|
});
|
|
vm.runInContext(script, context);
|
|
t.after(() => vm.runInContext('yieldPort.port1.close(); yieldPort.port2.close();', context));
|
|
return { elements, calls, context };
|
|
}
|
|
|
|
test('pending CVEs are searchable by identifier and PoC name without NVD metadata', async t => {
|
|
const entries = ['CVE-2026-109618', 'CVE-2026-109619'].map(cve => ({
|
|
cve, desc: 'Official CVE details are pending.', metadata_pending: true,
|
|
poc: ['https://github.com/example/php2shell-poc']
|
|
}));
|
|
for (const query of ['CVE-2026-109618', 'CVE-2026-109619', 'php2shell']) {
|
|
const { elements } = startPage(t, { search: `?q=${query}`, entries, trending: new Promise(() => {}) });
|
|
await new Promise(setImmediate);
|
|
const html = elements.get('[data-results]').innerHTML;
|
|
assert.match(html, /CVE metadata pending/);
|
|
assert.match(html, /https:\/\/github.com\/example\/php2shell-poc/);
|
|
assert.match(html, new RegExp(query === 'php2shell' ? 'CVE-2026-109618' : query));
|
|
}
|
|
});
|
|
|
|
test('repository security advisories have GHSA filtering without repository stars or dates', async t => {
|
|
const { context } = startPage(t, { trending: new Promise(() => {}) });
|
|
await new Promise(setImmediate);
|
|
const url = 'https://github.com/vendor/project/security/advisories/GHSA-1234';
|
|
vm.runInContext('repoMeta = { "vendor/project": [5000, "2026-10-06"] };', context);
|
|
const row = vm.runInContext(`pocRow(${JSON.stringify(url)})`, context);
|
|
assert.match(row, /GHSA-1234/);
|
|
assert.doesNotMatch(row, /★|5\.0k/);
|
|
assert.equal(vm.runInContext(`entrySources({poc: [${JSON.stringify(url)}]}).has('GHSA')`, context), true);
|
|
assert.equal(vm.runInContext(`repoFromUrl(${JSON.stringify(url)})`, context), null);
|
|
});
|
|
|
|
test('repository and artifact rows distinguish zero, missing metadata and non-repository links', async t => {
|
|
const { context } = startPage(t, { trending: new Promise(() => {}) });
|
|
await new Promise(setImmediate);
|
|
vm.runInContext('repoMeta = { "owner/poc": [0, "2026-10-21"] };', context);
|
|
for (const suffix of ['', '/blob/main/reproduce.py']) {
|
|
const row = vm.runInContext(`pocRow('https://github.com/Owner/PoC${suffix}', true)`, context);
|
|
assert.match(row, /0 <span class="star">★<\/span>/);
|
|
assert.match(row, /Repository stars: owner\/poc/);
|
|
assert.match(row, /Repository last push: 2026-10-21 UTC/);
|
|
}
|
|
const missing = vm.runInContext("pocRow('https://github.com/owner/missing')", context);
|
|
assert.match(missing, /Repository stars: owner\/missing \(unavailable\)/);
|
|
assert.match(missing, />N\/A<\/span>/);
|
|
assert.doesNotMatch(missing, /★/);
|
|
const external = vm.runInContext("trendRow({url:'https://www.exploit-db.com/exploits/12345', stars:5000, artifact:true}, true)", context);
|
|
assert.match(external, /No GitHub repository/);
|
|
assert.doesNotMatch(external, /★|5\.0k/);
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
vm.runInContext(`repoMeta['owner/poc'][1] = '${today}'`, context);
|
|
assert.match(vm.runInContext("repositoryCells('https://github.com/owner/poc')", context), />today<\/span>/);
|
|
});
|
|
|
|
test('late repository metadata reorders a long PoC list by stars regardless of CVE spelling', async t => {
|
|
let finishMetadata;
|
|
const repositories = new Promise(resolve => { finishMetadata = resolve; });
|
|
const named = 'https://github.com/owner/CVE-2021-1675';
|
|
const alias = 'https://github.com/nemo-wq/PrintNightmare-CVE-2021-34527';
|
|
const unknown = 'https://github.com/owner/unknown';
|
|
const entries = [{ cve: 'CVE-2021-1675', desc: 'PrintNightmare',
|
|
poc: [named, unknown, ...Array.from({length: 25}, (_, i) => `https://github.com/owner/CVE-2021-1675-${i}`), alias] }];
|
|
const { context, elements } = startPage(t, { search: '?q=CVE-2021-1675', entries, repositories,
|
|
trending: new Promise(() => {}) });
|
|
await new Promise(setImmediate);
|
|
vm.runInContext("state.pocOpen.add('CVE-2021-1675'); renderResults(null)", context);
|
|
finishMetadata({ 'owner/cve-2021-1675': [0, '2026-10-21'], 'nemo-wq/printnightmare-cve-2021-34527': [177, '2021-06-29'] });
|
|
await new Promise(setImmediate);
|
|
const links = Array.from(vm.runInContext('rankedLinks(dataset[0])', context));
|
|
assert.equal(links.length, 28);
|
|
assert.deepEqual(links.slice(0, 2), [alias, named]);
|
|
assert.ok(links.indexOf(unknown) > 1);
|
|
const html = elements.get('[data-results]').innerHTML;
|
|
assert.ok(html.indexOf(`href="${alias}"`) < html.indexOf(`href="${named}"`));
|
|
assert.match(html, /177 <span class="star">★/);
|
|
assert.equal(vm.runInContext("state.pocOpen.has('CVE-2021-1675')", context), true);
|
|
});
|
|
|
|
test('an open tab refreshes one complete data snapshot without losing its search or expansion', async t => {
|
|
let finishOldMetadata;
|
|
const repositories = new Promise(resolve => { finishOldMetadata = resolve; });
|
|
const original = { generated: '2026-10-21T10:00:00Z', with_pocs: 1, total_cves: 1, items: [], landed: [] };
|
|
const { context, elements } = startPage(t, { search: '?q=widget&src=github&sort=newest', repositories, trending: original });
|
|
await new Promise(setImmediate);
|
|
vm.runInContext("state.pocOpen.add('CVE-2026-1234')", context);
|
|
const feed = { ...original, generated: '2026-10-21T12:00:00Z' };
|
|
const entries = [{ cve: 'CVE-2026-1234', desc: 'widget', poc: ['https://github.com/owner/current'] }];
|
|
const data = { '/trending_poc.json': feed, '/CVE_list.json': entries,
|
|
'/repo_meta.json': { 'owner/current': [42, '2026-10-21'] }, '/cvss.json': {},
|
|
'/advisories.json': {}, '/nuclei.json': {}, '/kev.json': {}, '/epss.json': {} };
|
|
context.fetch = async url => ({ ok: true, json: async () => data[url.split('?')[0]] });
|
|
assert.equal(await vm.runInContext('refreshData(true)', context), true);
|
|
finishOldMetadata({ 'owner/current': [1, '2020-01-01'] });
|
|
await new Promise(setImmediate);
|
|
assert.equal(vm.runInContext('state.query', context), 'widget');
|
|
assert.equal(vm.runInContext('state.sort', context), 'NEWEST');
|
|
assert.equal(vm.runInContext("[...state.filters.source].join(',')", context), 'GITHUB');
|
|
assert.equal(vm.runInContext("state.pocOpen.has('CVE-2026-1234')", context), true);
|
|
assert.equal(vm.runInContext("repoMeta['owner/current'][0]", context), 42);
|
|
assert.match(elements.get('[data-results]').innerHTML, /42 <span class="star">★/);
|
|
assert.doesNotMatch(elements.get('[data-results]').innerHTML, /href="https:\/\/github.com\/owner\/poc"/);
|
|
assert.equal(await vm.runInContext('refreshData(true)', context), false);
|
|
});
|
|
|
|
test('failed or mixed-generation refreshes keep the previous searchable data', async t => {
|
|
const original = { generated: '2026-10-21T10:00:00Z', with_pocs: 1, total_cves: 1, items: [], landed: [] };
|
|
const { context } = startPage(t, { search: '?q=widget', trending: original });
|
|
await new Promise(setImmediate);
|
|
for (const failure of ['download', 'changed']) {
|
|
let feeds = 0;
|
|
context.fetch = async url => {
|
|
const file = url.split('?')[0];
|
|
if (failure === 'download' && file === '/repo_meta.json') throw new Error('Unavailable');
|
|
const payload = file === '/trending_poc.json'
|
|
? { ...original, generated: ++feeds > 1 && failure === 'changed' ? 'newer' : 'new' }
|
|
: file === '/CVE_list.json' ? [{ cve: 'CVE-2026-1234', desc: 'changed', poc: [] }] : {};
|
|
return { ok: true, json: async () => payload };
|
|
};
|
|
assert.equal(await vm.runInContext('refreshData(true)', context), false);
|
|
assert.equal(vm.runInContext('dataset[0].desc', context), 'widget');
|
|
assert.equal(vm.runInContext('indexMeta.generated', context), original.generated);
|
|
}
|
|
});
|
|
|
|
test('failed enrichment cannot silently apply bookmarked filters against empty data', async t => {
|
|
const { context, elements } = startPage(t, {
|
|
search: '?q=widget&kev=1&sev=critical', cvssError: true,
|
|
kev: Promise.reject(new Error('KEV unavailable')), trending: new Promise(() => {})
|
|
});
|
|
await new Promise(setImmediate);
|
|
assert.equal(vm.runInContext('state.kevOnly', context), false);
|
|
assert.equal(vm.runInContext('state.filters.severity.size', context), 4);
|
|
assert.match(elements.get('[data-results]').innerHTML, /href="\/CVE-2026-1234"/);
|
|
assert.match(elements.get('[data-status]').textContent, /KEV filter unavailable/);
|
|
assert.match(elements.get('[data-status]').textContent, /severity filter unavailable/);
|
|
});
|
|
|
|
test('missing repository metadata is visible and retried even without a newer publication', async t => {
|
|
const feed = { generated: '2026-10-21T10:00:00Z', with_pocs: 1, total_cves: 1, items: [], landed: [] };
|
|
const entries = [{ cve: 'CVE-2026-1234', desc: 'widget', poc: ['https://github.com/owner/poc'] }];
|
|
const { context, elements } = startPage(t, { search: '?q=widget', entries, trending: feed,
|
|
repositories: Promise.reject(new Error('Metadata temporarily unavailable')) });
|
|
await new Promise(setImmediate);
|
|
assert.match(elements.get('[data-status]').textContent, /repository metadata unavailable/);
|
|
context.fetch = async url => ({ ok: true, json: async () => {
|
|
const file = url.split('?')[0];
|
|
return file === '/trending_poc.json' ? feed : file === '/CVE_list.json' ? entries
|
|
: file === '/repo_meta.json' ? { 'owner/poc': [0, '2026-10-21'] } : {};
|
|
}});
|
|
assert.equal(await vm.runInContext('refreshData(true)', context), true);
|
|
assert.match(elements.get('[data-results]').innerHTML, /0 <span class="star">★/);
|
|
assert.doesNotMatch(elements.get('[data-status]').textContent, /repository metadata unavailable/);
|
|
});
|
|
|
|
test('curated artifact paths survive search rendering without duplicate repository roots', async t => {
|
|
const { context } = startPage(t, { trending: new Promise(() => {}) });
|
|
await new Promise(setImmediate);
|
|
const base = 'https://github.com/google/security-research';
|
|
const variants = ['lts', 'mitigation'].map(kind => `${base}/tree/master/pocs/linux/kernelctf/CVE-2024-26642_${kind}`);
|
|
const template = 'https://github.com/projectdiscovery/nuclei-templates/blob/main/http/Test.yaml?version=One#Request';
|
|
const entry = { cve: 'CVE-2024-26642', poc: [base, 'https://github.com/example/poc', 'https://github.com/EXAMPLE/POC/blob/main/exploit.py'],
|
|
collections: [...variants, variants[0].replace('google/security-research', 'GOOGLE/Security-Research')],
|
|
nuclei: [template, template.replace('Test.yaml', 'test.yaml')] };
|
|
context.variantEntry = entry;
|
|
vm.runInContext(`metadata = { 'CVE-2024-26642': { advisories: [[${JSON.stringify(base)}, ['Exploit']]] } };`, context);
|
|
for (const fn of ['entryLinks', 'rankedLinks']) {
|
|
const links = Array.from(vm.runInContext(`${fn}(variantEntry)`, context));
|
|
assert.equal(links.length, 5);
|
|
for (const url of [...variants, template, template.replace('Test.yaml', 'test.yaml')]) assert.ok(links.includes(url), url);
|
|
assert.ok(!links.includes(base));
|
|
}
|
|
const html = vm.runInContext('resultRow(variantEntry)', context);
|
|
for (const url of variants) assert.ok(html.includes(`href="${url}"`));
|
|
assert.doesNotMatch(html, /CREDIBLE ADVISORIES/);
|
|
assert.equal(vm.runInContext(`normalizedLink(${JSON.stringify(variants[0])}) === normalizedLink(${JSON.stringify(base)})`, context), true);
|
|
});
|
|
|
|
test('a stalled trending feed does not block a bookmarked search', async t => {
|
|
const { elements, calls } = startPage(t, { search: '?q=widget', trending: new Promise(() => {}) });
|
|
await new Promise(setImmediate);
|
|
assert.ok(calls.includes('/CVE_list.json'));
|
|
assert.equal(elements.get('[data-search]').value, 'widget');
|
|
assert.match(elements.get('[data-results]').innerHTML, /href="\/CVE-2026-1234"/);
|
|
});
|
|
|
|
test('a bookmarked typo is corrected when indexing finishes without changing filters', async t => {
|
|
const { elements, context } = startPage(t, {
|
|
search: '?q=wdiget&src=github&kev=1&sort=newest',
|
|
trending: new Promise(() => {}),
|
|
kev: { 'CVE-2026-1234': ['2026-10-06', false] }
|
|
});
|
|
await new Promise(setImmediate);
|
|
assert.equal(vm.runInContext('wordIndex.ready', context), true);
|
|
assert.match(elements.get('[data-results]').innerHTML, /href="\/CVE-2026-1234"/);
|
|
assert.match(elements.get('[data-status]').textContent, /widget for wdiget/);
|
|
assert.equal(elements.get('[data-search]').value, 'wdiget');
|
|
assert.equal(vm.runInContext('state.query', context), 'wdiget');
|
|
assert.equal(vm.runInContext('state.sort', context), 'NEWEST');
|
|
assert.equal(vm.runInContext('state.kevOnly', context), true);
|
|
assert.equal(vm.runInContext("[...state.filters.source].join(',')", context), 'GITHUB');
|
|
});
|
|
|
|
test('a failed trending feed preserves the published homepage rows', async t => {
|
|
const { elements } = startPage(t, { trending: Promise.reject(new Error('Feed unavailable')) });
|
|
await new Promise(setImmediate);
|
|
assert.match(elements.get('[data-trend-rows]').innerHTML, /Prerendered PoC/);
|
|
});
|
|
|
|
test('an empty successful feed replaces stale homepage rows', async t => {
|
|
const { elements } = startPage(t, { trending: { items: [], total_cves: 1, with_pocs: 1, generated: '2026-10-06' } });
|
|
await new Promise(setImmediate);
|
|
assert.match(elements.get('[data-trend-rows]').innerHTML, /No recent PoCs/);
|
|
});
|
|
|
|
test('an index failure stays visible when the optional trending feed arrives later', async t => {
|
|
let finishTrending;
|
|
const trending = new Promise(resolve => { finishTrending = resolve; });
|
|
const { elements } = startPage(t, { search: '?q=widget', trending, indexError: true });
|
|
await new Promise(setImmediate);
|
|
assert.match(elements.get('[data-results]').innerHTML, /could not be loaded/);
|
|
assert.equal(elements.get('[data-results]').hidden, false);
|
|
finishTrending({ items: [], total_cves: 1, with_pocs: 1, generated: '2026-10-06' });
|
|
await new Promise(setImmediate);
|
|
assert.match(elements.get('[data-results]').innerHTML, /could not be loaded/);
|
|
assert.equal(elements.get('[data-results]').hidden, false);
|
|
assert.equal(elements.get('[data-status]').textContent, 'index unavailable');
|
|
});
|
|
|
|
test('late KEV data cannot hide a bookmarked search failure', async t => {
|
|
let finishKev;
|
|
const kev = new Promise(resolve => { finishKev = resolve; });
|
|
const { elements } = startPage(t, {
|
|
search: '?q=widget', trending: new Promise(() => {}), kev, indexError: true
|
|
});
|
|
await new Promise(setImmediate);
|
|
finishKev({ 'CVE-2026-1234': ['2026-10-06', false] });
|
|
await new Promise(setImmediate);
|
|
assert.match(elements.get('[data-results]').innerHTML, /could not be loaded/);
|
|
assert.equal(elements.get('[data-results]').hidden, false);
|
|
assert.equal(elements.get('[data-trending]').hidden, true);
|
|
});
|
|
|
|
for (const sort of ['relevance', 'newest']) {
|
|
test(`late EPSS data refreshes results while respecting ${sort} sort`, async t => {
|
|
const elements = new Map();
|
|
const document = {
|
|
addEventListener() {},
|
|
querySelectorAll: () => [],
|
|
querySelector(selector) {
|
|
if (!elements.has(selector)) {
|
|
elements.set(selector, {
|
|
value: '',
|
|
innerHTML: '',
|
|
addEventListener() {},
|
|
setAttribute() {},
|
|
querySelectorAll: () => []
|
|
});
|
|
}
|
|
return elements.get(selector);
|
|
}
|
|
};
|
|
let finishEpss;
|
|
const pendingEpss = new Promise(resolve => { finishEpss = resolve; });
|
|
const records = ['CVE-2025-1234', 'CVE-2024-1234'].map(cve => ({
|
|
cve,
|
|
desc: 'widget remote execution',
|
|
poc: [`https://github.com/owner/${cve}`]
|
|
}));
|
|
const context = vm.createContext({
|
|
console,
|
|
document,
|
|
performance,
|
|
MessageChannel,
|
|
URLSearchParams,
|
|
location: { pathname: '/', search: `?q=widget&sort=${sort}` },
|
|
window: { matchMedia: () => ({ matches: false }) },
|
|
setTimeout() {},
|
|
clearTimeout() {},
|
|
async fetch(url) {
|
|
const data = url === '/epss.json' ? await pendingEpss
|
|
: url === '/CVE_list.json' ? records
|
|
: url === '/trending_poc.json'
|
|
? { items: [], generated: '2026-10-06', total_cves: 2, with_pocs: 2 }
|
|
: {};
|
|
return { ok: true, json: async () => data };
|
|
}
|
|
});
|
|
vm.runInContext(script, context);
|
|
t.after(() => vm.runInContext('yieldPort.port1.close(); yieldPort.port2.close();', context));
|
|
const resultIds = () => [...elements.get('[data-results]').innerHTML
|
|
.matchAll(/class="result-id" href="\/([^"]+)"/g)].map(match => match[1]);
|
|
|
|
await new Promise(setImmediate);
|
|
assert.deepEqual(resultIds(), ['CVE-2025-1234', 'CVE-2024-1234']);
|
|
|
|
finishEpss({ 'CVE-2025-1234': [0.01, 0.1], 'CVE-2024-1234': [0.99, 0.999] });
|
|
await new Promise(setImmediate);
|
|
assert.deepEqual(resultIds(), sort === 'relevance'
|
|
? ['CVE-2024-1234', 'CVE-2025-1234']
|
|
: ['CVE-2025-1234', 'CVE-2024-1234']);
|
|
assert.match(elements.get('[data-results]').innerHTML, /EPSS 99%/);
|
|
});
|
|
}
|