PageSourceSearch

https://drift-ai.org/assets/romy-privacy/privacy-layer.mjs

js drift-ai.org collected 2026-10-09 22:53:56 UTC 4,296 bytes, 35 lines download raw bytes

1// Shared browser/server layer. No network, logs, persistent state or reverse map.
2// Complements the reviewed baseline; it cannot promise complete anonymisation.
3import * as baseline from './privacy.mjs';
4export const VERSION='2026-09-25-minimisation-2';
5const omitted='[Sensitive details omitted. Please describe the immigration question without sensitive personal details.]';
6export function normalise(text){return String(text||'').normalize('NFKC').replace(/[\u200B-\u200D\u2060\uFEFF\u202A-\u202E\u2066-\u2069]/g,'')}
7export function emailIn(text){return baseline.emailIn(normalise(text))}
8// Match personal disclosures, not topic words: general sourced questions and
9// requests for a specialist must remain usable. This is still best-effort.
10const personalIdentity=/\b(?:i am|i['’]m|ich bin|my (?:wife|husband|partner|child|son|daughter) is|mein(?:e)? (?:frau|mann|partner(?:in)?|kind|sohn|tochter) ist)\s+(?:(?:a|an|ein|eine)\s+)?(?:muslim(?:a|in|isch)?|christian|christ(?:in|lich)?|jewish|jude|jüdin|jüdisch|hindu|buddhist(?:in|isch)?|sikh|atheist(?:in|isch)?|gay|lesbian|lesbisch|schwul|bisexual|bisexuell|homosexual|homosexuell|queer|transgender|transsexuell)\b/iu;
11const personalHealth=/\b(?:(?:i (?:take|use|inject|am taking)|i['’]m taking)\s+(?:(?:prescribed|daily)\s+)?insulin|ich (?:nehme|spritze|benutze)\s+(?:täglich\s+)?insulin|(?:i have|i['’]ve got|i suffer from|ich habe|ich leide an)\s+(?:diabetes|epilepsy|epilepsie|depression|ptsd|ptbs))\b/iu;
12export function sensitive(text){const t=normalise(text);return baseline.sensitive(t)||personalIdentity.test(t)||personalHealth.test(t)||/\b(?:i am|i['’]m|ich bin)\s+(?:pregnant|schwanger|hiv.positive)|\b(?:bei mir wurde|my diagnosis|meine diagnose|meine religion|my religion is|my sexual orientation)\b/i.test(t)}
13function cardNumber(s){const digits=s.replace(/\D/g,'');if(digits.length<13||digits.length>19)return false;let sum=0,twice=false;for(let i=digits.length-1;i>=0;i--){let n=Number(digits[i]);if(twice){n*=2;if(n>9)n-=9}sum+=n;twice=!twice}return sum%10===0}
14export function redact(text){let t=normalise(text)
15 .replace(/[\p{L}\p{N}._%+-]+@[\p{L}\p{N}.-]+\.[\p{L}]{2,}/gu,'[email withheld]')
16 .replace(/\b[\w.+-]+\s*(?:\[at\]|\(at\)|\sat\s)\s*[\w-]+(?:\s*(?:\[dot\]|\(dot\)|\sdot\s)\s*[\w-]+|\.[\w-]+)+\b/gi,'[email withheld]')
17 .replace(/\b(?:date of birth|dob|born on|geburtsdatum|geboren am)\s*(?:is|ist|:)?\s*(?:\d{4}-\d{1,2}-\d{1,2}|\d{1,2}[./-]\d{1,2}[./-]\d{2,4}|\d{1,2}\.?\s+[\p{L}]+\s+\d{4}|[\p{L}]+\s+\d{1,2},?\s+\d{4})/giu,'[date of birth withheld]')
18 .replace(/\b(?:full name|first name|last name|surname|name|vorname|nachname)\s*:\s*[\p{L}'’-]+(?:[ \t]+[\p{L}'’-]+){0,2}/giu,'[name withheld]')
19 .replace(/\b(?:document (?:number|id)|national id|identity card(?: number)?|personalausweis(?:nummer)?|versicherungsnummer|sozialversicherungsnummer|steueridentifikationsnummer|eAT(?: number| nummer)?)\s*(?:is|ist|:|#)?\s*[A-Z0-9][A-Z0-9/-]{4,}\b/gi,'[identifier withheld]')
20 .replace(/\b\d{1,5}\s+[\p{L}'’-]+(?:\s+[\p{L}'’-]+){0,3}\s+(?:street|road|avenue|lane|drive|st\.?|rd\.?)\b(?:[ ,]+(?:apt|apartment|flat|unit)\.?\s*[\w-]+)?/giu,'[street address withheld]')
21 .replace(/\b0(?:1[5-7]\d|[2-9]\d)[\d ()/-]{5,}\d\b/g,'[phone withheld]')
22 .replace(/\b(?:\d[ -]?){12,18}\d\b/g,m=>cardNumber(m)?'[payment card withheld]':m)
23 .replace(/\b(?:password|passwort|api[ _-]?key|access[ _-]?token)\s*[:=]\s*[^\s,;]+/gi,'[secret withheld]')
24 .replace(/\bBearer\s+[A-Za-z0-9._~+/-]{12,}=*/g,'[access token withheld]');
25 return baseline.redact(t);
26}
27export function safeInput(text){return sensitive(text)?omitted:redact(text)}
28export function safeFacts(facts){return Object.fromEntries(Object.entries(baseline.safeFacts(facts)).map(([k,v])=>[k,typeof v==='string'?safeInput(v):v]))}
29export function browserMessage(text,phase){
30 if(sensitive(text))return {blocked:true};
31 // Only an explicitly requested, standalone contact address crosses unmasked.
32 // The server encrypts this separately and removes it from transcripts/models.
33 const contact=['email','delay_email','queued','human'].includes(phase)&&text.length<=254&&/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text);
34 return {blocked:false,message:contact?normalise(text):safeInput(text),display:contact?'[Contact email withheld]':safeInput(text),contact};
35}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.