Kontaktní PII uniká přes veřejné volnotextové bio (a admin ho nevidí kvůli jazykovému fallbacku)
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Dvojitý a na první pohled rozporuplný report od klienta:
- „Autor má na veřejném profilu zobrazené telefonní číslo.”
- „Ale ve vnitřním systému ani v administraci jeho telefon nikde nevidíme — není jasné, odkud se načítá.”
Vypadá to jako záhada (odkud se bere číslo, které v DB ‚není’), ale jde o jednu příčinu.
Root cause
Sekce “Root cause”Dvě věci dohromady:
(a) Únik přes volnotext. Profil má strukturované pole phone (soukromé, nikde se veřejně nevykresluje) a volnotextové „O autorovi” (bio). Autor napsal kontakt přímo do bia: "Mgr. Jan Novák 777xxxxxx". Veřejná stránka bio vykresluje → telefon je venku. Strukturované phone s tím nesouvisí.
(b) Proč ho admin „nevidí”. Bio má jazykové varianty bio_cs, bio_en, bio_sk, …. Veřejná stránka bere bio přes fallback pickBio(lang → en → cs). Autor měl bio_cs prázdné, ale bio_en/bio_sk vyplněné kontaktem → veřejně se zobrazí bio_en. Admin editor přitom ukazoval jen phone a bio_cs (prázdné). Výsledek: kontakt je veřejně vidět, ale v adminu „nikde” — protože admin čte jiné pole / jinou jazykovou variantu než návštěvník.
Fix
Sekce “Fix”// lib: strip kontaktů z volnotextu — pracuje jen nad TEXTOVÝMI uzly HTML,// atributy (img rozměry, iframe src) nechává býtconst EMAIL = /[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}/gi;// telefon KONZERVATIVNĚ: jen +CC/00CC číslo, nebo holých 9 číslic v 3-3-3.// NIKDY tečka jako oddělovač (tečky používají DATA — "5. 9. - 15. 10. 2018").const PHONE = /(?:\+|00)\d{1,3}(?:[\s\-/]?\d){8,12}|\b\d{3}[\s\-/]?\d{3}[\s\-/]?\d{3}\b/g;
function stripContact(html: string) { let s = html.replace(/<a[^>]*href=["']?\s*(?:mailto|tel):[^>]*>[\s\S]*?<\/a>/gi, ''); return s.replace(/(<[^>]+>)|([^<]+)/g, (_m, tag, text) => tag ? tag : text.replace(EMAIL, '').replace(PHONE, r => r.replace(/\D/g,'').length>=9 ? '' : r));}Aplikuj na VŠECH zápisových cestách daného pole (self-edit, admin edit, edit „za uživatele”), ne jen na jedné. Strip se dělá při uložení (i do všech jazykových variant + legacy zrcadla). Existující data: projeď stejnou funkcí jako jednorázovou migraci (záloha → transformace → cílené UPDATE jen změněných sloupců → re-scan na 0).
Jak se tomu vyvarovat v jiných systémech
Sekce “Jak se tomu vyvarovat v jiných systémech”- Detection: projeď všechna veřejně renderovaná volnotextová pole regexem na e-mail + telefon. Pozor na jazykové varianty — scanuj VŠECHNY (
bio_*), ne jen default. Cross-check: co vidí veřejná stránka (přes fallback) vs. co vidí admin editor — musí to být totéž pole. - Anti-pattern: sanitizace jen „at render” na jednom místě (jiný renderer / export / API ji obejde); admin náhled čte default jazyk, veřejná stránka jiný přes fallback; spoléhat, že kontakt bude jen ve strukturovaném poli.
- Telefon-regex past: příliš hladový detektor sní DATA. Artist bio je plné výstav („5. 9. – 15. 10. 2018”) a rozpětí let („2013–2014”). NIKDY neber tečku jako oddělovač telefonu a měj práh tvaru (3-3-3 / +CC), ne jen „≥9 číslic”. Vždy ověř detektor na reálném vzorku PŘED hromadným stripem.
- Lepší přístup: policy „komunikace jen přes platformu” vynucuj při zápisu (server-side strip) + uživateli to viditelně oznam („kontakt jsme z bia odstranili”), ne potichu. Admin a veřejná stránka musí renderovat z téhož zdroje pravdy.
Sister bugs / související
Sekce “Sister bugs / související”Rodina „admin vidí jiná data než uživatel” (jazykový fallback, cache, jiný renderer) — viz [[silent-constraint-rejection-error-as-value]]. A „hladový číselný regex” — viz [[greedy-regex-eats-digit-in-prefix-split]] (tam sežral číslici telefonu, tady by sežral datum).