PII & PHI Detection and Redaction in AI Pipelines — with AWS Bedrock Guardrails
> July 1, 2025
AI pipelines process enormous volumes of text — support tickets, medical notes, contracts, HR records. That text almost always contains sensitive personal data. Getting the handling wrong is not a configuration mistake; it is a legal and ethical failure. This post explains why PII and PHI must be treated with care, how detection and redaction work in general, and where AWS Bedrock Guardrails fits inside the RAG architecture established in this series.
// What Are PII and PHI?
PII (Personally Identifiable Information) is any data that can be used — alone or in combination — to identify a specific individual.
┌──────────────────────────────────────────────────────┐
│ COMMON PII TYPES │
│ │
│ Direct identifiers Quasi-identifiers │
│ ───────────────── ──────────────────── │
│ • Full name • ZIP code │
│ • National ID / SSN • Date of birth │
│ • Passport number • Gender │
│ • Driver's licence • Employer name │
│ • Email address • Job title │
│ • Phone number • Neighbourhood │
│ • Home address • Ethnicity │
│ • IP address │
│ • Device / cookie ID │
└──────────────────────────────────────────────────────┘
A quasi-identifier looks harmless on its own, but three or four of them together can uniquely pinpoint a person in a dataset. That combination risk is why PII handling cannot be reduced to a blocklist of obvious fields.
PHI (Protected Health Information) is a subset of PII defined under HIPAA in the United States. It is PII that is linked to a health condition, treatment, or payment.
┌──────────────────────────────────────────────────────┐
│ PHI = PII + health linkage │
│ │
│ PII alone: "Jane Smith, 04/1985, Boston" │
│ │ │
│ + health linkage │
│ │ │
│ PHI: "Jane Smith was admitted for │
│ chemotherapy on 2024-03-01" │
│ │
│ PHI covers: diagnoses, medications, lab results, │
│ insurance IDs, appointment dates, provider names │
└──────────────────────────────────────────────────────┘
// Why PII and PHI Must Be Protected
> Legal and regulatory obligations
Regulations mandate specific safeguards. Ignoring them is not an option:
┌──────────────────────────────────────────────────────────────────┐
│ REGULATORY LANDSCAPE │
│ │
│ Regulation Region Core requirement │
│ ────────── ────────── ────────────────────────────────── │
│ GDPR EU / UK Minimise collection; right to │
│ erasure; breach notification 72 h │
│ HIPAA United States Encrypt PHI at rest and in transit; │
│ audit access; business associate │
│ agreements for vendors │
│ CCPA / CPRA California Right to know, delete, opt out │
│ PIPEDA Canada Consent-based collection │
│ PDPA Thailand/SG Lawful basis for processing │
└──────────────────────────────────────────────────────────────────┘
> The AI-specific risk surface
Traditional software touched PII in structured database records. AI systems introduce new attack surfaces:
┌───────────────────────────────────────────────────────────────┐
│ WHERE PII LEAKS IN AN AI PIPELINE │
│ │
│ 1. SOURCE DOCUMENTS │
│ PDFs, emails, and notes ingested into the vector store │
│ may contain names, IDs, and diagnoses in plain text │
│ │
│ 2. USER QUERIES │
│ Users often paste PII into their own questions: │
│ "Is John Doe (DOB 12/1990) eligible for the plan?" │
│ │
│ 3. RETRIEVED CONTEXT │
│ The retrieval step surfaces verbatim document chunks │
│ that may include sensitive fields │
│ │
│ 4. LLM OUTPUT │
│ The model may echo, summarise, or extrapolate PII │
│ from context it was given │
│ │
│ 5. LOGS AND TRACES │
│ Prompt/response logs stored for debugging often │
│ capture the full unredacted conversation │
└───────────────────────────────────────────────────────────────┘
Each of these surfaces requires a different mitigation. A guardrail on the output alone is not enough if logs capture raw prompts.
> Ethical responsibility beyond compliance
Regulations set a floor, not a ceiling. Users of AI systems have a reasonable expectation that:
- their health data will not be surfaced to other users through retrieval collisions
- a chatbot will not repeat their phone number or address back unnecessarily
- sensitive information shared in confidence will not appear in model outputs seen by third parties
Compliance gets you out of court. Ethics keeps you trusted.
// Detection — How Do You Find PII in Text?
There are four broad approaches, and production systems typically combine several.
> Approach 1 — Pattern matching (regex and rule sets)
Regular expressions catch PII that has a fixed format: SSNs, phone numbers, credit card numbers, email addresses. The advantage is speed and zero infrastructure. The disadvantage is brittleness — patterns vary by country, and free-text names or diagnoses cannot be regex-matched.
┌─────────────────────────────────────────────────────┐
│ PATTERN MATCHING │
│ │
│ Input: "Call me at 555-867-5309 or │
│ email kate@example.com" │
│ │
│ Rule: PHONE ─▶ \d{3}-\d{3}-\d{4} ─▶ MATCH │
│ Rule: EMAIL ─▶ \S+@\S+\.\S+ ─▶ MATCH │
│ Rule: NAME ─▶ ??? ─▶ NO MATCH │
│ │
│ Good for: structured PII with fixed formats │
│ Weak for: names, free-text diagnoses, addresses │
└─────────────────────────────────────────────────────┘
> Approach 2 — Named Entity Recognition (NER)
NER is a classical NLP technique. A model is trained to label spans of text as PERSON, LOCATION, ORGANISATION, DATE, and so on. It handles names and addresses that regex cannot.
┌─────────────────────────────────────────────────────┐
│ NAMED ENTITY RECOGNITION │
│ │
│ Input: "Dr. Sarah Lee prescribed metformin │
│ to the patient in Austin on Monday" │
│ │
│ [PERSON] → Dr. Sarah Lee │
│ [MEDICATION] → metformin (medical NER model) │
│ [LOCATION] → Austin │
│ [DATE] → Monday │
│ │
│ Good for: names, places, organisations │
│ Weak for: implicit PII ("the patient in room 4") │
└─────────────────────────────────────────────────────┘
Popular open-source options: spaCy (general NER), medspaCy and AWS Comprehend Medical (clinical NER), Microsoft Presidio (PII-focused, pluggable).
> Approach 3 — Machine learning classifiers
Beyond span-level NER, transformer-based classifiers can tag entire sentences or paragraphs as sensitive or non-sensitive. They learn from labelled examples in your specific domain and can catch contextual PII that neither regex nor generic NER recognises.
┌─────────────────────────────────────────────────────┐
│ ML CLASSIFIER APPROACH │
│ │
│ Training data: │
│ ┌──────────────────────────────┬────────┐ │
│ │ "Patient admitted for..." │ PHI │ │
│ │ "The quarterly revenue..." │ SAFE │ │
│ │ "Please call John at..." │ PII │ │
│ └──────────────────────────────┴────────┘ │
│ │ │
│ ▼ fine-tune │
│ transformer model │
│ │ │
│ ▼ │
│ New text ──▶ [PHI / PII / SAFE] label │
│ │
│ Good for: domain-specific sensitivity signals │
│ Cost: requires labelled data and model hosting │
└─────────────────────────────────────────────────────┘
> Approach 4 — Managed cloud services (AWS Comprehend, Bedrock Guardrails)
Cloud providers now offer pre-trained PII detection as a managed API. No model to train or host. You send text, get back entity labels and offsets.
AWS Comprehend has a DetectPiiEntities endpoint. AWS Bedrock Guardrails integrates directly into the inference path and can both detect and redact in a single call, as shown later.
┌─────────────────────────────────────────────────────┐
│ MANAGED SERVICE APPROACH │
│ │
│ Your text ──▶ API call ──▶ managed model │
│ │ │
│ ▼ │
│ list of entities with type + offset │
│ e.g. [NAME at 0-8, SSN at 22-33] │
│ │
│ Good for: fast integration, no MLOps │
│ Trade-off: data leaves your process boundary │
│ (check data processing agreements) │
└─────────────────────────────────────────────────────┘
// Redaction Strategies — What Do You Do After Detection?
Detection tells you where sensitive data lives. Redaction is the action you take. The right strategy depends on whether the data needs to remain usable downstream.
┌──────────────────────────────────────────────────────────────────┐
│ REDACTION STRATEGY SPECTRUM │
│ │
│ Most reversible ◀─────────────────────────────▶ Most private │
│ │
│ TOKENISE PSEUDONYMISE ANONYMISE / REDACT │
│ ──────── ──────────── ───────────────────── │
│ Replace with Replace with Replace with type │
│ a lookup key a consistent placeholder or │
│ stored in a fake value blank entirely │
│ vault (same fake name (no mapping kept) │
│ each time for │
│ Can recover the same real one) Cannot recover │
│ original later Supports analytics Original is gone │
│ │
│ Example: Example: Example: │
│ "John" → TK-0042 "John" → "Alex P." "John" → [NAME] │
└──────────────────────────────────────────────────────────────────┘
> Choosing a strategy
- Tokenisation suits cases where the system needs to act on the identity later — e.g. personalised responses after de-anonymisation at delivery time.
- Pseudonymisation suits analytics over data that must remain internally consistent — counting how many times the same patient appears — without exposing the real name.
- Anonymisation / redaction suits RAG output: the user just needs a safe answer; the real value adds no value to the response.
// The Two Surfaces in a RAG Pipeline
PII can enter the pipeline at two very different points, and each needs a different defence.
┌──────────────────────────────────────────────────────────────────┐
│ TWO SURFACES TO PROTECT │
│ │
│ SURFACE A — DATA AT REST (ingestion time) │
│ ────────────────────────────────────────── │
│ Source documents uploaded to S3 may already contain PII. │
│ If ingested as-is, those values are embedded into the vector │
│ store and can be retrieved verbatim in any future query. │
│ │
│ Mitigation: pre-process documents before S3 upload, │
│ or run a redaction step inside the Lambda that triggers │
│ the Knowledge Base sync. │
│ │
│ SURFACE B — DATA IN MOTION (inference time) │
│ ────────────────────────────────────────── │
│ The user's query and the LLM's answer both travel through │
│ the inference layer. PII in the query may be echoed back; │
│ PII retrieved from context may be surfaced in the answer. │
│ │
│ Mitigation: Bedrock Guardrails applied to both the input │
│ and output of every model call. │
└──────────────────────────────────────────────────────────────────┘
// The Architecture — Where Each Layer Lives
┌────────────────────────────────────────────────────────────────────┐
│ END-TO-END ARCHITECTURE │
│ │
│ ┌──────────────┐ │
│ │ Source docs │──upload──▶ [PRE-PROCESS / REDACT at rest] │
│ └──────────────┘ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ S3 Bucket │ │
│ │ (clean docs)│ │
│ └──────┬──────┘ │
│ │ S3 event │
│ ▼ │
│ ┌─────────────┐ │
│ │ Lambda │ │
│ │ (start sync)│ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Bedrock KB │ │
│ │+ OpenSearch │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────────────┐ question │ │
│ │ User │─────────────▶ ┌───────┴──────────────────────┐ │
│ └──────────────┘ │ LangGraph │ │
│ │ ROUTE ─▶ RETRIEVE ─▶ │ │
│ │ GENERATE │ │
│ └───────────────┬──────────────┘ │
│ │ raw answer │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ Bedrock Guardrails │ │
│ │ [in-motion redaction] │ │
│ └────────────┬───────────┘ │
│ │ safe answer │
│ ┌──────────────┐ ▼ │
│ │ User │◀────────────────────────────── │
│ └──────────────┘ │
└────────────────────────────────────────────────────────────────────┘
// Phase 1 — Ingestion: S3 + Bedrock Knowledge Base
AWS Bedrock Knowledge Base is a fully managed service that handles chunking, embedding, and vector indexing. You point it at an S3 bucket, press sync, and it maintains a vector store backed by OpenSearch Serverless. No infra to operate.
> Creating the Knowledge Base (console walkthrough)
- Open Amazon Bedrock → Knowledge Bases → Create knowledge base
- Give it a name and an IAM role with S3 read and OpenSearch access
- Choose Amazon S3 as the data source and point to your bucket
- Select Titan Embeddings v2 as the embedding model
- Let Bedrock provision an OpenSearch Serverless collection automatically
- Click Sync — Bedrock crawls S3, chunks every document, and indexes vectors
> Keeping the KB in sync automatically
┌────────────────────────────────────────────────────┐
│ AUTO-SYNC PATTERN │
│ │
│ New file lands in S3 │
│ │ │
│ ▼ │
│ S3 Event notification │
│ │ │
│ ▼ │
│ EventBridge Rule │
│ │ │
│ ▼ │
│ Lambda function │
│ │ │
│ ▼ │
│ bedrock-agent.start_ingestion_job( │
│ knowledgeBaseId, dataSourceId ) │
│ │
│ Bedrock re-crawls, re-embeds, re-indexes │
└────────────────────────────────────────────────────┘
The sync call is a single API invocation. Bedrock handles the rest.
// Phase 2 — Retrieval: LangChain Graph
LangChain's LangGraph library lets you define a state machine where each node is a function. Three nodes are all that is needed:
┌────────────────────────────────────────────────────────┐
│ LANGGRAPH NODES │
│ │
│ ┌─────────────┐ │
│ user query──▶│ ROUTE │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ │ needs KB? │ general question? │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ RETRIEVE │ │ GENERATE │ │
│ │ (Bedrock KB)│ │ (direct LLM)│ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ └────────────┬───────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ GENERATE │◀── retrieved context │
│ │ (Claude 3) │ │
│ └─────────────┘ │
└────────────────────────────────────────────────────────┘
Each node is a small function that receives a shared state dictionary and returns updates to it. The graph compiles into a runnable that accepts a user query and produces an answer.
ROUTE inspects the query and decides whether the Knowledge Base is needed. A simple keyword check or a small LLM call to classify the intent both work here.
RETRIEVE calls the Bedrock KB retrieval API with the user's query and returns the top-k document chunks as context.
GENERATE passes the query and any retrieved context to Claude and returns the raw answer. This raw answer then flows into the guardrail layer.
// Phase 3 — Safety: Bedrock Guardrails (PII Redaction)
Bedrock Guardrails is the in-motion protection layer. It is applied as a configuration attached to every model inference call — both to the incoming prompt and to the outgoing answer.
> What Guardrails detect and redact
┌───────────────────────────────────────────────────────────────┐
│ BEDROCK GUARDRAILS — PII ENTITY TYPES │
│ │
│ General PII Healthcare PII (HIPAA-relevant) │
│ ─────────── ─────────────────────────── │
│ NAME MEDICAL_RECORD_NUMBER │
│ EMAIL HEALTH_INSURANCE_BENEFICIARY │
│ PHONE DEVICE_ID (e.g. pacemaker S/N) │
│ ADDRESS CERTIFICATE_LICENSE_NUMBER │
│ SSN / TAX_ID VEHICLE_IDENTIFICATION_NUMBER │
│ CREDIT_DEBIT_NUMBER IP_ADDRESS │
│ DATE_OF_BIRTH URL │
│ DRIVER_ID / PASSPORT_NUMBER AWS_ACCESS_KEY │
└───────────────────────────────────────────────────────────────┘
> How redaction works inside a Guardrail call
┌──────────────────────────────────────────────────────────────────┐
│ GUARDRAIL REDACTION FLOW │
│ │
│ Raw LLM output: │
│ "Jane's SSN is 123-45-6789, her email is jane@clinic.org, │
│ she visited Dr. Patel on 04/12/2024" │
│ │ │
│ ▼ │
│ Bedrock Guardrail (server-side) │
│ │
│ Detect: NAME → "Jane" (offset 0–3) │
│ SSN → "123-45-6789" (offset 14–24) │
│ EMAIL → "jane@clinic.org"(offset 40–54) │
│ NAME → "Dr. Patel" (offset 67–75) │
│ DATE → "04/12/2024" (offset 79–88) │
│ │ │
│ Action: ANONYMIZE → replace each span with [TYPE] │
│ │ │
│ ▼ │
│ Redacted output: │
│ "[NAME]'s SSN is [SSN], her email is [EMAIL], │
│ she visited [NAME] on [DATE]" │
└──────────────────────────────────────────────────────────────────┘
The action can be set to ANONYMIZE (replace with type label) or BLOCK (refuse to answer entirely). Anonymize is generally preferred for RAG assistants — the answer remains useful while the sensitive value is removed.
> Setting up a Guardrail (console)
- Open Amazon Bedrock → Guardrails → Create guardrail
- Under Sensitive information filters, enable the PII entity types you need (NAME, EMAIL, SSN, PHONE, ADDRESS, DATE_OF_BIRTH, and so on)
- Set the action to Anonymize
- Optionally enable input filtering to also redact PII from the user's own query before it reaches the model
- Save and note the
guardrailIdandguardrailVersion
> Attaching a Guardrail to an inference call (pseudo-code)
call bedrock converse API with:
model = "claude-3-sonnet"
messages = [ user_prompt ]
guardrailConfig = {
guardrailIdentifier: YOUR_GUARDRAIL_ID
guardrailVersion: "DRAFT" or version number
trace: enabled ← logs what was detected
}
Bedrock applies the guardrail to both the input and output.
The response object contains the redacted answer.
No PII appears in the returned text.
Enabling the trace mode returns a guardrailTrace object in the response that lists every entity detected and every action taken — useful for audit logging.
// Handling Logs — The Hidden Surface
Most data-breach analysis finds that the leak was not in the product itself but in an observability system that captured raw conversations. At every phase, ensure:
┌──────────────────────────────────────────────────────────────┐
│ LOGGING CHECKLIST │
│ │
│ Layer What to log What NOT to log │
│ ───── ──────────── ──────────────── │
│ API gateway request ID, latency raw query text │
│ LangGraph nodes node name, duration state dict contents │
│ Bedrock inference model ID, token cnt prompt or response │
│ Guardrail trace entity types found actual entity value │
│ S3 access object key, time file content │
└──────────────────────────────────────────────────────────────┘
The guardrail trace gives you entity types (e.g. "SSN detected at offset 14") without logging the values. That is the right balance for compliance audit trails.
// Comparison of Detection Approaches
┌───────────────────────────────────────────────────────────────────┐
│ DETECTION APPROACH COMPARISON │
│ │
│ Approach Strengths Weaknesses │
│ ────────────── ────────────────── ───────────────────────── │
│ Regex / rules Fast, no infra, Format-dependent, misses │
│ deterministic names, context-blind │
│ │
│ NER (spaCy / Handles names and Needs domain fine-tuning, │
│ Presidio) locations, open must be hosted and scaled │
│ source │
│ │
│ ML classifier Domain-aware, Requires labelled data, │
│ catches implicit model versioning overhead │
│ sensitivity │
│ │
│ Managed API Zero MLOps, scales Data leaves your process; │
│ (Comprehend / automatically, check BAA / DPA with AWS │
│ Guardrails) HIPAA-eligible for PHI workloads │
└───────────────────────────────────────────────────────────────────┘
For most AWS-native RAG stacks, the right answer is Bedrock Guardrails for inference-time protection combined with Amazon Comprehend Medical for any pre-ingestion PHI scan of source documents.
// Key Takeaways
- PII and PHI are not just a compliance checkbox — they represent a genuine risk that AI pipelines amplify because retrieval surfaces verbatim text at scale
- There are two surfaces to protect: data at rest (inside the vector store) and data in motion (user queries and LLM responses)
- Detection approaches span a spectrum from fast-but-brittle regex to accurate-but-costly ML classifiers; production systems layer several
- Redaction is not a binary — tokenisation, pseudonymisation, and anonymisation have different reversibility and utility trade-offs
- Bedrock Guardrails applies PII detection and anonymisation server-side, in the inference path, with no extra infrastructure and with audit tracing built in
- Logs are a hidden PII surface — design your observability stack to record metadata rather than raw text from the start