';
}
const parsed = repoFromUrl(url);
const href = escapeHTML(url);
// A gist or an advisory has no owner and no stars; the star and age columns
// stay empty rather than inventing a repository shape for it.
if (!parsed) {
return '
`;
}
function compact(value) {
return value.toLowerCase().replace(/[^a-z0-9]/g, '');
}
/** Best links first. A repository named after the CVE is a proof of concept for
* it; a 5k-star scanner that merely mentions it is not, so stars only order
* repositories that are already about this CVE. Links with no star count keep
* their original order behind both: a reference has no repository to rank. */
function rankedLinks(entry) {
if (entry._ranked) return entry._ranked;
const id = compact(entry.cve);
const scored = (entry.poc || []).map((url, index) => {
const parsed = repoFromUrl(url);
const meta = parsed && repoMeta[(parsed.owner + '/' + parsed.repo).toLowerCase()];
return {
url,
index,
dedicated: compact(url).includes(id) ? 1 : 0,
stars: meta ? meta[0] : -1
};
});
scored.sort((a, b) =>
(b.dedicated - a.dedicated) || (b.stars - a.stars) || (a.index - b.index));
// A nuclei template leads: of everything linked here it is the one entry that
// runs as it stands, against a target, without reading somebody's code first.
// Curated entries lead: templates, exploit archives, modules and runnable
// environments can be used as they stand without first reading a repository.
entry._ranked = uniqueSourceLinks([
...(entry.nuclei || []), ...(entry.msf || []), ...(entry.edb || []), ...(entry.vulhub || []),
...(entry.collections || []),
...scored.map(item => item.url)
]);
return entry._ranked;
}
function cvssTooltip(id) {
const rows = ((metadata[id] || {}).cvss || []);
if (!Array.isArray(rows)) return '';
return rows.filter(Array.isArray).map(row => {
const source = row[4] ? ` by ${row[4]}` : '';
const assessment = row[5] ? ` (${row[5]})` : '';
return `CVSS ${row[0]} ${row[1]} ${row[2]}${source}${assessment}\n${row[3] || ''}`.trim();
}).join('\n\n');
}
function advisoryRow(item) {
if (!Array.isArray(item) || !item[0]) return '';
const url = String(item[0]);
const tags = Array.isArray(item[1]) ? item[1].map(String) : [];
const tag = tags.includes('Vendor Advisory') ? 'VENDOR' : tags.includes('Patch') ? 'PATCH' : 'ADVISORY';
return '
`;
}
/** Hide the expander on descriptions that already fit; a button that does
* nothing when clicked is worse than no button. */
function pruneExpanders() {
for (const row of el.results.querySelectorAll('.result-row')) {
const desc = row.querySelector('.result-desc');
const button = row.querySelector('.expander');
if (!desc || !button) continue;
if (!desc.classList.contains('is-open') && desc.scrollHeight <= desc.clientHeight + 1) {
button.hidden = true;
}
}
}
function renderResults(elapsed) {
const results = state.results;
el.trending.hidden = true;
el.results.hidden = false;
if (!results.length) {
const subject = state.query.length >= MIN_QUERY
? ` for ${escapeHTML(state.query)}`
: ' for the active filters';
el.results.innerHTML = `
${shown.map(resultRow).join('')}${footer}`;
pruneExpanders();
if (elapsed != null) {
el.status.textContent = `matched in ${Math.max(1, Math.round(elapsed))}ms`;
}
}
function trendRow(item) {
const popular = item.stars >= 500 ? ' is-popular' : '';
// The trending table flags the same catalogue the CVE rows do, so a row worth
// reading first is visible without opening it.
const flagged = item.cve && kev[item.cve]
? `KEV`
: '';
return `
`;
}
function renderTrending() {
el.results.hidden = true;
el.trending.hidden = false;
const ranked = trending.slice();
if (state.mode === 'TRENDING') {
// Stars weighted against the age the row displays, so the order is legible
// from the table itself: a fresh PoC pulling stars outranks a bigger one
// that has been sitting still.
const rank = r => r.stars / Math.pow((r._pushed == null ? 8760 : r._pushed) + 6, 0.45);
ranked.sort((a, b) => rank(b) - rank(a));
} else {
ranked.sort((a, b) => (a._pushed == null ? Infinity : a._pushed) - (b._pushed == null ? Infinity : b._pushed));
}
const rows = ranked.slice(0, TREND_ROWS);
el.trendNote.textContent = state.mode === 'TRENDING'
? 'stars weighted against time since the last commit'
: 'newest commit first';
el.trendRows.innerHTML = rows.length
? rows.map(trendRow).join('')
: '
No recent PoCs.
';
document.querySelectorAll('.switch button').forEach(button => {
button.setAttribute('aria-pressed', String(button.dataset.mode === state.mode));
});
}
function paintHeroStats() {
if (!el.statTotal) return;
if (indexMeta) {
el.statTotal.textContent = formatCount(indexMeta.total_cves);
el.statPocs.textContent = formatCount(indexMeta.with_pocs);
}
const flagged = Object.keys(kev).length;
if (flagged) el.statKev.textContent = formatCount(flagged);
}
function idleStatus() {
// The hero carries the totals now, so the field keeps its slot for timings.
return '';
}
function render() {
if (state.query.length < MIN_QUERY && !hasActiveFilters()) {
el.status.textContent = state.ready ? idleStatus() : 'loading index…';
renderTrending();
return;
}
if (!state.ready) {
el.results.hidden = false;
el.trending.hidden = true;
el.status.textContent = 'loading index…';
el.results.innerHTML = '
Loading the CVE index…
';
return;
}
const started = performance.now();
state.results = runSearch(state.query.length >= MIN_QUERY ? state.query : '');
renderResults(performance.now() - started);
}
/* ---- wiring ------------------------------------------------------------ */
// A full pass over the index costs ~200ms, so one per keystroke makes typing
// stutter. 160ms covers an ordinary typing cadence without the wait being felt;
// clearing the field stays instant.
let searchTimer = null;
el.input.addEventListener('input', () => {
const next = el.input.value.trim();
if (next === state.query) return;
state.query = next;
state.shown = PAGE_SIZE;
clearTimeout(searchTimer);
if (next.length < MIN_QUERY) {
render();
return;
}
searchTimer = setTimeout(render, 160);
});
document.querySelector('.search').addEventListener('submit', event => event.preventDefault());
function renderFilterOptions() {
for (const [kind, options] of Object.entries(FILTER_OPTIONS)) {
el.filterOptions[kind].innerHTML = options.map(([value, label]) =>
``
).join('');
}
}
function closeFilterMenus(except) {
for (const kind of Object.keys(FILTER_OPTIONS)) {
if (kind === except) continue;
el.filterOptions[kind].hidden = true;
el.filterTriggers[kind].setAttribute('aria-expanded', 'false');
}
}
function filterTitle(kind) {
const selected = [...state.filters[kind]];
return selected.length === FILTER_OPTIONS[kind].length ? 'All values' : selected.join(', ');
}
function syncFilterControls() {
for (const kind of Object.keys(FILTER_OPTIONS)) {
const filter = state.filters[kind];
const summary = filter.size === FILTER_OPTIONS[kind].length
? 'ALL'
: filter.size === 1 ? [...filter][0] : `${filter.size} SELECTED`;
el.filterSummaries[kind].textContent = summary;
el.filterTriggers[kind].title = filterTitle(kind);
for (const checkbox of el.filterOptions[kind].querySelectorAll('[data-filter-value]')) {
checkbox.checked = filter.has(checkbox.dataset.filterValue);
}
}
el.kevOnly.setAttribute('aria-pressed', String(state.kevOnly));
el.clearFilters.hidden = !hasActiveFilters();
}
for (const kind of Object.keys(FILTER_OPTIONS)) {
el.filterTriggers[kind].addEventListener('click', () => {
const opening = el.filterOptions[kind].hidden;
closeFilterMenus(kind);
el.filterOptions[kind].hidden = !opening;
el.filterTriggers[kind].setAttribute('aria-expanded', String(opening));
});
el.filterOptions[kind].addEventListener('change', event => {
const checkbox = event.target.closest('[data-filter-value]');
if (!checkbox) return;
const filter = state.filters[kind];
const value = checkbox.dataset.filterValue;
if (checkbox.checked) filter.add(value);
else filter.delete(value);
state.shown = PAGE_SIZE;
syncFilterControls();
render();
});
}
document.addEventListener('click', event => {
if (!event.target.closest('.filter-menu')) closeFilterMenus();
});
el.kevOnly.addEventListener('click', () => {
state.kevOnly = !state.kevOnly;
state.shown = PAGE_SIZE;
syncFilterControls();
render();
});
el.clearFilters.addEventListener('click', () => {
state.kevOnly = false;
for (const [kind, filter] of Object.entries(state.filters)) {
filter.clear();
for (const [value] of FILTER_OPTIONS[kind]) filter.add(value);
}
state.shown = PAGE_SIZE;
syncFilterControls();
render();
});
renderFilterOptions();
syncFilterControls();
// Typing anywhere on the page means typing into the search field. The keystroke
// is not swallowed: focusing during keydown lets the character land in the
// field it just moved to, so the first letter is never lost.
document.addEventListener('keydown', event => {
if (event.ctrlKey || event.metaKey || event.altKey) return;
const openFilter = Object.keys(FILTER_OPTIONS).find(kind =>
el.filterTriggers[kind].getAttribute('aria-expanded') === 'true');
if (event.key === 'Escape' && openFilter) {
closeFilterMenus();
el.filterTriggers[openFilter].focus();
return;
}
if (document.activeElement === el.input) {
if (event.key === 'Escape') {
el.input.value = '';
el.input.dispatchEvent(new Event('input', { bubbles: true }));
el.input.blur();
}
return;
}
const active = document.activeElement;
const editing = active && (active.isContentEditable
|| ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].includes(active.tagName));
if (editing) return;
// A lone slash is the shortcut, so it focuses without typing itself.
if (event.key === '/') {
event.preventDefault();
el.input.focus();
return;
}
if (event.key.length !== 1 && event.key !== 'Backspace') return;
el.input.focus();
});
// A pointer landing on the page is not a request to type, so the field is only
// focused up front where a keyboard is the likely input.
if (window.matchMedia('(min-width: 900px)').matches) el.input.focus();
document.querySelector('.switch').addEventListener('click', event => {
const button = event.target.closest('button[data-mode]');
if (!button) return;
state.mode = button.dataset.mode;
renderTrending();
});
el.results.addEventListener('click', event => {
const desc = event.target.closest('[data-toggle-desc]');
const poc = event.target.closest('[data-toggle-poc]');
const advisory = event.target.closest('[data-toggle-advisory]');
const more = event.target.closest('[data-more-results]');
if (desc) {
const id = desc.dataset.toggleDesc;
state.descOpen.has(id) ? state.descOpen.delete(id) : state.descOpen.add(id);
} else if (poc) {
const id = poc.dataset.togglePoc;
state.pocOpen.has(id) ? state.pocOpen.delete(id) : state.pocOpen.add(id);
} else if (advisory) {
const id = advisory.dataset.toggleAdvisory;
state.advisoryOpen.has(id) ? state.advisoryOpen.delete(id) : state.advisoryOpen.add(id);
} else if (more) {
state.shown += PAGE_SIZE;
} else {
return;
}
renderResults(null);
});
async function loadJSON(url, options) {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`${url} (${response.status})`);
return response.json();
}
(async () => {
try {
const meta = await loadJSON('/trending_poc.json', { cache: 'no-store' });
indexMeta = meta;
trending = (meta.items || []).map(item => Object.assign({}, item, {
_pushed: hoursSince(item.pushed),
_created: hoursSince(item.created)
}));
const minutes = Math.max(0, Math.round((Date.now() - Date.parse(meta.generated)) / 60000));
el.refreshed.textContent = minutes < 60
? `index refreshed ${minutes} minute${minutes === 1 ? '' : 's'} ago`
: `index refreshed ${Math.round(minutes / 60)} hour${Math.round(minutes / 60) === 1 ? '' : 's'} ago`;
} catch (err) {
console.warn(err.message);
}
paintHeroStats();
render();
// The repository metadata is optional: without it the PoC rows simply lose
// their star and age columns, which is the documented fallback.
loadJSON('/kev.json', { cache: 'no-cache' }).then(data => {
kev = data || {};
el.kevOnly.disabled = false;
paintHeroStats();
if (state.ready && (state.query || hasActiveFilters())) render();
else renderTrending();
}).catch(err => console.warn(err.message));
loadJSON('/cve_metadata.json', { cache: 'no-cache' }).then(data => {
metadata = data || {};
el.filterTriggers.severity.disabled = false;
if (state.ready && (state.query || hasActiveFilters())) render();
}).catch(err => console.warn(err.message));
loadJSON('/nuclei.json', { cache: 'no-cache' }).then(data => {
ratings = data || {};
el.filterTriggers.severity.disabled = false;
if (state.ready && (state.query || hasActiveFilters())) render();
}).catch(err => console.warn(err.message));
loadJSON('/epss.json', { cache: 'no-cache' }).then(data => {
epss = data || {};
if (state.ready && (state.query || hasActiveFilters())) renderResults(null);
}).catch(err => console.warn(err.message));
loadJSON('/repo_meta.json', { cache: 'no-cache' }).then(data => {
repoMeta = data || {};
for (const entry of dataset) entry._ranked = null;
if (state.ready && (state.query || hasActiveFilters())) renderResults(null);
}).catch(err => console.warn(err.message));
try {
dataset = prepareDataset(await loadJSON('/CVE_list.json', { cache: 'no-cache' }));
state.ready = true;
el.filterTriggers.source.disabled = false;
render();
} catch (err) {
console.warn(err.message);
el.status.textContent = 'index unavailable';
}
})();