pii-pattern-scanner / index.html
aitrainingdatabroker's picture
Space
68ffeb5 verified
Raw History Blame Contribute Delete
5.25 kB
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>PII Pattern Scanner</title><meta name="description" content="Paste text to highlight patterns that look like personal or secret details. It only finds patterns; names and free-form details need a model or a person."><style>
:root{--navy:#14213d;--yellow:#f5c518;--ink:#1d2433;--muted:#5b6475;--line:#d9dde5;--bg:#fff;--soft:#f4f6f9}
@media (prefers-color-scheme:dark){:root{--ink:#e8ebf2;--muted:#9aa3b5;--line:#2c3547;--bg:#10151f;--soft:#182030}}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif}
main{max-width:860px;margin:0 auto;padding:28px 16px 48px}h1{margin:0 0 6px;font-size:1.7rem}h2{font-size:1.1rem;margin:22px 0 8px}
p{margin:0 0 12px}.note{color:var(--muted);font-size:.95rem}label{display:block;font-weight:600;margin:16px 0 6px}
input,select,textarea{width:100%;padding:10px;border:1px solid var(--line);border-radius:6px;font:inherit;background:var(--bg);color:var(--ink)}
textarea{min-height:140px;resize:vertical}input:focus,select:focus,textarea:focus,button:focus-visible{outline:3px solid var(--yellow);outline-offset:1px}
button{font:inherit;border:1px solid var(--ink);border-radius:6px;padding:8px 14px;cursor:pointer;background:var(--bg);color:var(--ink)}
button.primary{background:var(--navy);color:#fff;border-color:var(--navy)}
.cols{display:grid;grid-template-columns:1fr 1fr;gap:14px}@media (max-width:640px){.cols{grid-template-columns:1fr}}
.card{background:var(--soft);border:1px solid var(--line);border-radius:8px;padding:12px 14px;margin:0 0 10px}.card b{display:block}
.tag{display:inline-block;font-size:.8rem;border:1px solid var(--line);border-radius:99px;padding:1px 8px;margin-right:6px;color:var(--muted)}
mark{background:var(--yellow);color:#14213d;border-radius:3px;padding:0 2px}.out{white-space:pre-wrap;word-wrap:anywhere}
.links{margin-top:28px;padding-top:14px;border-top:1px solid var(--line)}.links a{display:block;margin:4px 0}
a{color:#1d4ed8}@media (prefers-color-scheme:dark){a{color:#8ab4ff}}footer{margin-top:24px;color:var(--muted);font-size:.9rem}
</style></head>
<body><main>
<h1>PII Pattern Scanner</h1>
<p class="note">Paste text to highlight patterns that look like personal or secret details. It only finds patterns; names and free-form details need a model or a person. Runs in your browser; what you type is not sent anywhere.</p>
<label for="t">Text</label><textarea id="t"></textarea><p><button class="primary" id="b">Scan</button> <button id="c">Mask matches</button></p><div id="o"></div>
<div class="links"><b>Read more on AITrainingDataBroker.com</b><a href="https://aitrainingdatabroker.com/data/team-chat/?utm_source=huggingface&amp;utm_medium=space&amp;utm_campaign=pii-pattern-scanner" rel="noopener">Can you license team chat to AI companies?</a><a href="https://aitrainingdatabroker.com/data/quickbooks-data-export/?utm_source=huggingface&amp;utm_medium=space&amp;utm_campaign=pii-pattern-scanner" rel="noopener">QuickBooks data export for AI licensing</a><a href="https://aitrainingdatabroker.com/de-identification/?utm_source=huggingface&amp;utm_medium=space&amp;utm_campaign=pii-pattern-scanner" rel="noopener">De-identification checklists</a></div>
<footer>By <a href="https://aitrainingdatabroker.com/?utm_source=huggingface&amp;utm_medium=space&amp;utm_campaign=pii-pattern-scanner" rel="noopener">AI Training Data Broker</a>. General information, not legal advice and not a certification.</footer>
</main>
<script>
const esc = s => String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const P=[['EMAIL',/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi],['PHONE',/(?:\+?\d{1,2}[ .-]?)?(?:\(\d{3}\)|\d{3})[ .-]?\d{3}[ .-]?\d{4}/g],['SSN_LIKE',/\b\d{3}-\d{2}-\d{4}\b/g],
['IP_ADDRESS',/\b(?:\d{1,3}\.){3}\d{1,3}\b/g],['KEY_LIKE',/\b(?:sk|pk|key|token|demo_key)[_-][A-Za-z0-9_-]{12,}\b/g],['CARD_LIKE',/\b(?:\d[ -]?){13,19}\b/g]];
function luhn(s){const d=s.replace(/\D/g,'');if(d.length<13||d.length>19)return false;let t=0,a=false;for(let i=d.length-1;i>=0;i--){let n=+d[i];if(a){n*=2;if(n>9)n-=9}t+=n;a=!a}return t%10===0}
function find(t){const f=[];for(const [k,re] of P){for(const m of t.matchAll(re)){if(k==='CARD_LIKE'&&!luhn(m[0]))continue;f.push({k,s:m.index,e:m.index+m[0].length})}}
f.sort((a,b)=>a.s-b.s||b.e-a.e);const o=[];let end=-1;for(const x of f){if(x.s>=end){o.push(x);end=x.e}}return o}
function render(mask){const t=document.getElementById('t').value;const f=find(t);let out='',p=0;
for(const x of f){out+=esc(t.slice(p,x.s))+(mask?`<mark>[${x.k}]</mark>`:`<mark title="${x.k}">${esc(t.slice(x.s,x.e))}</mark>`);p=x.e}out+=esc(t.slice(p));
const c={};f.forEach(x=>c[x.k]=(c[x.k]||0)+1);document.getElementById('o').innerHTML=`<div class="card out">${out||'Nothing pasted.'}</div><p>${f.length?Object.entries(c).map(([k,v])=>`<span class="tag">${k} ${v}</span>`).join(''):'No pattern matched. Names and free-form details are not covered.'}</p>`}
document.getElementById('b').onclick=()=>render(false);document.getElementById('c').onclick=()=>render(true);
</script></body></html>