Spaces:
Running
Running
Download index.html from coralexbadea/SafetyCard: direct link, hf CLI and curl.
- Browser
- Download file 17.6 kB
-
https://huggingface.co/spaces/coralexbadea/SafetyCard/resolve/main/index.html
- Command line
-
hf download hf://spaces/coralexbadea/SafetyCard/index.html
-
curl -L -o index.html https://huggingface.co/spaces/coralexbadea/SafetyCard/resolve/main/index.html
17.6 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>SafetyCard: Free Safety Scanner for Hugging Face LLMs</title> | |
| <meta name="description" content="Is this Hugging Face model safe to download? SafetyCard grades pickle and remote-code risk instantly in your browser, checks lineage and uncensored/abliterated safety removal on your own GPU. Free and open source."> | |
| <meta name="keywords" content="LLM safety scanner, Hugging Face model security, safetensors, pickle exploit, trust_remote_code, uncensored model detection, abliterated, model lineage, AI safety benchmark, open source"> | |
| <meta property="og:title" content="SafetyCard: a free safety report card for any Hugging Face LLM"> | |
| <meta property="og:description" content="Grade file safety in seconds. Check lineage and safety structure on your own GPU. Free, open source, measured not guessed."> | |
| <meta property="og:type" content="website"> | |
| <meta name="twitter:card" content="summary"> | |
| <script type="application/ld+json"> | |
| {"@context":"https://schema.org","@type":"SoftwareApplication","name":"SafetyCard","applicationCategory":"SecurityApplication","operatingSystem":"Web, Linux, macOS, Windows","offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"description":"Free safety report card for Hugging Face language models: file safety grade, lineage check and safety-structure check, run on your own GPU.","license":"https://www.apache.org/licenses/LICENSE-2.0"} | |
| </script> | |
| <script type="application/ld+json"> | |
| {"@context":"https://schema.org","@type":"FAQPage","mainEntity":[ | |
| {"@type":"Question","name":"Is it safe to download a Hugging Face model?","acceptedAnswer":{"@type":"Answer","text":"Usually, if the weights are safetensors and the model needs no custom code (grade A). Pickle files (.bin, .pt, .ckpt) and models requiring trust_remote_code can run code when loaded. SafetyCard flags both before you download anything."}}, | |
| {"@type":"Question","name":"How can I tell if a model was uncensored or abliterated?","acceptedAnswer":{"@type":"Answer","text":"Removing safety training tends to weaken the model's internal separation of harmful and harmless requests. SafetyCard measures that separation against the parent model and reports a difference only when it is clear."}}, | |
| {"@type":"Question","name":"Does SafetyCard detect hidden backdoors?","acceptedAnswer":{"@type":"Answer","text":"No. No public tool can currently guarantee a model is backdoor-free, and SafetyCard does not claim to. It reports evidence it can measure reliably."}}, | |
| {"@type":"Question","name":"Is SafetyCard free?","acceptedAnswer":{"@type":"Answer","text":"Yes. The instant scan runs in your browser and deep scans run on your own hardware. It is Apache-2.0 open source."}}]} | |
| </script> | |
| <style> | |
| :root{--bg:#f7f8fa;--surface:#ffffff;--text:#16181d;--muted:#5b6170;--border:#e2e5ea;--accent:#0f766e;--accent-ink:#ffffff;--code:#f1f3f6; | |
| --gA:#15803d;--gB:#a16207;--gC:#c2410c;--gF:#b91c1c;--shadow:0 1px 2px rgba(0,0,0,.05),0 4px 16px rgba(0,0,0,.05)} | |
| @media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#0e1116;--surface:#161a21;--text:#e8eaee;--muted:#9aa1ad;--border:#2a303a;--accent:#2dd4bf;--accent-ink:#06201d;--code:#1d222b; | |
| --gA:#4ade80;--gB:#facc15;--gC:#fb923c;--gF:#f87171;--shadow:none}} | |
| :root[data-theme="dark"]{--bg:#0e1116;--surface:#161a21;--text:#e8eaee;--muted:#9aa1ad;--border:#2a303a;--accent:#2dd4bf;--accent-ink:#06201d;--code:#1d222b;--gA:#4ade80;--gB:#facc15;--gC:#fb923c;--gF:#f87171;--shadow:none} | |
| *{box-sizing:border-box}html{-webkit-text-size-adjust:100%} | |
| body{margin:0;background:var(--bg);color:var(--text);font:16px/1.6 system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif} | |
| main{max-width:960px;margin:0 auto;padding:32px 16px 64px} | |
| h1{font-size:clamp(1.8rem,4.5vw,2.6rem);line-height:1.15;margin:0 0 12px;letter-spacing:-.02em} | |
| h2{font-size:1.35rem;margin:48px 0 12px;letter-spacing:-.01em}h3{font-size:1.05rem;margin:20px 0 6px} | |
| p{margin:0 0 12px}.lede{font-size:1.15rem;color:var(--muted);max-width:720px} | |
| .pill{display:inline-block;font-size:.8rem;font-weight:600;color:var(--accent);border:1px solid var(--border);border-radius:999px;padding:2px 10px;margin-bottom:14px} | |
| .card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:20px;box-shadow:var(--shadow)} | |
| .scan{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px} | |
| .scan input{flex:1 1 260px;min-width:0;font:inherit;padding:12px 14px;border-radius:10px;border:1px solid var(--border);background:var(--surface);color:var(--text)} | |
| .scan input:focus{outline:2px solid var(--accent);outline-offset:1px} | |
| button{font:inherit;font-weight:600;padding:12px 18px;border-radius:10px;border:0;background:var(--accent);color:var(--accent-ink);cursor:pointer} | |
| button:disabled{opacity:.6;cursor:progress} | |
| .examples{font-size:.88rem;color:var(--muted);margin-top:8px}.examples a{color:var(--accent);cursor:pointer;text-decoration:underline;margin-right:10px} | |
| #result{margin-top:18px}.grade{display:flex;align-items:center;gap:14px;margin-bottom:10px} | |
| .badge{flex:none;width:56px;height:56px;border-radius:12px;display:grid;place-items:center;font-size:1.7rem;font-weight:800;color:#fff} | |
| .bA{background:var(--gA)}.bB{background:var(--gB)}.bC{background:var(--gC)}.bF{background:var(--gF)} | |
| ul.facts{margin:8px 0 0;padding-left:20px}ul.facts li{margin:2px 0} | |
| .err{color:var(--gF);font-weight:600}.muted{color:var(--muted)} | |
| .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px} | |
| .grid .card h3{margin-top:0} | |
| pre{background:var(--code);border:1px solid var(--border);border-radius:10px;padding:14px;overflow-x:auto;font-size:.88rem;line-height:1.5} | |
| code{background:var(--code);padding:1px 5px;border-radius:5px;font-size:.92em}pre code{background:none;padding:0} | |
| .tablewrap{overflow-x:auto;border:1px solid var(--border);border-radius:12px;background:var(--surface)} | |
| table{border-collapse:collapse;width:100%;font-size:.92rem}th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--border);white-space:nowrap} | |
| th{font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}tr:last-child td{border-bottom:0} | |
| td.num{font-variant-numeric:tabular-nums} | |
| .tag{font-size:.78rem;font-weight:700;padding:2px 8px;border-radius:6px;border:1px solid var(--border)} | |
| .weaker{color:var(--gF)}.stronger{color:var(--gA)} | |
| details{border-top:1px solid var(--border);padding:12px 0}details summary{cursor:pointer;font-weight:600} | |
| footer{margin-top:56px;font-size:.88rem;color:var(--muted)}a{color:var(--accent)} | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <span class="pill">Free · Open source · Runs on your GPU</span> | |
| <h1>🛡️ SafetyCard: a free safety report card for any Hugging Face LLM</h1> | |
| <p class="lede">Is this model safe to download? Is it really derived from the model it claims? Has its safety training been stripped out? Get answers measured from the files and weights themselves.</p> | |
| <section class="card" aria-labelledby="scan-h"> | |
| <h2 id="scan-h" style="margin-top:0">⚡ Instant file scan</h2> | |
| <p class="muted">Runs in your browser through the Hugging Face Hub API. No weights are downloaded and nothing is sent to us.</p> | |
| <form class="scan" id="scanform"> | |
| <label for="rid" class="muted" style="position:absolute;left:-9999px">Model id</label> | |
| <input id="rid" placeholder="org/model, e.g. Qwen/Qwen2.5-1.5B-Instruct" autocomplete="off" spellcheck="false"> | |
| <button id="go" type="submit">Scan files</button> | |
| </form> | |
| <div class="examples">Try: <a data-ex="Qwen/Qwen2.5-1.5B-Instruct">Qwen2.5-1.5B-Instruct</a><a data-ex="openai-community/gpt2">gpt2</a><a data-ex="deepseek-ai/DeepSeek-V3">DeepSeek-V3</a><a data-ex="sshleifer/tiny-gpt2">tiny-gpt2</a></div> | |
| <div id="result" aria-live="polite"></div> | |
| </section> | |
| <h2>What SafetyCard checks</h2> | |
| <div class="grid"> | |
| <div class="card"><h3>⚡ File safety, A–F</h3><p>Pickle files that can run code when loaded, models that need <code>trust_remote_code</code>, the declared base model, the license and Hugging Face's own malware scan.</p><p class="muted">Instant, in your browser.</p></div> | |
| <div class="card"><h3>🧬 Lineage</h3><p>Compares weights tensor by tensor with the claimed base model: fine-tuned from it, heavily modified, or not derived from it at all.</p><p class="muted">Your GPU, or a CPU for small models.</p></div> | |
| <div class="card"><h3>🛡️ Safety structure</h3><p>Measures whether the model still separates harmful from harmless requests internally, compared with its parent. Uncensoring tends to weaken this.</p><p class="muted">Your GPU. No text is generated.</p></div> | |
| </div> | |
| <h2>🔬 Run the deep scan free on your own GPU</h2> | |
| <pre><code>git clone https://huggingface.co/spaces/coralexbadea/SafetyCard && cd SafetyCard | |
| pip install -r requirements.txt | |
| python -m safetycard.cli scan Qwen/Qwen2.5-1.5B-Instruct --deep --out card.json</code></pre> | |
| <p>Prefer a web interface? Run <code>python app.py</code> for the full local UI with all three checks. Every deep scan saves a <code>card.json</code> you can share or submit to the leaderboard.</p> | |
| <h2>🏆 Leaderboard</h2> | |
| <p class="muted">Deep-scan cards with real measurements. Add yours by opening a pull request on this Space that adds your <code>card.json</code> to <code>results/</code>.</p> | |
| <div class="tablewrap"><table id="lb"><thead><tr><th>Model</th><th>File</th><th>Parent</th><th>Lineage</th><th>Weight change</th><th>Safety d′ (range)</th><th>vs parent</th></tr></thead><tbody><tr><td colspan="7" class="muted">Loading…</td></tr></tbody></table></div> | |
| <h2>How accurate is it?</h2> | |
| <p>Every score comes with its own controls, so you can tell a signal from noise:</p> | |
| <ul class="facts"> | |
| <li><b>Held-out scoring.</b> The direction and the layer are chosen on one half of the prompts and scored on the other half.</li> | |
| <li><b>Permutation null.</b> The whole procedure is repeated on shuffled labels, which gives the chance level printed on every card.</li> | |
| <li><b>Prompt resampling.</b> Each model is measured three times. "Weaker" or "stronger than parent" is reported only when the ranges do not overlap.</li> | |
| </ul> | |
| <p>Calibration on real models: official Qwen2.5-1.5B-Instruct scores d′ 10.16 (8.80–11.00) against 4.00 for its pretrained base. A community "uncensored" fine-tune of it scores 7.10 (6.59–7.36): <span class="weaker">weaker than its parent</span>. Lineage: official instruct-tuning changes the weights by 0.010–0.042, while two separately pretrained models of the same architecture measure 1.18.</p> | |
| <h2>Built to be safe itself</h2> | |
| <p>A scanner that handles untrusted models must not become the attack. SafetyCard loads safetensors only, never unpickles a file, never sets <code>trust_remote_code</code> and generates no text.</p> | |
| <h2>FAQ</h2> | |
| <details><summary>Is it safe to download a Hugging Face model?</summary><p>Usually, if the weights are safetensors and the model needs no custom code. That is grade A. Pickle-format files (<code>.bin</code>, <code>.pt</code>, <code>.ckpt</code>) can run arbitrary code when loaded, and so can models that require <code>trust_remote_code</code>. SafetyCard flags both before you download anything.</p></details> | |
| <details><summary>How can I tell if a model was uncensored or abliterated?</summary><p>Removing safety training tends to weaken the model's internal harmful-versus-harmless separation. SafetyCard measures that separation against the parent model and reports a difference only when it is clear. Below about 1B parameters the measurement is too noisy, and the card warns you.</p></details> | |
| <details><summary>Is this model really a fine-tune of the model it claims?</summary><p>The lineage check compares weights tensor by tensor against the declared base. An unrelated model stands out immediately.</p></details> | |
| <details><summary>Does SafetyCard detect hidden backdoors?</summary><p>No, and we will not claim it does. No public tool can currently guarantee that a model is backdoor-free. SafetyCard reports the evidence it can measure reliably.</p></details> | |
| <details><summary>Is it free?</summary><p>Yes. The instant scan runs in your browser, and deep scans run on your own hardware. It is open source under Apache-2.0.</p></details> | |
| <h2>Limitations</h2> | |
| <ul class="facts"> | |
| <li>A clean card is evidence, not a guarantee. It does not prove that a model is safe or free of hidden behaviours.</li> | |
| <li>Below about 1B parameters the safety-structure check is too noisy to compare models reliably.</li> | |
| <li>The safety-structure check needs a parent model as a reference point.</li> | |
| <li>The prompt sets are public, so a model trained to fool exactly this test could do so.</li> | |
| <li>Leaderboard cards are self-reported. Each one records the model revision and tool version so anyone can re-run it.</li> | |
| </ul> | |
| <footer>SafetyCard v0.1.0 · Apache-2.0 · <a href="https://huggingface.co/spaces/coralexbadea/SafetyCard/tree/main">Source code</a></footer> | |
| </main> | |
| <script src="scan.js"></script> | |
| <script> | |
| (function(){ | |
| const $ = (id) => document.getElementById(id); | |
| const esc = (s) => String(s == null ? "" : s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c])); | |
| const hfLink = (id) => `<a href="https://huggingface.co/${esc(id)}" target="_blank" rel="noopener">${esc(id)}</a>`; | |
| function render(x){ | |
| const facts = [ | |
| `Pickle files (can run code when loaded): ${x.pickle_files.length ? x.pickle_files.map(esc).join(", ") : "none"}`, | |
| `Needs <code>trust_remote_code</code>: ${x.needs_trust_remote_code ? "<b>yes</b>" : "no"}`, | |
| `Declared base model: ${x.declared_base_models.length ? x.declared_base_models.map(hfLink).join(", ") : "not declared"}`, | |
| `Hugging Face malware scan: ${x.hf_security_scan ? (x.hf_security_scan.files_with_issues.length ? "<b>issues in " + x.hf_security_scan.files_with_issues.map(esc).join(", ") + "</b>" : (x.hf_security_scan.scans_done ? "no issues reported" : "not finished")) : "not available"}`, | |
| `License: ${esc(x.license || "not declared")} · Size: ${x.total_size_gb} GB${x.gated ? " · gated" : ""}`, | |
| `Deep scan: ${x.deep_scan_supported ? "supported (safetensors, no remote code)" : "not supported for this repo"}` | |
| ]; | |
| if (x.note) facts.push(`<span class="muted">${esc(x.note)}</span>`); | |
| $("result").innerHTML = `<div class="grade"><div class="badge b${x.grade}" aria-label="grade ${x.grade}">${x.grade}</div> | |
| <div><b>File safety: grade ${x.grade}</b><br><span class="muted">${esc(x.grade_meaning)}</span></div></div> | |
| <ul class="facts">${facts.map((f) => `<li>${f}</li>`).join("")}</ul> | |
| <p class="muted" style="margin-top:10px">${hfLink(x.repo_id)} · revision <code>${esc((x.revision || "").slice(0,12))}</code>. A clean grade is evidence, not a guarantee.</p>`; | |
| } | |
| async function run(id){ | |
| $("go").disabled = true; $("result").innerHTML = '<p class="muted">Scanning…</p>'; | |
| try { render(await SafetyCardScan.scanRepo(id)); } | |
| catch (e) { $("result").innerHTML = `<p class="err">${esc(e.message)}</p>`; } | |
| finally { $("go").disabled = false; } | |
| } | |
| $("scanform").addEventListener("submit", (ev) => { ev.preventDefault(); run($("rid").value); }); | |
| document.querySelectorAll("[data-ex]").forEach((a) => a.addEventListener("click", () => { $("rid").value = a.dataset.ex; run(a.dataset.ex); })); | |
| const q = new URLSearchParams(location.search).get("model"); | |
| if (q) { $("rid").value = q; run(q); } | |
| function row(c){ | |
| const vs = c.vs_parent || ""; | |
| const cls = vs.startsWith("WEAKER") ? "weaker" : vs.startsWith("STRONGER") ? "stronger" : ""; | |
| const d = c.dprime == null ? "" : `${c.dprime} (${c.dprime_min}–${c.dprime_max})${c.small_model_warning ? " ⚠️ <1B" : ""}`; | |
| return `<tr><td>${hfLink(c.model)}</td><td><span class="tag">${esc(c.grade)}</span></td><td>${c.parent ? hfLink(c.parent) : ""}</td> | |
| <td>${esc(c.lineage || "")}</td><td class="num">${c.weight_change == null ? "" : c.weight_change}</td><td class="num">${d}</td><td class="${cls}">${esc(vs)}</td></tr>`; | |
| } | |
| function summarize(card, file){ | |
| const s = card.safety || {}, g = card.lineage || {}; | |
| return {file, model: card.model, grade: card.files.grade, parent: card.parent_model, lineage: g.verdict, | |
| weight_change: g.overall_rel_change, dprime: s.dprime_mean, dprime_min: s.dprime_min, dprime_max: s.dprime_max, | |
| vs_parent: s.vs_parent, small_model_warning: s.reliable_size === false}; | |
| } | |
| async function loadBoard(){ | |
| let rows = null; | |
| try { // live: list results/ via the Hub API, so merged pull requests show up without a rebuild | |
| const t = await fetch("https://huggingface.co/api/spaces/coralexbadea/SafetyCard/tree/main/results"); | |
| if (t.ok) { | |
| const files = (await t.json()).map((f) => f.path.split("/").pop()).filter((n) => n.endsWith(".json") && n !== "index.json"); | |
| rows = (await Promise.all(files.map(async (n) => { | |
| try { const r = await fetch("results/" + n); return r.ok ? summarize(await r.json(), n) : null; } catch (e) { return null; } | |
| }))).filter(Boolean); | |
| } | |
| } catch (e) { rows = null; } | |
| if (!rows || !rows.length) { try { rows = await (await fetch("results/index.json")).json(); } catch (e) { rows = []; } } | |
| rows.sort((a, b) => (b.dprime == null) - (a.dprime == null) || String(a.model).localeCompare(b.model)); | |
| document.querySelector("#lb tbody").innerHTML = rows.length ? rows.map(row).join("") : '<tr><td colspan="7" class="muted">No cards yet. Be the first!</td></tr>'; | |
| } | |
| loadBoard(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |