Space Grotesk for prose, IBM Plex Mono for every identifier, and the flat
hairline-bordered surfaces from the handoff. Green marks what you can act
on and amber only ever means a star count; nothing else is coloured.
Three changes to how the same data reads. Descriptions clamp to three lines
behind an expander, so a kernel CVE no longer pushes the links people came
for below the fold. Raw URLs become owner / repo rows carrying stars and
last-commit age, ranked best first, five at a time. And an empty search box
now keeps the trending PoCs on screen with a TRENDING / RECENT toggle
rather than showing an empty page.
The search grammar, ranking and negation are unchanged.
Ten modules totalling 2,206 lines, reachable from no workflow and no live
script, alongside 18 MB of github-all.txt and references-all.txt that
nothing reads and 400 KB of stale api/v1 output that shipped in every
Pages artifact. scripts/README.md documented this as the build process.
utils.py existed for one caller and duplicated functions build_site.py
already had, so it folds into it. Nothing imports a third-party package
any more, and requirements.txt goes with it.
Typing before the payload arrives searched an empty dataset and reported
no results, then the loader hid the table and left a blank page, so the
query had to be typed a second time. Show that data is loading and run
the pending query once it lands.
Unquoted words were scored independently, so "active directory" matched a
description holding "active session" and "working directory" as strongly
as a real AD advisory, and the newest-first tie-break then put the wrong
one on top. Reward the words appearing together.
Plain substring matching also meant "rce" hit "open source" and "sudo"
hit "pseudo"; require a term to start a word, while still allowing it to
match into one so "word" finds "wordpress". Weight matches near the start
of a description, in coarse buckets so a few characters never outweigh
recency: "log4j rce" now leads with Log4j2 rather than GeoTools.
Scores were boosted by how many PoC links matched, which promoted old
CVEs with large link piles above recent findings; searching WordPress led
with 2015-2018. Rank on match quality alone and break ties by CVE year
and number, newest first.
Only the id and description are needed for every comparison, so PoC text
is derived on demand: preparing 108934 entries drops from 2.9s to 0.6s.
Dropping no-store lets the browser revalidate the 53 MB payload instead
of refetching it on every visit; the 5 KB trending file stays uncached.
Build result rows directly, since String.replace reads $& in the
replacement as a pattern, and escape link text like every other cell.
Pages builds from the workflow artifact, so the committed CVE_list.json
was never served while adding a fresh 53 MB blob to history every day.
Generate it during the Pages build instead and drop the duplicate
generator; scripts/build_site.py is now the only thing that writes it.
The jinja2 templates re-rendered a trending table that logic.js replaces
on load, so the page shell goes back to being a plain static file and the
Pages build no longer installs anything. Shallow-clone the sync job too:
it never reads git history, and the three-day lookback already recovers
a skipped day.