Back to news

How-to Guide

How to set up PII redaction for AI workflows.

How to set up PII redaction for AI workflows: Implement comprehensive PII detection and redaction across your AI pipelines using Microsoft Presidio,…

AI Kick Start editorial image for How to set up PII redaction for AI workflows.
Decision

Pilot

Choose one repeated workflow with a visible owner and enough weekly volume to prove the saving.

Risk to watch

Faster mistakes

Keep a review queue and scoped credentials until the workflow has survived real production runs.

Proof to collect

Time baseline

Measure the manual run time, exception rate, approval time, and weekly hours returned.

TL;DR

TL;DR: Sending data to LLM APIs risks exposing PII. This guide implements a multi-layer redaction system using [Microsoft Presidio](https://github.com/microsoft/presidio) for entity detection, custom recognisers for domain-specific data, and architectural patterns that ensure sensitive data never leaves your infrastructure unprotected.

Key takeaways

  • Presidio: Open-source PII detection and anonymisation from Microsoft
  • Coverage: 30+ built-in entities: email, phone, SSN, credit card, address
  • Custom entities: Add regex/ML recognisers for domain-specific PII
  • Architecture: Redact before API call; deanonymise after response
  • Compliance: GDPR, CCPA, HIPAA-ready with audit logging
  • Analysis: Analysis Every time your team pastes a customer email, a support transcript, or a spreadsheet into an AI tool, a copy of that text leaves the building.
Table of contents

Analysis

Every time your team pastes a customer email, a support transcript, or a spreadsheet into an AI tool, a copy of that text leaves the building. It travels to someone else's servers. For a lot of Australian businesses that is fine right up until the day it isn't: a name, a phone number, a Medicare reference, or a client's home address ends up in a prompt log somewhere you have no control over.

The fix is not "stop using AI". It is to scrub the personal details out of the text before it goes anywhere, then stitch them back in when the answer comes home. The customer's name becomes a placeholder on the way out, and the placeholder becomes their name again on the way back. The AI does its job. The private bits never leave your control.

Microsoft built an open-source tool for exactly this, called Presidio, and it already knows how to spot dozens of kinds of personal data out of the box. The rest of this piece is the build: how to detect personal data, how to teach the tool about your own internal codes, how to make the redaction reversible, and how to keep an audit trail your compliance people will actually accept. It is written for engineers, but the idea underneath is simple enough to explain to anyone signing off the budget.

Analysis

Prerequisites

Step-by-Step Framework

Step 1: Basic PII Detection

Presidio splits the work in two. The `AnalyzerEngine` (opens in a new tab) finds the personal data, and the AnonymizerEngine decides what to do with each piece it finds. You point it at some text, it hands back the entities it spotted, and you tell it how to mask each type.

# redaction/basic.py
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
from presidio_anonymizer.entities import OperatorConfig

analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()

def redact_pii(text: str) -> dict:
 # Analyse
 results = analyzer.analyze(text=text, language='en')

 # Anonymise
 anonymized = anonymizer.anonymize(
 text=text,
 analyzer_results=results,
 operators={
 "DEFAULT": OperatorConfig("replace", {"new_value": "<REDACTED>"}),
 "PERSON": OperatorConfig("mask", {"type": "hash", "hash_type": "sha256"}),
 "EMAIL_ADDRESS": OperatorConfig("replace", {"new_value": "<EMAIL>"})
 }
 )

 return {
 "original": text,
 "redacted": anonymized.text,
 "entities_found": [{"type": r.entity_type, "start": r.start, "end": r.end} for r in results],
 "entity_count": len(results)
 }

# Test
text = "Contact John Smith at john.smith@example.com or 555-123-4567. His SSN is 123-45-6789."
result = redact_pii(text)
print(result["redacted"])
# Contact <REDACTED> at <EMAIL> or <REDACTED>. His SSN is <REDACTED>.

One caution before you copy that block into production. The PERSON line above mixes up two different Presidio operators. The `mask` operator (opens in a new tab) only understands chars_to_mask, masking_char and from_end; the type and hash_type keys actually belong to the separate hash operator. As written, this will not SHA-256 the name and will probably error or quietly do nothing. If you want hashing, switch the operator to "hash". Presidio ships five operators in total: replace, redact, mask, hash and encrypt, so pick the one that matches what you mean.

Step 2: Custom Entity Recognisers

Presidio's built-in list covers the usual suspects: names, emails, phone numbers, credit cards, addresses. What it does not know is your business. Your employee IDs, your project codes, your internal record numbers all look like ordinary text to it. You teach it those with a `PatternRecognizer` (opens in a new tab) and a regex.

# redaction/custom_entities.py
from presidio_analyzer import PatternRecognizer, Pattern

# Employee ID recogniser
employee_id_pattern = Pattern(
 name="employee_id",
 regex=r"EMP-[0-9]{5,8}",
 score=0.9
)
employee_recognizer = PatternRecognizer(
 supported_entity="EMPLOYEE_ID",
 patterns=[employee_id_pattern]
)

# Project code recogniser
project_pattern = Pattern(
 name="project_code",
 regex=r"PRJ-[A-Z]{2,4}-\d{4}",
 score=0.85
)
project_recognizer = PatternRecognizer(
 supported_entity="PROJECT_CODE",
 patterns=[project_pattern]
)

# Medical record number (HIPAA)
mrn_pattern = Pattern(
 name="mrn",
 regex=r"MRN\d{8,10}",
 score=0.95
)
mrn_recognizer = PatternRecognizer(
 supported_entity="MEDICAL_RECORD_NUMBER",
 patterns=[mrn_pattern]
)

# Register custom recognisers
from presidio_analyzer import RecognizerRegistry

registry = RecognizerRegistry()
registry.load_predefined_recognizers()
registry.add_recognizer(employee_recognizer)
registry.add_recognizer(project_recognizer)
registry.add_recognizer(mrn_recognizer)

analyzer = AnalyzerEngine(registry=registry)

The score on each pattern is your confidence dial. A medical record number with a strict format earns a 0.95; a looser pattern that might catch false positives deserves something lower. Load the predefined recognisers first, then stack your own on top.

Step 3: Reversible Redaction (Deanonymisation)

Masking is enough when you only need an answer about the text. But plenty of workflows need the real values back: a booking confirmation has to name the actual customer, not <PERSON_xxxx>. That is the redact-then-restore pattern Presidio is built around. You swap each entity for a unique token, keep a private map of token-to-original, and reverse the swap after the LLM replies.

# redaction/reversible.py
import hashlib
import json
from typing import Dict

class ReversibleRedactor:
 def __init__(self):
 self.analyzer = AnalyzerEngine()
 self.vault: Dict[str, str] = {} # hash -> original

 def redact(self, text: str) -> tuple[str, dict]:
 results = self.analyzer.analyze(text=text, language='en')
 vault = {}
 current_pos = 0
 parts = []

 for result in sorted(results, key=lambda x: x.start):
 # Add text before entity
 parts.append(text[current_pos:result.start])

 # Create vault key
 entity_text = text[result.start:result.end]
 vault_key = f"<{result.entity_type}_{self._hash(entity_text)}>"
 vault[vault_key] = entity_text

 parts.append(vault_key)
 current_pos = result.end

 parts.append(text[current_pos:])
 redacted = "".join(parts)

 return redacted, vault

 def restore(self, text: str, vault: dict) -> str:
 restored = text
 for key, value in vault.items():
 restored = restored.replace(key, value)
 return restored

 def _hash(self, text: str) -> str:
 return hashlib.sha256(text.encode()).hexdigest()[:8]

# Usage
redactor = ReversibleRedactor()
original = "John Smith booked a flight for EMP-12345"
redacted, vault = redactor.redact(original)
# "<PERSON_a1b2c3d4> booked a flight for <EMPLOYEE_ID_e5f6g7h8>"

llm_response = f"Booking confirmed for {redacted.split()[0]}"
final = redactor.restore(llm_response, vault)
# "Booking confirmed for John Smith"

The hashes in those comments (a1b2c3d4 and so on) are stand-ins to show the shape of the output. Real SHA-256 prefixes will look nothing like that. The point is that the token is derived from the value rather than a sequential counter, so an attacker reading the redacted text can't infer how many records you hold or guess the next one.

Step 4: FastAPI Middleware

Doing this by hand on every call gets tedious, so push it into middleware (opens in a new tab) that sits in front of your endpoints. The request body gets redacted on the way in, the vault rides along in request state, and the response gets restored on the way out.

# redaction/middleware.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import json

app = FastAPI()
redactor = ReversibleRedactor()

@app.middleware("http")
async def pii_redaction_middleware(request: Request, call_next):
 # Skip if endpoint opts out
 if request.url.path in ["/health", "/metrics"]:
 return await call_next(request)

 # Redact request body
 body = await request.body()
 if body:
 body_text = body.decode()
 redacted_body, vault = redactor.redact(body_text)

 # Store vault in request state for restoration
 request.state.vault = vault

 # Replace request body
 async def receive():
 return {"type": "http.request", "body": redacted_body.encode()}
 request._receive = receive

 # Process request
 response = await call_next(request)

 # Restore PII in response
 if hasattr(request.state, 'vault') and request.state.vault:
 response_body = b"".join([chunk async for chunk in response.body_iterator])
 restored_body = redactor.restore(response_body.decode(), request.state.vault)
 return JSONResponse(
 content=json.loads(restored_body),
 status_code=response.status_code
 )

 return response

@app.post("/llm/completions")
async def llm_completions(request: Request):
 body = await request.json()
 # Forward to LLM API (body already redacted by middleware)
 # ... LLM call
 return {"completion": "Processed: " + body.get("prompt", "")}

Treat this as a working sketch, not a finished product. Overriding request._receive and draining response.body_iterator are real Starlette techniques, but they're fragile: they assume JSON in and JSON out, and they will break on streaming responses or anything that isn't valid JSON. Fine for learning the pattern, worth hardening before it touches customer traffic.

Step 5: Claude Code Integration

If you want the redaction step to fire automatically from inside an agent workflow, you can wrap the service in a small client that calls your /redact endpoint. The snippet below shows the idea.

// .claude/skills/pii-guard.ts
import { defineSkill } from '@anthropic/claude-sdk';
import { z } from 'zod';

const PII_ENDPOINT = process.env.PII_SERVICE_URL || 'http://localhost:8000';

export default defineSkill({
 name: 'pii-guard',
 description: 'Redacts PII before sending to LLM APIs',

 input: z.object({
 text: z.string(),
 mode: z.enum(['redact', 'reversible', 'check']).default('reversible')
 }),

 output: z.object({
 processed: z.string(),
 entities_found: z.array(z.string()),
 vault: z.record(z.string()).optional()
 }),

 async execute({ text, mode }) {
 const response = await fetch(`${PII_ENDPOINT}/redact`, {
 method: 'POST',
 headers: { 'Content-Type': 'application/json' },
 body: JSON.stringify({ text, mode })
 });

 return response.json();
 }
});

Read this one as illustration rather than something you can npm install and run. The @anthropic/claude-sdk package and its defineSkill() function don't appear to be a real, published API. Anthropic's actual TypeScript library is `@anthropic-ai/sdk` (opens in a new tab), and Claude Code skills are authored as Markdown SKILL.md files rather than registered through a TypeScript function. The useful takeaway is the shape: validate the input, call your redaction service, hand back the cleaned text plus the vault.

Step 6: Audit Logging

The logging is what turns "we redact PII" into something you can prove. Record the entity types, the counts, the source, and a hash of the original. Never write the values themselves to the log, or you've just recreated the leak you were trying to stop.

# redaction/audit.py
import structlog
import json
from datetime import datetime

logger = structlog.get_logger("pii_audit")

def log_redaction(original_text: str, redacted_text: str, entities: list, source_ip: str):
 logger.info(
 "PII redaction performed",
 timestamp=datetime.utcnow().isoformat(),
 source_ip=source_ip,
 entity_types=[e['type'] for e in entities],
 entity_count=len(entities),
 original_length=len(original_text),
 redacted_length=len(redacted_text),
 # NEVER log the actual text or entity values
 text_hash=hashlib.sha256(original_text.encode()).hexdigest()
 )

def log_api_call(redacted_prompt: str, model: str, vault_size: int):
 logger.info(
 "LLM API call with redacted data",
 timestamp=datetime.utcnow().isoformat(),
 model=model,
 vault_entries=vault_size,
 prompt_length=len(redacted_prompt)
 )

Do/Don't

DoDon't
Use reversible redaction for workflows needing restorationSend PII to LLM APIs without any protection
Log entity types and counts, never valuesLog the actual PII values
Add custom recognisers for your domainRely solely on Presidio's default set
Audit every redaction and API callSkip audit logging
Hash vault keys instead of sequential IDsUse predictable replacement tokens

Compliance Matrix

A redaction layer helps you meet these requirements; it does not single-handedly make you compliant. Read the matrix as "this is the part of the puzzle redaction solves", not a tick-box for the whole regulation.

RequirementImplementation
GDPR Art. 32 (opens in a new tab)Encryption + access controls on vault
HIPAA Safe Harbor (opens in a new tab)Remove 18 identifiers; use MRN recogniser
CCPAAudit logging; right to deletion
SOC 2Access logs; quarterly PII scanning

Conclusion

Redacting personal data before it reaches an LLM API is the difference between using AI safely and hoping nobody audits you. Presidio gives you solid coverage from the start, and a few custom recognisers cover the codes specific to your business. The reversible pattern keeps the data protected in transit while you still get usable answers back. Audit everything, keep the real values out of your logs, and re-test the pipeline every quarter so it doesn't quietly rot.

Source trail

Primary references to keep this briefing grounded

AI and automation information changes quickly. Use these official or primary references to verify the claims, pricing, product behaviour, and compliance details before committing budget or production data.

Frequently asked questions

What is the practical takeaway from How to set up PII redaction for AI workflows?

Implement comprehensive PII detection and redaction across your AI pipelines using Microsoft Presidio, custom entity recognisers, and architectural patterns for data privacy compliance. For AI Kick Start readers, the key is to translate the idea into one AI implementation workflow with clear inputs, review points, and measurable outcomes. The article should be treated as implementation guidance, not a substitute for workflow design.

Who should use How to set up PII redaction for AI workflows guidance in How-to Guide?

This guidance is most useful for Operations and governance teams who need to decide whether the topic changes tool selection, automation design, search visibility, data handling, training, or operational governance.

How should an Australian business implement How to set up PII redaction for AI workflows?

Start small: pick one useful business workflow, test it with real inputs, keep a human review point, and measure the result before scaling. If the pilot improves time saved and quality score, document the pattern, link it to the relevant service or resource page, and then decide whether it belongs in a production workflow.

What to do next

  1. For How to set up PII redaction for AI workflows, write down the single AI implementation workflow this article should improve.
  2. Collect real examples, edge cases, and source material before testing How to set up PII redaction for AI workflows with any AI output.
  3. Before implementing How to set up PII redaction for AI workflows, add a human review checkpoint for quality, privacy, brand, or customer-impact risk.
  4. Measure time saved, quality score, review effort for How to set up PII redaction for AI workflows before deciding whether to scale.
  5. Connect How to set up PII redaction for AI workflows to a related service, resource, or training path so readers have a clear next action.

Want help applying this? Explore our AI automation services.

AI Kick Start is an Illawarra-based AI studio in Figtree, helping businesses across Wollongong, Shellharbour and Kiama and right across Australia put AI to work.

Explore with AI

Use the article as a decision prompt

Summarise this AI Kick Start article for an Australian business owner. Focus on the useful decision, the risks, and the first practical next step: How to set up PII redaction for AI workflows

Turn this into a practical roadmap.

Use the guide as a starting point, then map the first workflow worth building.

Book an AI strategy call