Topic 9.7
Privacy and Personal Data
In one line
Whatever the user types may leave the browser, reach your server, a model provider and your logs. Decide what is sent, remove personal data that is not needed before it leaves, ask for consent, keep only what you must for as short a time as you can, and never write prompts to logs carelessly.
Think of it like this
Posting a letter. Before it goes in the box you decide what goes in the envelope, whether to black out the account number, and who is allowed to read it on the way. Once posted, you cannot take it back. Every message to an AI feature is a letter that passes several hands, so the thinking has to happen before it is sent.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Personal data
- Information about an identifiable person: name, email, phone, address, card number, order history.
- Data minimisation
- Collecting and sending only what the task needs.
- Redaction
- Replacing sensitive parts of a text with a label before it is used or stored.
- Luhn check
- A checksum all payment card numbers pass. It separates card numbers from other long digit strings.
- Consent
- The user's informed, clear agreement to a use of their data, which they can refuse.
- Retention
- How long data is kept before it is deleted.
- Metadata
- Data about the data: time, length, status. It is not the content itself.
- Canary marker
- A made-up unique string you type on purpose and then search for, to find where text ends up.
Step by step
01Redact the obvious shapes
The first example turns free text into safer text: emails become [email], phone numbers [phone], card numbers [card]. It returns counts so you can tell the user what was removed. Card candidates are only redacted if they pass the Luhn check, which is why the 16-digit order-like number in the test stays.
All numbers here are made up. 4111 1111 1111 1111 is a well-known test card number that is not tied to any account.
function luhn(digits) {
let sum = 0;
for (let i = 0; i < digits.length; i++) {
let d = Number(digits[digits.length - 1 - i]);
if (i % 2 === 1) { d *= 2; if (d > 9) d -= 9; }
sum += d;
}
return sum % 10 === 0;
}02Show the user what was removed
Silent redaction confuses people ("why did Mitra ignore my number?"). Show a short note under the message: "We hid 1 email and 1 card number before sending." It also teaches users not to paste such data in the first place.
03Log facts, not words
The second example is a logging helper. Given a chat turn it writes a record with an id, a day number, the message length, the number of redactions and the outcome. The text is never in the record. When consent is off, nothing is stored at all, and the answer still works.
04Delete on schedule
A purge function receives the records and today's day number and removes anything older than the retention limit. The example prints what stayed and what went, including the edge case of a record exactly on the limit. In production it runs from a scheduled job, and a test runs it against a fixed clock like this one.
05Plant a marker and look for it
Last, the example types a made-up marker into a message, passes it through a stand-in redaction step, saves the turn, and then searches the record store and the log lines for the marker. It must appear in neither. In your own test suite the same search also covers the provider call recorder and the error reports, and you run it on every change.
Try it yourself
- 1
Log the words
In the second example, change the
lines.push(...)call to also include" text=" + text. Run it.Log line 2 now holds the whole Hindi sentence, although history was not saved for that user. The log became the storage they declined.
- 2
Skip the checksum
In the first example, change the card branch condition to
if (digits.length >= 13 && digits.length <= 19)(noluhn).The long order number is now replaced by
[card]. A rule that blanks ordinary numbers teaches users to ignore the note.
Code & diagrams
Every value is made up (the card number is a public test number). Patterns find shapes, not meaning: names and street addresses pass straight through, so redaction lowers risk and does not remove it.
Preview
Console
Nothing logged yet.Expected console output
"Mail me at [email] about ORD-1042" | hidden: 1 email
"Call [phone] please" | hidden: 1 phone
"My card is [card], charge it" | hidden: 1 card
"Order number 1234 5678 9012 3456 is late" | hidden: nothing
"मेरा नंबर [phone] है और ईमेल [email]" | hidden: 1 email, 1 phone
"" | hidden: nothingHindi text and an emoji are counted as characters with the spread operator (not UTF-16 units). The raw text held the planted marker, but only the redacted text was saved, so the search finds nothing, which is the result you want from your real test.
Preview
Console
Nothing logged yet.Expected console output
saved
history not saved (no consent)
saved
purge on day 130: kept a1,a4, removed 1
log line 2: chat id=a2 day=110 chars=21 redactions=0 outcome=ok
raw text had the marker: true
marker found in stored data or logs: falseBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Treat every long number as a card
In the first example, change the card branch to if (digits.length >= 13 && digits.length <= 19) { counts.card++; return "[card]"; }, then run it.
Myth vs fact
Myth
Redaction by pattern removes all personal data.
Fact
It finds common shapes. Names, addresses and free-text descriptions pass through. Send less data and tell users what is sent.
Myth
Logs are internal, so printing prompts is fine.
Fact
Logs are copied to vendors, kept for months and read by many people. Treat them as a data store with the same rules.
Myth
If the user agreed once, we may keep everything.
Fact
Consent should be specific and revocable, and you still keep only what you need for as long as you need it.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Prefer redaction on the server as well as the browser. The browser check helps the user (they see the note), and the server check is the real protection because it covers every client. Do both, and share the patterns from one module so they cannot drift apart.
- ▸
Check what third-party scripts record. Session-replay and analytics tools can capture what is typed or displayed in the chat. Mask inputs, add the chat container to the tool's exclusion list, and test with a marker.
- ▸
Make deletion real: when a user deletes a conversation, remove it from the database, caches, search indexes and backups on your retention schedule, and say so in plain words. Soft-delete flags that keep the text forever are a privacy bug.
Remember this
- 1
The problem this solves: People paste emails, order details, phone numbers and card numbers into chat boxes, because it is easy. That text then travels to your server, to a model provider, and often into your logs, analytics and error reports. Each is a copy that can leak, be requested by a regulator, or outlive the reason it was collected. This is OWASP's LLM02, Sensitive Information Disclosure, seen from the frontend.
- 2
Map where the text goes. For one question: (1) the browser (memory, maybe
localStoragefor drafts); (2) your server's request logs; (3) the model provider, who may keep requests for a period under their terms; (4) your database if you save chats; (5) analytics and error-reporting tools that capture inputs or page text; (6) other users, if answers are cached or shared. Draw this map for your product. Every box is a place to protect and a reason to send less. - 3
Send less (data minimisation). The best protection is not sending the data. Does the model need the customer's email address to explain the return policy? Send an order id, not the full record. Send the last four digits of a card, never the number. Choose what goes into the prompt in code, field by field, instead of dumping a whole database row.
- 4
Redact before sending. A redaction step in the client or server finds personal data in free text and replaces it with a label such as
[email]. Patterns catch obvious shapes: email addresses, phone numbers and card numbers (check card candidates with the Luhn check, a simple checksum that real card numbers pass, so order numbers do not get blanked by mistake). Redaction by pattern is imperfect. It misses names and addresses, so show the user a note, and treat it as reducing risk, not removing it. - 5
Ask first (consent). Tell users clearly, before their first message, that text goes to an AI service, and link to a plain-language page. Make optional things optional: saving history, using chats to improve the product. If consent is off, the feature should still work; you simply store nothing. The screen should say which mode the user is in.
- 6
Retention: keep it short. Decide how long each kind of data lives, write it down and delete on schedule. Keep metadata (time, length, outcome, token counts) longer than content (the text), because metadata answers most operational questions without holding private words. A scheduled job that deletes rows older than the limit is a feature to test, not a promise.
- 7
Logs are the quiet leak. Developers print requests while debugging, and the print stays. Rules: never log message bodies, full URLs with query strings, headers with keys, or model replies in production; log ids, sizes, timings and outcomes instead. Check error reporters and analytics too: configure them to mask inputs and skip the chat area. A prompt in a log file is a copy nobody planned to protect.
- 8
Detect and verify. Detect: search your logs and database in a test environment for a planted marker such as
PLANTED-4111-CANARY; if a marker you typed into the chat appears anywhere it should not, you have found a leak. Verify: a test sends a message with fake personal data, then asserts that the provider call, the log and the stored record contain only redacted or no content. The examples below show each step with obviously fake data.
Explain it without notes
Name the places a user's message can be copied after they press send.
Why is the Luhn check useful when redacting card numbers?
What belongs in a production log line for a chat turn and what does not?
How would you test that personal data does not leak into logs?
Practice
Add a rule to the first example that replaces any 6-digit code that follows the word OTP with [otp], and add a sample for it.
Change purge in the second example so it removes records with consent: false immediately, whatever their age. Why is the check also worth having if saveTurn already skips them?
Trade-offs
- ↔
More redaction means safer data and answers that lose context (the model cannot see the email it was asked about). Redact what the task does not need, and keep what it does, in a considered way.
- ↔
Short retention reduces risk and removes data you might want for debugging. Keep metadata longer than content to get most of the debugging value.
Done when you can
I can draw the map of where one chat message is copied.
I can redact emails, phone numbers and card numbers, and explain what pattern redaction misses.
I can tell the user what was hidden before sending.
I can write log lines that hold facts about a turn and not its words.
I can describe a retention rule and a test that deletes on schedule.
I can plant a marker and check where it ends up.