Mask names, emails, and phone numbers before pasting text into an AI tool
Remove or replace obvious personal data before sending text to an AI chat assistant, then review both the masked text and a detection report for anything the script missed. This tutorial uses a small synthetic example and keeps the original file unchanged.
Content checked 2026.09.21Example files included
Show contents
Who this is forFor people who want a practical pre-check before sending work text containing possible names, email addresses, or phone numbers to an AI assistant.
What you need
Python 3.12
A text editor and an AI chat assistant
01Start with a small synthetic message
This example is entirely synthetic. The names, email addresses, phone numbers, company details, and message content are invented for the tutorial. The purpose is to practice removing obvious personal data before text leaves your local workspace.
Save the following text as message.txt. It contains two names, two email addresses, two phone numbers, and one deliberately awkward reference that a simple masking rule will miss.
text
Project review notes
Alice Kim asked us to send the revised table to alice.kim@example.com.
If the file is delayed, call Alice at 010-1234-5678.
Bob Lee will review the second draft. His email is bob.lee@example.com and his office number is 02-345-6789.
The final approval should also be checked with A. Kim before Friday.
02Ask AI for a masking plan, not for the sensitive text
You can ask an AI assistant how to design masking rules without sending it the actual sensitive document. Describe the categories and use invented examples instead.
A useful answer should distinguish structured identifiers from unstructured language. Email addresses and phone numbers often have recognizable patterns. Human names are harder: ordinary words can be names, names can contain initials or multiple scripts, and the same person may be referenced in several forms.
03Convert the AI answer into explicit masking rules
For this synthetic example, use a small approved list for names and regular expressions for email addresses and phone numbers. This is intentionally narrower than a general personal-data detector.
Data type
Rule
Placeholder
Full name
Replace only exact approved names Alice Kim and Bob Lee
[NAME_1], [NAME_2]
Email
Match common address-like patterns containing a local part, @, and domain
[EMAIL_1], [EMAIL_2]
Phone
Match the two phone formats present in this example
[PHONE_1], [PHONE_2]
Other text
Leave unchanged for manual review
No automatic replacement
Consistent placeholders are useful because repeated references can remain understandable without retaining the original identifier. The script below gives the same placeholder to repeated occurrences of the same detected value.
04Apply the rules to a local copy
The following Python script reads message.txt, applies the approved rules, and writes results into a new outputs folder. It also writes a replacements.csv report containing every detected original value and its placeholder. It stops if outputs already exists and never overwrites message.txt.
python
import csv
import re
import sys
from pathlib import Path
INPUT = Path("message.txt")
OUTPUT_DIR = Path("outputs")
MASKED_FILE = OUTPUT_DIR / "message_masked.txt"
REPORT_FILE = OUTPUT_DIR / "replacements.csv"
KNOWN_NAMES = ["Alice Kim", "Bob Lee"]
EMAIL_RE = re.compile(
r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"
)
PHONE_RE = re.compile(
r"(?<!\d)(?:010-\d{4}-\d{4}|02-\d{3}-\d{4})(?!\d)"
)
def make_replacer(kind, mapping, rows):
def replace(match):
original = match.group(0)
if original not in mapping:
placeholder = f"[{kind}_{len(mapping) + 1}]"
mapping[original] = placeholder
rows.append(
{
"type": kind,
"original": original,
"replacement": placeholder,
}
)
return mapping[original]
return replace
def mask_known_names(text, mapping, rows):
for name in KNOWN_NAMES:
if name not in text:
continue
if name not in mapping:
placeholder = f"[NAME_{len(mapping) + 1}]"
mapping[name] = placeholder
rows.append(
{
"type": "NAME",
"original": name,
"replacement": placeholder,
}
)
text = text.replace(name, mapping[name])
return text
def main():
if not INPUT.is_file():
sys.exit(f"Input file not found: {INPUT}")
if OUTPUT_DIR.exists():
sys.exit(f"Stop: {OUTPUT_DIR} already exists. Review or rename it first.")
original = INPUT.read_text(encoding="utf-8")
rows = []
name_map = {}
email_map = {}
phone_map = {}
masked = mask_known_names(original, name_map, rows)
masked = EMAIL_RE.sub(make_replacer("EMAIL", email_map, rows), masked)
masked = PHONE_RE.sub(make_replacer("PHONE", phone_map, rows), masked)
OUTPUT_DIR.mkdir()
MASKED_FILE.write_text(masked, encoding="utf-8")
with REPORT_FILE.open("x", encoding="utf-8", newline="") as target:
writer = csv.DictWriter(
target,
fieldnames=["type", "original", "replacement"],
)
writer.writeheader()
writer.writerows(rows)
print(f"Unique names masked: {len(name_map)}")
print(f"Unique emails masked: {len(email_map)}")
print(f"Unique phones masked: {len(phone_map)}")
print(f"Masked copy: {MASKED_FILE}")
print(f"Replacement report: {REPORT_FILE}")
if __name__ == "__main__":
main()
Place mask_personal_data.py beside message.txt and run python mask_personal_data.py. For this exact example, the expected summary is 2 unique names, 2 unique emails, and 2 unique phone numbers masked.
05Check the replacements against the source
Work out the expected replacements before relying on the result. The script should record six unique detected values.
Type
Original
Replacement
NAME
Alice Kim
[NAME_1]
NAME
Bob Lee
[NAME_2]
EMAIL
alice.kim@example.com
[EMAIL_1]
EMAIL
bob.lee@example.com
[EMAIL_2]
PHONE
010-1234-5678
[PHONE_1]
PHONE
02-345-6789
[PHONE_2]
Alice Kim appears twice in the source but should use [NAME_1] both times. The report contains unique detected values rather than one row for every occurrence.
06Review what the automatic rules missed
Automatic replacement is only the first pass. Open outputs/message_masked.txt and read the entire result before pasting it anywhere. In this example, the last paragraph still contains A. Kim. The script cannot know from its approved list that this may refer to Alice Kim.
Search the masked file for @ and confirm that no email address remains.
Search for 010- and 02- and confirm that the two expected phone formats are gone.
Search for Alice Kim and Bob Lee and confirm that the approved full names are gone.
Read every line manually for initials, nicknames, signatures, addresses, employee IDs, account numbers, project-specific identifiers, or other identifying context.
Notice that A. Kim remains. Decide manually whether it identifies a person in the real document and mask it if necessary.
Compare replacements.csv with the source and confirm that each listed replacement corresponds to the intended text.
07Common mistakes and limits
Do not paste the original sensitive text into an AI tool merely to ask the AI what should be masked.
Do not assume a regular expression can reliably identify arbitrary human names.
Do not rely on one phone-number pattern if your source may contain spaces, country codes, extensions, parentheses, or other formats.
Do not delete the mapping report until you have finished checking the masked text, but protect that report because it contains the original identifiers.
Do not overwrite the source file. Keep the original separate from the masked copy.
Do not assume masking three categories removes confidential business information, credentials, financial data, health information, or other sensitive content.
For real workflows, define what must be removed based on the document, applicable policy, and purpose of the AI task. When possible, provide only the minimum text required for the task instead of masking a large document and sending the whole thing.
Execution and verification record
2026-09-21 · hand-checked example · Python 3.12
I checked the synthetic source text and expected replacements by hand.
The expected detection set contains 2 unique full names, 2 unique email addresses, and 2 unique phone numbers.
Alice Kim occurs twice but should consistently map to [NAME_1].
The deliberately abbreviated reference A. Kim is expected to remain unmasked and demonstrates a false negative.
The script reads message.txt and writes only outputs/message_masked.txt and outputs/replacements.csv.
The script stops if outputs already exists instead of replacing a previous review.
Verification limits
I did not execute the Python code; the small example and expected results were checked manually.
The full-name detection uses an explicit two-name list and is not a general named-entity recognition system.
The email regular expression covers common address-like forms but is not intended to implement every valid email syntax.
The phone regular expression covers only the two formats used in this synthetic example.
Masking obvious identifiers does not guarantee anonymization or compliance with any specific privacy law or organizational policy.
Turn “automate this” into an executable task description. Attach a synthetic sample with no sensitive data and a hand-checked expected result to complete a request for a script that totals work logs by team.
Check an aggregation function with four rows you can calculate by hand and 12 unit tests. Verify not only normal values but also empty input, zero, decimals, and invalid input.
Split a plausible summary into facts, calculations, and interpretations. Recalculate ratios and averages from synthetic monthly data, and rewrite sentences with missing sources or overstated causes into checkable statements.
Treat an AI-generated regex as a draft, not a finished rule. Build a small synthetic test table, compare expected and actual matches, revise the pattern, and save a review report before using it on real data.