Last October I pasted a payment callback log into a 200-person engineering chat group, just to ask why signature verification kept failing. Three minutes later someone messaged me privately: "The log you posted has four real mobile numbers, an ID number and an internal IP address. Your call." I recalled it immediately, but people had already taken screenshots. The incident went through the company's internal process in the end, and we could not fully rule out that user data had been seen outside. Since then I have had one hard rule: any text that leaves my screen gets cleaned locally first.
1. A leak is almost never a breach, it is one careless paste
Going back over that log, the phone numbers were not the only thing I posted. These are the leak points that are easiest to overlook:
AuthorizationandCookiein the request headers. Many troubleshooting screenshots capture them along with everything else, which publishes the session token.mobile=13800001234in a URL query string. It is easier to miss than a number in the body because it looks like part of the configuration.- SQL fragments, especially a full
INSERT INTO user_order (...)statement, which exposes every column value. - An order number combined with a shipping address and a name. Together those three pinpoint one person, even if each field alone does not look sensitive.
- Test accounts are often an employee's own mobile number. Once written into a repository or a screenshot, it cannot be taken back.
- Other people can run OCR on numbers in a screenshot, so redaction has to happen on the image itself, not just in the text.
2. Why keep the format instead of blacking out the whole line
Blacking everything out looks safest, but then a colleague cannot use it to check which order it was, and you end up reading the original value to them over another channel, which defeats the whole point. The usual approach keeps the digits that identify the record and hides the digits that identify the person. My rules:
| Type | Masking rule | After masking |
|---|---|---|
| Mobile number (11 digits) | Keep the first 3 and the last 4, hide the middle 4 | 138****5678 |
| ID number (18 digits) | Keep the first 3 and the last 4, hide the middle 11 | 110***********1234 |
| Bank card number | Keep only the last 4 | **** **** **** 1234 |
| Email address | Keep the first character before the @ and the full domain | z***@example.com |
| IP address | Keep the first two octets, hide the last two | 192.168.*.* |
The ID row matters most: digits 7 to 14 are exactly the date of birth, so they must fall inside the masked range. Masking only the final check digit is no masking at all. Hiding the middle four digits of a mobile number still leaves ten thousand possibilities; with a name and a city that is not absolutely safe, but it is an order of magnitude weaker than the full number.
3. My three-step process, with a reverse check at the end
- Run the text through Privacy Masking first. Paste it in and it masks mobile numbers, ID numbers, bank cards, emails and IP addresses by pattern. You can change the mask character to
#or a custom symbol, and the whole block is handled in one pass before you copy it out. Everything runs in the browser, so the content never leaves this machine. That is why I am willing to run production logs through it. - Images are a separate matter. Chat histories and admin screenshots have to be redacted at the pixel level, so I use Image Mosaic to select sensitive areas. You can draw several blocks in a row, and push the grain to 40px or more before sending anything out. Doing the same area twice is less hassle than trying to get it perfect in one pass.
- Before sending, do a reverse check. Paste the result into Text Extractor and scan for mobile numbers, ID numbers, emails and IPs one category at a time; the list should come back empty. I added this step after that incident, because when you read three long log lines by eye it is easy to miss the one number hidden in a URL.
The reverse check has a natural limit that needs stating: these tools match digit strings by pattern. They do not recognise Chinese names, shipping addresses, store names or order numbers. So step three catches numbers that were left unmasked, not text that is identifiable by meaning, and the last pass still has to be done by a person.
4. Practices that look like redaction but are not
- Base64 or URL encoding: fully reversible and keyless, so it is just an envelope around the original value. Being called out for this in a review is the worst trade you can make.
- Keeping only the first and last digit: it looks safe, but if the name and birth date are also present, that much of the number is enough to shrink the candidate set to something very small.
- Inconsistent masking width: three digits on one line and five on the next lets a reader infer the original length and prefix range from the count alone.
- Deleting by hand: removing the number but leaving the spaces and alignment around the
"phone":key still hints at the range. For anything at scale, use a tool, not the Delete key. - Treating masking as a one-off screenshot task: the reliable approach replaces values at the logging layer and masks uniformly before output, rather than patching them when someone copies a log during troubleshooting.
One piece of compliance wording is easy to mix up: the Personal Information Protection Law treats de-identification and anonymisation separately. De-identified data that can still identify a specific person without extra information remains personal information; anonymisation requires that identification be impossible and irreversible. Keeping the last four digits, as in the table above, is the former. Publishing externally and showing internally are therefore two different things, and "I already masked it" is not a reason to post masked output in a public community.
5. Common questions
Q: What is the difference between masking and encryption?
Encryption has a key and is reversible; its purpose is protection in storage and in transit. Masking is usually an irreversible redaction whose purpose is to limit exposure when something is displayed. The two do not conflict: logs should be encrypted at rest, and screenshots should be masked before they are sent.
Q: Can a message that has already been sent be fixed?
A recall is visible only to you; whatever someone already copied or screenshotted is gone. What you can do is assess the exposure. Can the number still be used to log in, does the person involved need to be told, and should a card tied to a payment be replaced? Start that process as early as possible.
Q: Can asterisks be reversed?
No. Masking is one-way. Keep the original data yourself and use the masked output only for sharing. Do not store it in a database as a reversible intermediate format.
Q: Can masked data be used directly as test data?
For number fields, yes, but note that usable and consistent are different things: unique constraints and foreign keys will collide when values are masked in bulk. A real test database should be built with a batch masking script, not by pasting fragments.
Q: Is the content uploaded anywhere?
I only pick tools that run entirely in the browser, where text and images never leave the device. With production data that matters more than speed, and it is worth making it a precondition when choosing a tool.
Looking back at that private message, the lucky part was that the same person noticed it first. Masking costs about twenty extra seconds after a copy and paste, and it saves a two-week investigation. That maths works in any direction, as long as you make it a fixed step in the process instead of something you remember at the last minute.