AI Safety, Privacy and Responsible Use

RAG, AI Agents & MCP · Evaluation, safety and production · 14 min read · Updated

Protecting people when you use AI

  1. People trust AI features with real information, so protect them first.
  2. Send only the data the task needs, and remove names, numbers and IDs you can do without.
  3. The model answers. Remember that it can be wrong, biased or out of date.
  4. Reduce hallucinations: ground answers in real sources and check them before showing them.
  5. For decisions that affect people, such as hiring, health or money, a person must make the final call.

A resume contains some of the most personal information a person has: name, phone number, address, work history, sometimes identity numbers. When you build an AI feature on top of that data, you take on a duty. A mistake is not only a bug. It can expose someone's identity, unfairly reject a candidate or mislead a person who trusts your answer.

This lesson is practical. You will learn the main risks, build a redaction layer that removes personal data before it leaves your server, and learn a set of habits for responsible products. This is not legal advice. Laws differ by country and change, so for a real product, ask a qualified professional.

Risk 1: wrong answers that sound right

Hallucination is the most visible risk. The model produces a smooth answer that is not true. You cannot reduce it to zero, but you can reduce it a lot with layers:

  • Ground the answer. Use RAG and tell the model to answer only from the sources (lessons 13 and 16).
  • Allow “I don't know”. A bot that admits when it does not know is safer than one that always answers.
  • Cite and verify. Numbered citations that your code checks.
  • Test. An evaluation set with questions that have no answer (lesson 22).
  • Show uncertainty. For high-stakes advice, say plainly that the user should verify with a professional or the original source.
  • Keep a human in the loop for decisions that matter, like rejecting a job applicant.

Risk 2: personal data leaving your system

When you call an AI provider, your text travels to their servers. For a resume, that text may contain an email address, a phone number, a home address and ID numbers. Ask yourself a simple question about every piece of data: does the model need this to do the job? Almost never does it need the phone number to summarise a work history.

The best protection is data minimisation: do not send what you do not need. The technique is called redaction. Before the text leaves your server, you replace personal data with placeholders like [EMAIL_1]. The model works with the placeholders. When the answer comes back, your server puts the real values back, only for the user who owns them. The model never saw the real values.

// Remove personal data from text BEFORE it leaves your server for an AI provider.
// This is a first line of defence. Patterns miss things, so also tell users not to paste secrets.

const luhn = (digits) => {
  let sum = 0;
  let alt = false;
  for (let i = digits.length - 1; i >= 0; i--) {
    let n = Number(digits[i]);
    if (alt) { n *= 2; if (n > 9) n -= 9; }
    sum += n;
    alt = !alt;
  }
  return sum % 10 === 0;
};

// Order matters: longer, more specific patterns run first.
// (?<!\d[ -]?) and (?![ -]?\d) mean "not part of a longer number", so we never cut a number in half.
const RULES = [
  { type: 'EMAIL', re: /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi },
  { type: 'PAN', re: /\b[A-Z]{5}[0-9]{4}[A-Z]\b/g },
  { type: 'CARD', re: /(?<!\d[ -]?)\d(?:[ -]?\d){12,18}(?![ -]?\d)/g, check: (m) => luhn(m.replace(/\D/g, '')) },
  { type: 'AADHAAR', re: /(?<!\d[ -]?)[2-9]\d{3}[ -]?\d{4}[ -]?\d{4}(?![ -]?\d)/g },
  { type: 'PHONE', re: /(?<!\d[ -]?)(?:\+\d{1,3}[ -]?)?(?:\(?\d{2,5}\)?[ -]?)?\d{3,5}[ -]?\d{4,5}(?![ -]?\d)/g, check: (m) => m.replace(/\D/g, '').length >= 10 },
];

// Returns { text, found, map }. map lets you put the real values back into the reply for the user.
export function redact(input) {
  let text = String(input);
  const map = {};
  const counts = {};
  for (const { type, re, check } of RULES) {
    text = text.replace(re, (m) => {
      if (check && !check(m)) return m;
      counts[type] = (counts[type] || 0) + 1;
      const token = `[${type}_${counts[type]}]`;
      map[token] = m;
      return token;
    });
  }
  return { text, found: Object.entries(counts).map(([type, count]) => ({ type, count })), map };
}

export const restore = (text, map) => Object.entries(map).reduce((t, [token, value]) => t.split(token).join(value), String(text));

Let us look at the details, because they teach a lot.

  • Order matters. Longer and more specific patterns run first. A card number is checked before a phone number, otherwise part of a card number could be caught as a phone number.
  • Checksums reduce false alarms. A 16-digit number is only treated as a card number if it passes the Luhn check, the checksum that real card numbers pass. A random 16-digit order number is left alone.
  • Do not cut a number in half. The patterns use lookbehind and lookahead ((?<!\d[ -]?) and (?![ -]?\d)) so that they only match a whole number, not a part of a longer one.
  • Indian identity numbers. The lab recognises the PAN format (five letters, four digits, one letter) and 12-digit Aadhaar-style numbers. Add the patterns that matter for your users and countries.

Check your provider and your promises

  • Read the provider's data terms. Do they keep your data? For how long? Is it used to train models? Business and API terms often differ from consumer apps, so read the ones that apply to you.
  • Match your privacy policy to reality. Do not say “we never share your data” if your text goes to an AI provider. Tell users that AI tools send their text to a provider.
  • Keep as little as you can. Do not store prompts and answers forever. Set a retention time and delete after it.
  • Do not log personal data. Log counts and timings, not the user's text.
  • Let users delete. When a user deletes an account, delete the resume text, the vectors and any memory the AI saved.

Risk 3: unfairness

AI that helps with hiring touches people's livelihoods, so fairness is not optional. Models learn from human writing, and that writing contains bias. A model that scores resumes might favour certain names, schools, genders, ages or places without anyone intending it.

  • Do not use sensitive traits (gender, age, religion, caste, marital status, health) as inputs for scoring or ranking. Redact them if the task does not need them.
  • Test for differences. Run the same resume with different names or details and see whether the output changes in a way it should not.
  • Keep a person in charge of decisions that affect people. Use AI to help, explain and suggest, not to silently reject.
  • Be able to explain. Show why a score or suggestion was given, for example which skills matched.

Also keep in mind the law. Many countries have data protection rules, for example the GDPR in Europe and the Digital Personal Data Protection Act, 2023 in India, and some rules treat AI used in hiring as a higher-risk activity (the EU AI Act is an example). The details change, so check the current rules and take professional advice before you launch a product that makes or influences hiring decisions.

Risk 4: misuse and unsafe content

If your app lets users type anything, someone will try to misuse it: to make the model write harmful content, to reveal its instructions, or to send spam. Defend with layers: a clear system prompt, input length limits, rate limits per user, output checks for your use case, and a way to report problems. Also remember lesson 21: never let model output trigger a risky action without a rule in code.

A short checklist before you launch an AI feature

  1. What data goes to the model? Is each field necessary? Redact the rest.
  2. Where does that data go? Have you read the provider's terms and updated your privacy policy?
  3. How long do you keep prompts, answers and memory? Can users delete them?
  4. What is the worst wrong answer? Is there a human check for high-stakes cases?
  5. Have you tested questions with no answer, injection attempts and sensitive data?
  6. Could the feature treat groups of people unfairly? How would you notice?
  7. What will you do when something goes wrong? Who can switch the feature off?

Your task: build the privacy page

Build a redaction function, a free preview endpoint and a summary endpoint that never sends personal data to the model.

What you will build

  • server/privacy.js with a redact function (rules for email, PAN, card, Aadhaar-style and phone numbers) and a restore function.
  • Routes POST /api/privacy/preview (no AI call) and POST /api/privacy/summarise (redacts, calls Claude, restores).
  • An Angular page that shows exactly what the model would see and which types of data were found.

Steps

  1. Write the rules in a list. Each rule has a type, a regular expression and an optional check function. Put the card rule before the Aadhaar and phone rules and add the Luhn check.
  2. Write redact. For each rule, replace each match with a token like [EMAIL_1]. Keep a map from token to real value and count each type.
  3. Write restore. Replace every token in a text with its real value.
  4. Write the preview route. Cut the text to 8,000 characters and return the redacted text and the found types.
  5. Write the summarise route. Redact first, send only the redacted text, then restore the values in the answer. Return the text that was sent so that the page can show it.
  6. Test with a resume that has an email, a phone number, a PAN, an Aadhaar-style number and a card number. Also test numbers that must be left alone, like years and a 16-digit number that is not a card.

Hints

  • Never print or log the map of real values.
  • Keep the lookbehind and lookahead in the number patterns so that numbers are not cut in half.
  • Make the page show the redacted text next to the summary so a user can see what was protected.

You know it works when the preview shows placeholders instead of personal data, the model never receives the real values, the summary contains the real email again for the user, and ordinary numbers like years are untouched.

Solution

You have read privacy.js above. Here are the route and the Angular page.

import { Router } from 'express';
import { client, MODEL, textOf } from '../claude.js';
import { redact, restore } from '../privacy.js';

const router = Router();

const clean = (v) => String(v || '').trim().slice(0, 8000);

// POST /api/privacy/preview  { text }  ->  { redacted, found }   (no AI call, free)
router.post('/privacy/preview', (req, res) => {
  const text = clean(req.body?.text);
  if (!text) return res.status(400).json({ error: { message: 'Please paste some text.' } });
  const { text: redacted, found } = redact(text);
  res.json({ redacted, found });
});

// POST /api/privacy/summarise  { text }  ->  { summary, sentToModel, found }
router.post('/privacy/summarise', async (req, res) => {
  const text = clean(req.body?.text);
  if (!text) return res.status(400).json({ error: { message: 'Please paste some text.' } });

  const { text: safe, found, map } = redact(text);
  const reply = await client.messages.create({
    model: MODEL,
    max_tokens: 400,
    system: 'Summarise the resume text in 3 short bullet points. Keep placeholders like [EMAIL_1] exactly as written.',
    messages: [{ role: 'user', content: `<resume>\n${safe}\n</resume>` }],
  });

  // The model never saw the real values. We put them back for the user only.
  res.json({ summary: restore(textOf(reply), map), sentToModel: safe, found });
});

export default router;
import { Component, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { errorText } from '../util';

type Found = { type: string; count: number }[];

@Component({
  selector: 'app-privacy',
  imports: [FormsModule],
  template: `
    <h1>Privacy: redact before you send</h1>
    <p class="hint">Personal data is replaced with placeholders on the server before any text reaches the AI. Preview is free and makes no AI call.</p>

    <div class="card">
      <label for="t">Resume text</label>
      <textarea id="t" [(ngModel)]="text" style="min-height:190px"></textarea>
      <button (click)="preview()" [disabled]="busy() || !text.trim()">Preview what would be sent</button>
      <button class="secondary" (click)="summarise()" [disabled]="busy() || !text.trim()">Summarise with Claude</button>
      @if (error()) { <div class="err">{{ error() }}</div> }
    </div>

    @if (sent()) {
      <div class="card">
        <h2>What the model sees</h2>
        <pre class="out">{{ sent() }}</pre>
        <div class="chips">@for (f of found(); track f.type) { <span>{{ f.type }} x{{ f.count }}</span> } @empty { <p class="muted">Nothing sensitive found.</p> }</div>
      </div>
    }
    @if (summary()) {
      <div class="card"><h2>Summary (real values restored for you)</h2><div class="answer">{{ summary() }}</div></div>
    }
  `,
})
export class PrivacyPage {
  private http = inject(HttpClient);

  text = `Asha Verma, Accounts Assistant\nEmail: asha.verma@example.com  Phone: +91 98765 43210\nPAN: ABCDE1234F  Aadhaar: 2345 6789 0123\n\nThree years of invoice entry in Tally for ABC Traders. Reduced payment delays by 20%.`;
  busy = signal(false);
  error = signal('');
  sent = signal('');
  summary = signal('');
  found = signal<Found>([]);

  async preview() {
    this.busy.set(true);
    this.error.set('');
    this.summary.set('');
    try {
      const r = await firstValueFrom(this.http.post<{ redacted: string; found: Found }>('/api/privacy/preview', { text: this.text }));
      this.sent.set(r.redacted);
      this.found.set(r.found);
    } catch (e) {
      this.error.set(errorText(e));
    } finally {
      this.busy.set(false);
    }
  }

  async summarise() {
    this.busy.set(true);
    this.error.set('');
    try {
      const r = await firstValueFrom(this.http.post<{ summary: string; sentToModel: string; found: Found }>('/api/privacy/summarise', { text: this.text }));
      this.summary.set(r.summary);
      this.sent.set(r.sentToModel);
      this.found.set(r.found);
    } catch (e) {
      this.error.set(errorText(e));
    } finally {
      this.busy.set(false);
    }
  }
}

What you learned

  • Reduce hallucinations in layers: grounding, “I don't know”, citations, tests and human review.
  • Minimise data: redact personal data before it reaches an AI provider, and restore it only for the owner.
  • Read your provider's terms, keep your privacy policy true, keep data only as long as needed and let users delete it.
  • Treat fairness and law as design requirements, especially when AI touches hiring.

In the last technical lesson you will make your AI feature reliable and affordable in production.

Download the solution (ai-lab-lesson-23.zip)

Frequently asked questions

How do I stop personal data from reaching an AI provider?

Redact it on your server before the call. Replace emails, phone numbers and ID numbers with placeholders, send only the redacted text and put the real values back in the answer for the user. Also read the provider's data terms.

What is the Luhn check?

It is a checksum that valid payment card numbers pass. Using it lets you tell a real card number from a random long number, so you redact fewer false matches.

Can AI be used for hiring decisions?

It can help, but it is risky. Avoid using sensitive traits, test for unfair differences, keep a person responsible for decisions, and check the laws that apply in your country. This is not legal advice.

Is redaction with regular expressions enough?

It is a good first layer, but it misses names, addresses and unusual formats. Combine it with user guidance, stronger detection where needed and careful choice of providers.