SafetyCard / index.html
coralexbadea's picture
SafetyCard v0.1.0: free safety report card for Hugging Face LLMs
e69a1c5 verified
Raw History Blame Contribute Delete
17.6 kB
<!doctype html>
<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) => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[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>