redact-personal-data

Replaces names, emails, phone numbers, street addresses, dates, card numbers, Social Security numbers, IP addresses and links in a text with markers before the text goes to a hosted AI model, a shared document or a knowledge base, and lists what it could not catch. Use when an agent is about to send customer emails, tickets, notes, reviews or transcripts to a model or a shared place, or when an owner asks to anonymise a file for testing.

Approved for client workInterns may use it on client work, through the gateway.· Version 1

Redact personal data

Strips personal details out of text before it goes anywhere it does not need them. The FTC's guide for business puts it simply: if there is no legitimate business need for sensitive personal information, don't keep it. A redacted copy is the default input for drafting, summarising and testing.

This is a pattern tool. It is reliable for things with a fixed shape and weak for everything else. Say so every time you hand over.

Inputs

  • The text file to redact (plain text, CSV or pasted email body).
  • A names list, one per line: the customers, staff and family members the business knows appear in the text. Patterns cannot find names; the list can.
  • What the redacted copy is for (a model prompt, a knowledge base article, test data). That decides how strict to be.

If the text comes from the owner's mailbox, read it only through the AHL Action Gateway (gmail.search, gmail.read). The agent holds an agent token, never a password, and stores no credentials.

Steps

  1. Run the script on a local copy:

    node scripts/redact.mjs examples/ticket.txt --names examples/names.txt
    

    It prints the redacted text, a --- line, and a count per kind. Compare with examples/expected-output.txt.

  2. Know what it catches (the markers):

    • [EMAIL] any address shaped x@y.tld
    • [URL] links starting http:// or https://
    • [SSN] 123-45-6789 shape only
    • [CARD] 13 to 19 digits, spaced or dashed, that pass the Luhn check
    • [PHONE] US 10-digit numbers with separators, optional +1
    • [IP] IPv4 addresses
    • [ADDRESS] a house number, up to three capitalised words and a common street suffix, with an optional apartment or unit
    • [DATE] MM/DD/YYYY and YYYY-MM-DD
    • [NAME] only names on the list, whole words, any case
  3. Read the output yourself for what it misses, and redact by hand:

    • names not on the list, nicknames, initials, handles and signatures
    • order, account, policy, member, licence and plate numbers without a fixed shape (the example's order number survives)
    • schools, employers, towns, and details that point to one person ("the only nurse on our street", "my son's school")
    • dates written in words ("March 3rd"), ages, birthdays
    • phone numbers with no separators or outside the US format
    • health, money, legal or family details written as ordinary sentences
    • anything inside images, PDFs or attachments: the script reads plain text only For health information, the HIPAA de-identification list in 45 CFR 164.514(b)(2) has eighteen kinds of identifier, including ZIP codes, all dates except the year, and full-face photos; this skill does not meet that standard alone. Use health-data-guard as well.
  4. Decide the leftovers. Replace each by hand with the same marker style ([ORDER], [SCHOOL], [PLACE]). Keep the meaning: "a customer in [PLACE] reports a leak" still works for drafting.

  5. Save the redacted copy beside the original with -redacted in the file name. Never overwrite the original; the business may need it.

Output

The redacted text, the per-kind counts, and a short list headed Not caught by patterns naming each item you redacted by hand and anything you left in on purpose, with the reason.

Edge cases

  • Re-identification by combination: town plus job plus date can identify someone even with every name gone. Generalise ("in September", "a tradesperson").
  • A card number or a secret appears: redact it, and tell the owner; see payment-card-guard and secrets-scan, because the original now needs cleaning too.
  • Children's details: use minors-data-guard before this skill.
  • False positives: part numbers that look like phone numbers will be marked. Restore them only if you are sure.

Never

  • Send unredacted text to a hosted model or a shared place to "check" it first.
  • Claim a file is anonymous because the script found nothing.
  • Keep the names list or the original in the agent's memory, logs or prompts after the job.
  • Put a password, token or key into the redacted copy, or into any file this skill writes.

Hand over

The person who owns the data reads the redacted copy against the original and the Not caught by patterns list before it leaves the business. They confirm the names list was complete, decide any item marked as left in, and delete working copies when the job is done.

The skill folder

Every file in the folder. Download each one into a folder with the same layout, or copy it.

redact-personal-data/
  SKILL.md
  examples/expected-output.txt
  examples/names.txt
  examples/ticket.txt
  scripts/redact.mjs
SKILL.md
---
name: redact-personal-data
description: Replaces names, emails, phone numbers, street addresses, dates, card numbers, Social Security numbers, IP addresses and links in a text with markers before the text goes to a hosted AI model, a shared document or a knowledge base, and lists what it could not catch. Use when an agent is about to send customer emails, tickets, notes, reviews or transcripts to a model or a shared place, or when an owner asks to anonymise a file for testing.
metadata:
  ahl-category: safety
  ahl-jobs: support, front-desk, reviews, training, compliance
  ahl-workflows: people--1948-it-services-ticket-notes-to-knowledge-base, front--1767-childcare-weekly-family-newsletter, operations--1503-hotel-reviews-to-fix-list, operations--2302-tutoring-session-notes-to-parent-update
  ahl-human-check: A person reads every redacted file before it leaves the business, and decides anything the patterns cannot judge.
  ahl-proposed-status: approved
  ahl-version: 1
  ahl-sources: |
    FTC — Protecting Personal Information: A Guide for Business — https://www.ftc.gov/business-guidance/resources/protecting-personal-information-guide-business — 2026-10-06
    Legal Information Institute (Cornell) — 45 CFR § 164.514 Other requirements relating to uses and disclosures of protected health information — https://www.law.cornell.edu/cfr/text/45/164.514 — 2026-10-06
---

# Redact personal data

Strips personal details out of text before it goes anywhere it does not need them. The FTC's guide for business puts it simply: if there is no legitimate business need for sensitive personal information, don't keep it. A redacted copy is the default input for drafting, summarising and testing.

This is a pattern tool. It is reliable for things with a fixed shape and weak for everything else. Say so every time you hand over.

## Inputs

- The text file to redact (plain text, CSV or pasted email body).
- A names list, one per line: the customers, staff and family members the business knows appear in the text. Patterns cannot find names; the list can.
- What the redacted copy is for (a model prompt, a knowledge base article, test data). That decides how strict to be.

If the text comes from the owner's mailbox, read it only through the AHL Action Gateway (`gmail.search`, `gmail.read`). The agent holds an agent token, never a password, and stores no credentials.

## Steps

1. **Run the script** on a local copy:

   ```bash
   node scripts/redact.mjs examples/ticket.txt --names examples/names.txt
   ```

   It prints the redacted text, a `---` line, and a count per kind. Compare with [examples/expected-output.txt](examples/expected-output.txt).

2. **Know what it catches** (the markers):
   - `[EMAIL]` any address shaped `x@y.tld`
   - `[URL]` links starting `http://` or `https://`
   - `[SSN]` `123-45-6789` shape only
   - `[CARD]` 13 to 19 digits, spaced or dashed, that pass the Luhn check
   - `[PHONE]` US 10-digit numbers with separators, optional `+1`
   - `[IP]` IPv4 addresses
   - `[ADDRESS]` a house number, up to three capitalised words and a common street suffix, with an optional apartment or unit
   - `[DATE]` `MM/DD/YYYY` and `YYYY-MM-DD`
   - `[NAME]` only names on the list, whole words, any case

3. **Read the output yourself** for what it misses, and redact by hand:
   - names not on the list, nicknames, initials, handles and signatures
   - order, account, policy, member, licence and plate numbers without a fixed shape (the example's order number survives)
   - schools, employers, towns, and details that point to one person ("the only nurse on our street", "my son's school")
   - dates written in words ("March 3rd"), ages, birthdays
   - phone numbers with no separators or outside the US format
   - health, money, legal or family details written as ordinary sentences
   - anything inside images, PDFs or attachments: the script reads plain text only
   For health information, the HIPAA de-identification list in 45 CFR 164.514(b)(2) has eighteen kinds of identifier, including ZIP codes, all dates except the year, and full-face photos; this skill does not meet that standard alone. Use `health-data-guard` as well.

4. **Decide the leftovers.** Replace each by hand with the same marker style (`[ORDER]`, `[SCHOOL]`, `[PLACE]`). Keep the meaning: "a customer in [PLACE] reports a leak" still works for drafting.

5. **Save** the redacted copy beside the original with `-redacted` in the file name. Never overwrite the original; the business may need it.

## Output

The redacted text, the per-kind counts, and a short list headed **Not caught by patterns** naming each item you redacted by hand and anything you left in on purpose, with the reason.

## Edge cases

- **Re-identification by combination**: town plus job plus date can identify someone even with every name gone. Generalise ("in September", "a tradesperson").
- **A card number or a secret appears**: redact it, and tell the owner; see `payment-card-guard` and `secrets-scan`, because the original now needs cleaning too.
- **Children's details**: use `minors-data-guard` before this skill.
- **False positives**: part numbers that look like phone numbers will be marked. Restore them only if you are sure.

## Never

- Send unredacted text to a hosted model or a shared place to "check" it first.
- Claim a file is anonymous because the script found nothing.
- Keep the names list or the original in the agent's memory, logs or prompts after the job.
- Put a password, token or key into the redacted copy, or into any file this skill writes.

## Hand over

The person who owns the data reads the redacted copy against the original and the **Not caught by patterns** list before it leaves the business. They confirm the names list was complete, decide any item marked as left in, and delete working copies when the job is done.
examples/expected-output.txt
Ticket 4471 (invented example, Lakeside Plumbing Supply)
From: [NAME] <[EMAIL]>
Phone: [PHONE]
Received: [DATE]

Hi, the faucet I bought on [DATE] leaks. Please send the replacement to
[ADDRESS]. My neighbour [NAME] has the same model.
I paid with card [CARD] and my order number is 1000234561.
My son's school is Hillcrest Elementary, if you need to reach me there.
Tracking page: [URL]
---
ADDRESS: 1
CARD: 1
DATE: 2
EMAIL: 1
NAME: 2
PHONE: 1
URL: 1
examples/names.txt
Jordan Mirelli
Jordan
Tess Okafor
examples/ticket.txt
Ticket 4471 (invented example, Lakeside Plumbing Supply)
From: Jordan Mirelli <jordan.mirelli@example.com>
Phone: (555) 014-2290
Received: 2026-09-14

Hi, the faucet I bought on 09/02/2026 leaks. Please send the replacement to
418 Birchwood Lane, Apt 3B. My neighbour Tess Okafor has the same model.
I paid with card 4111 1111 1111 1111 and my order number is 1000234561.
My son's school is Hillcrest Elementary, if you need to reach me there.
Tracking page: https://track.example.com/abc123
scripts/redact.mjs
#!/usr/bin/env node
// Replaces pattern-shaped personal data in a text file with markers.
// Usage: node scripts/redact.mjs <input.txt> [--names names.txt]
// Prints the redacted text, then a line "---", then a count per kind.
// Local files only; no network. Names are caught ONLY if listed in --names.
import fs from "node:fs";

const args = process.argv.slice(2);
const input = args[0];
if (!input || !fs.existsSync(input)) {
  console.error("Usage: node scripts/redact.mjs <input.txt> [--names names.txt]");
  process.exit(2);
}
let text = fs.readFileSync(input, "utf8").replace(/\r\n/g, "\n");
const namesIdx = args.indexOf("--names");
const names = namesIdx > -1 && fs.existsSync(args[namesIdx + 1])
  ? fs.readFileSync(args[namesIdx + 1], "utf8").split(/\r?\n/).map((n) => n.trim()).filter(Boolean)
  : [];

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;
}

const counts = {};
const hit = (kind) => { counts[kind] = (counts[kind] || 0) + 1; return `[${kind}]`; };

// Order matters: longer and more specific shapes first.
const rules = [
  ["EMAIL", /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g],
  ["URL", /\bhttps?:\/\/[^\s)>\]]+/g],
  ["SSN", /\b\d{3}-\d{2}-\d{4}\b/g],
  // 13-19 digits, optional single spaces or dashes; only redacted if it passes the Luhn check.
  ["CARD", /\b(?:\d[ -]?){12,18}\d\b/g, (m) => luhn(m.replace(/\D/g, ""))],
  ["PHONE", /(?:\+1[ .-]?)?\(?\b\d{3}\)?[ .-]?\d{3}[ .-]\d{4}\b/g],
  ["IP", /\b(?:\d{1,3}\.){3}\d{1,3}\b/g],
  // House number + up to three words + a common US street suffix.
  ["ADDRESS", /\b\d{1,6}\s+(?:[A-Z][a-z]+\s+){1,3}(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Boulevard|Ln|Lane|Dr|Drive|Ct|Court|Way|Pl|Place|Ter|Terrace|Cir|Circle)\b\.?(?:,?\s+(?:Apt|Unit|Suite|#)\s*\w+)?/g],
  ["DATE", /\b(?:\d{1,2}\/\d{1,2}\/\d{2,4}|\d{4}-\d{2}-\d{2})\b/g],
];

for (const [kind, re, ok] of rules) {
  text = text.replace(re, (m) => (ok && !ok(m) ? m : hit(kind)));
}
for (const name of names) {
  const re = new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "gi");
  text = text.replace(re, () => hit("NAME"));
}

process.stdout.write(text.endsWith("\n") ? text : text + "\n");
console.log("---");
const kinds = Object.keys(counts).sort();
if (!kinds.length) console.log("No pattern matches. Read the text yourself: names and free-text details are not caught by patterns.");
for (const k of kinds) console.log(`${k}: ${counts[k]}`);

Use it in Claude Code

Put the folder at .claude/skills/redact-personal-data/ in your project (or ~/.claude/skills/redact-personal-data/ to use it in every project). Claude Code finds SKILL.md there and uses the skill when a request matches its description.

Other agents that read the Agent Skills format take the same folder.

Sources