Marketplaces have a recurring problem: buyers and sellers who meet on the platform want to take the conversation, and the transaction, off it. The quickest way is to share a phone number in chat. I’ve built a masking step for exactly this, and the interesting lessons weren’t about regular expressions.
Mask on the way in, not on the way out
The first instinct is to mask in the UI: store the message as written, hide the number when it’s rendered. It’s the wrong layer. The raw message still reaches the database, the logs, the analytics export, the search index, the support tool, and every backup. Each of those is a place the data now lives and a place it can leak from.
Mask at ingestion — between “message received” and “message persisted” — so that the rest of the system never sees the raw value. A small serverless function is a good fit: it’s stateless, it scales with message volume, and it sits on the one path every message must take.
People are creative
Detection starts with patterns for phone numbers and emails, and immediately meets users who don’t want to be detected:
- spacing and punctuation:
0300 123 4567,0300-1234567,0 3 0 0 … - digits as words, sometimes mixed:
zero three double-oh one two three - other scripts: Arabic-Indic digits look nothing like
0–9to a naive pattern - splitting across messages: the first half now, the second half next
The approach that holds up is to normalise first, then detect: fold whitespace and punctuation, map number words and other-script digits onto 0–9, and run detection on the normalised text while masking the corresponding spans in the original. Keep a short window of recent messages per conversation so split numbers are caught when the second half arrives.
False positives are a product problem
Order numbers, prices and quantities are digits too. Mask them and you break legitimate conversations, and users will find the work-around faster than you can patch it. Use context: length and prefix rules for the regions you serve, nearby keywords, and an allow-list for the platform’s own identifier formats. Measure the false-positive rate on real (already-masked) traffic and review samples by hand. Treat it as a classifier, because it is one.
Decide what happens to the original
If you need the original — for fraud investigations or legal holds — store it encrypted, in a separate store with its own access control and audit log, and an expiry. If you don’t need it, don’t keep it. “We might want it later” is how a masking feature turns into a breach report.
Make the masking itself observable
Count masks by type and by surface, and alert on sudden changes: a drop to zero means detection broke; a spike means either a new evasion trick or a new false-positive pattern. Never log the matched text to debug it. Log the type, the position and a hash.
The general rule is bigger than chat: personal data should be handled at the edge of the system and never travel further than it must. Every hop downstream is a place you’ll have to clean up later.