Download app.js from HashgraphOnline/hol-plugin-security: direct link, hf CLI and curl.
- Browser
- Download file 7.56 kB
-
https://huggingface.co/spaces/HashgraphOnline/hol-plugin-security/resolve/main/app.js
- Command line
-
hf download hf://spaces/HashgraphOnline/hol-plugin-security/app.js
-
curl -L -o app.js https://huggingface.co/spaces/HashgraphOnline/hol-plugin-security/resolve/main/app.js
7.56 kB
| const DATASET_ERROR = "Could not load snapshot JSON. Open the dataset: https://huggingface.co/datasets/HashgraphOnline/hol-plugin-security"; | |
| const PLUGIN_COLS = ["slug","display_name","developer_name","category","trust_score","safety_label","scanner_grade","score_security","score_mcp_posture","findings_high","findings_total","github_stars","mcp_server_count","skill_count","version","ecosystems","scanner_provider","owner_verified","publisher_verified","snapshot_at","description","repository","profile_url"]; | |
| const ADV_COLS = ["owasp_mcp","owasp_mcp_name","slug","title","severity","threat_class","summary","surfaces","url","date_modified","curated"]; | |
| const FIX_COLS = ["scenario_id","scenario_family","harness_id","comparator","outcome","receipt_created","modeled","harness_independent","median_latency_ms","p95_latency_ms","run_id","schema_version"]; | |
| function unique(rows, key) { | |
| return [...new Set(rows.map(r => String(r[key] ?? "")).filter(Boolean))].sort(); | |
| } | |
| function paginate(rows, page, size) { | |
| const pages = Math.max(1, Math.ceil(rows.length / size)); | |
| page = Math.min(Math.max(1, page), pages); | |
| const start = (page - 1) * size; | |
| return { slice: rows.slice(start, start + size), page, pages, n: rows.length, start, end: Math.min(start + size, rows.length) }; | |
| } | |
| function searchFilter(rows, q) { | |
| q = (q || "").trim().toLowerCase(); | |
| if (!q) return rows; | |
| return rows.filter(r => Object.values(r).some(v => String(v).toLowerCase().includes(q))); | |
| } | |
| function eqFilter(rows, key, value) { | |
| if (!value || value === "(all)") return rows; | |
| return rows.filter(r => String(r[key]) === value); | |
| } | |
| function sortRows(rows, key, dir) { | |
| const copy = rows.slice(); | |
| copy.sort((a, b) => { | |
| const va = a[key], vb = b[key]; | |
| const na = Number(va), nb = Number(vb); | |
| let cmp; | |
| if (va !== "" && vb !== "" && va != null && vb != null && !Number.isNaN(na) && !Number.isNaN(nb)) cmp = na - nb; | |
| else cmp = String(va).localeCompare(String(vb), undefined, { numeric: true, sensitivity: "base" }); | |
| return dir === "desc" ? -cmp : cmp; | |
| }); | |
| return copy; | |
| } | |
| function el(tag, attrs, html) { | |
| const n = document.createElement(tag); | |
| if (attrs) Object.entries(attrs).forEach(([k, v]) => { | |
| if (k === "class") n.className = v; | |
| else n.setAttribute(k, v); | |
| }); | |
| if (html != null) n.innerHTML = html; | |
| return n; | |
| } | |
| function renderTable(table, rows, cols) { | |
| table.innerHTML = ""; | |
| const thead = el("thead"); | |
| const tr = el("tr"); | |
| cols.forEach(c => tr.appendChild(el("th", { "data-col": c }, c))); | |
| thead.appendChild(tr); | |
| const tbody = el("tbody"); | |
| rows.forEach(r => { | |
| const row = el("tr"); | |
| cols.forEach(c => { | |
| const v = r[c]; | |
| const td = el("td"); | |
| const s = v == null ? "" : String(v); | |
| if (typeof v === "string" && s.startsWith("http")) { | |
| const a = el("a", { href: s, target: "_blank", rel: "noopener" }); | |
| a.textContent = s.replace(/^https?:\/\//, "").slice(0, 64); | |
| td.appendChild(a); | |
| } else td.textContent = s; | |
| row.appendChild(td); | |
| }); | |
| tbody.appendChild(row); | |
| }); | |
| table.appendChild(thead); | |
| table.appendChild(tbody); | |
| } | |
| function bindTable(opts) { | |
| const state = { q: "", filters: {}, page: 1, size: opts.pageSize, sortKey: opts.sortKey, sortDir: opts.sortDir || "desc" }; | |
| const controls = document.getElementById(opts.controlId); | |
| const status = document.getElementById(opts.statusId); | |
| const table = document.getElementById(opts.tableId); | |
| const search = el("input", { type: "search", placeholder: "Search…" }); | |
| search.addEventListener("input", () => { state.q = search.value; state.page = 1; draw(); }); | |
| controls.appendChild(search); | |
| opts.filterKeys.forEach(key => { | |
| const sel = el("select"); | |
| sel.appendChild(el("option", { value: "(all)" }, key + ": all")); | |
| unique(opts.rows, key).forEach(v => sel.appendChild(el("option", { value: v }, v))); | |
| sel.addEventListener("change", () => { state.filters[key] = sel.value; state.page = 1; draw(); }); | |
| controls.appendChild(sel); | |
| }); | |
| const sizeSel = el("select"); | |
| opts.sizes.forEach(s => sizeSel.appendChild(el("option", { value: String(s) }, String(s) + " / page"))); | |
| sizeSel.value = String(opts.pageSize); | |
| sizeSel.addEventListener("change", () => { state.size = Number(sizeSel.value); state.page = 1; draw(); }); | |
| controls.appendChild(sizeSel); | |
| const prev = el("button", { type: "button" }, "Prev"); | |
| const next = el("button", { type: "button" }, "Next"); | |
| prev.addEventListener("click", () => { state.page -= 1; draw(); }); | |
| next.addEventListener("click", () => { state.page += 1; draw(); }); | |
| controls.appendChild(prev); | |
| controls.appendChild(next); | |
| table.addEventListener("click", (e) => { | |
| const th = e.target.closest("th"); | |
| if (!th) return; | |
| const key = th.getAttribute("data-col"); | |
| if (state.sortKey === key) state.sortDir = state.sortDir === "asc" ? "desc" : "asc"; | |
| else { state.sortKey = key; state.sortDir = "desc"; } | |
| draw(); | |
| }); | |
| function draw() { | |
| let rows = searchFilter(opts.rows, state.q); | |
| Object.entries(state.filters).forEach(([k, v]) => { rows = eqFilter(rows, k, v); }); | |
| if (state.sortKey) rows = sortRows(rows, state.sortKey, state.sortDir); | |
| const pg = paginate(rows, state.page, state.size); | |
| state.page = pg.page; | |
| status.textContent = pg.n + " rows · page " + pg.page + "/" + pg.pages + " · showing " + (pg.n ? pg.start + 1 : 0) + "–" + pg.end + (state.sortKey ? " · sort " + state.sortKey + " " + state.sortDir : ""); | |
| renderTable(table, pg.slice, opts.cols); | |
| } | |
| draw(); | |
| } | |
| document.querySelectorAll(".tabs button").forEach(btn => { | |
| btn.addEventListener("click", () => { | |
| document.querySelectorAll(".tabs button").forEach(b => b.classList.remove("active")); | |
| document.querySelectorAll(".panel").forEach(p => p.classList.remove("active")); | |
| btn.classList.add("active"); | |
| document.getElementById(btn.dataset.tab).classList.add("active"); | |
| }); | |
| }); | |
| async function loadJSON(path) { | |
| const res = await fetch(path); | |
| if (!res.ok) throw new Error(path + " HTTP " + res.status); | |
| return res.json(); | |
| } | |
| (async () => { | |
| try { | |
| const [plugins, advisories, runtime] = await Promise.all([ | |
| loadJSON("data/plugins.json"), | |
| loadJSON("data/advisories.json"), | |
| loadJSON("data/runtime_fixtures.json"), | |
| ]); | |
| bindTable({ | |
| rows: plugins, cols: PLUGIN_COLS, controlId: "plugin-controls", statusId: "plugin-status", tableId: "plugin-table", | |
| filterKeys: ["safety_label", "scanner_grade", "category"], pageSize: 50, sizes: [25, 50, 100, 205], | |
| sortKey: "trust_score", sortDir: "desc" | |
| }); | |
| bindTable({ | |
| rows: advisories, cols: ADV_COLS, controlId: "advisory-controls", statusId: "advisory-status", tableId: "advisory-table", | |
| filterKeys: ["owasp_mcp", "severity", "threat_class"], pageSize: 22, sizes: [22, 50], | |
| sortKey: "owasp_mcp", sortDir: "asc" | |
| }); | |
| bindTable({ | |
| rows: runtime, cols: FIX_COLS, controlId: "runtime-controls", statusId: "runtime-status", tableId: "runtime-table", | |
| filterKeys: ["scenario_family", "harness_id", "comparator", "outcome"], pageSize: 50, sizes: [25, 50, 100, 220], | |
| sortKey: "scenario_id", sortDir: "asc" | |
| }); | |
| } catch (err) { | |
| const box = document.querySelector("header"); | |
| const p = document.createElement("div"); | |
| p.className = "note"; | |
| p.innerHTML = "<strong>Dataset load error.</strong> " + DATASET_ERROR + "<br/><code></code>"; | |
| p.querySelector("code").textContent = String(err); | |
| box.appendChild(p); | |
| } | |
| })(); | |