Practical Compliance

Handling PII Before It Reaches an AI Model

⏱ 14 min

Course 4 covered keeping sensitive fields out of an ability’s response to an AI agent. This lesson covers the other direction of the same problem: keeping personal data out of the prompts your own WordPress code constructs when it calls an AI model directly, using the PHP AI Client SDK, for something like summarization, moderation, or classification. Both are the same underlying discipline, decide what an AI system actually needs to see, and never hand it more, applied at two different points in the pipeline: what an ability returns, and what a prompt contains.

What you'll learn in this lesson
Why this is the prompt-construction half of Course 4's data minimization lesson
output_schema controls what agents receive back, this lesson controls what your own code sends out.
A practical redaction function for common PII patterns
Emails, phone numbers, and a pluggable pattern for names or addresses.
Where redaction belongs in the request pipeline
Immediately before prompt construction, not as an afterthought on the response.
Why redaction is a judgment call, not a solved problem
Regex-based redaction is imperfect, and knowing its limits matters as much as using it.
Prerequisites

Course 4’s “Protecting Sensitive Data from AI Agents” lesson, and Course 5’s PHP AI Client SDK overview. This lesson assumes you’re already constructing prompts from WordPress data somewhere in your codebase.

Step 1: Two different places PII can reach a model, and why both matter

An ability’s output_schema governs what comes back from a WordPress action an agent calls. A prompt your own plugin code builds, to summarize a support ticket, moderate a comment, or classify a product review, governs what goes into a model in the other direction. Both are data-minimization problems, and treating only one of them carefully leaves a real gap.

Two directions, one discipline
DirectionWhere it’s controlledCovered in
WordPress action to AI agentoutput_schema and a deliberately narrow execute_callback returnCourse 4, Lesson 6
WordPress code to AI model (via PHP AI Client SDK)What your code includes when it constructs a prompt stringThis lesson

Step 2: Redact before you build the prompt, not after

The discipline from Course 4 applies here too: don’t pass through a full database row, comment object, or order object into a prompt template and trust that the model won’t echo something sensitive back. Build the text that reaches the model deliberately, and redact known-sensitive patterns before that text is ever concatenated into the request.

// File: my-plugin.php
function my_plugin_redact_pii( string $text ): string {
	// Emails.
	$text = preg_replace( '/[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/', '[redacted-email]', $text );

	// Phone numbers, a permissive pattern covering common formats.
	$text = preg_replace( '/\+?\d[\d\s().-]{7,}\d/', '[redacted-phone]', $text );

	return $text;
}

Apply it immediately before constructing the request, not as a step you might remember to add later:

// File: my-plugin.php
function my_plugin_summarize_ticket( int $ticket_id ) {
	$ticket = my_plugin_get_ticket( $ticket_id );

	// Redact before this ever becomes part of a prompt string.
	$safe_body = my_plugin_redact_pii( $ticket->body );

	$prompt = "Summarize this support ticket in two sentences:\n\n" . $safe_body;

	return ai_client()
		->using_provider( 'anthropic' )
		->generate_text( $prompt );
}
Redact the actual variable that reaches the prompt, not a copy used elsewhere

It’s an easy mistake to redact a value for logging purposes and then separately, further down the function, build the prompt from the original unredacted variable. Redact once, early, and use only the redacted variable for everything downstream, prompt construction and logging alike.

Step 3: Combine with narrow selection, not just redaction

Regex redaction catches known patterns, emails, phone numbers, and with more effort, some name and address formats. It won’t catch everything, and it shouldn’t be the only line of defense. Pair it with the same practice from Course 4: only pull the specific fields a prompt actually needs, rather than handing an entire object to a templating function and redacting the result.

// File: my-plugin.php
// Weaker: passes the whole object through, redaction has to catch everything.
$prompt = "Summarize:\n\n" . my_plugin_redact_pii( print_r( $ticket, true ) );

// Stronger: select only the fields the summary actually needs, then redact those.
$prompt = "Summarize this support ticket:\n\n" . my_plugin_redact_pii( $ticket->subject . "\n" . $ticket->body );
Select the specific fields a prompt needs before building the string
The same "output_schema around the workflow" discipline from Course 4, applied to prompt inputs.
Run redaction on the selected text, as a second layer
Catches patterns that slip through even careful field selection.
Never redact a copy and send the original
Redact the exact value used in the request.
Redact before logging too
The audit log in Lesson 7 should store the same redacted version, not the raw input.

Step 4: Know the limits of regex-based redaction

Regex patterns reliably catch structured data like emails and standard phone number formats. They’re much less reliable against names, physical addresses, or personal details embedded in free-form prose, “my daughter Maya’s school” isn’t something a regex pattern will reasonably catch. Where a feature routinely processes free text likely to contain this kind of information, more robust approaches exist, a named-entity recognition pass, an allowlist of only specific structured fields rather than free text at all, or simply not sending that category of content to an external model in the first place. Treat regex redaction as a real, useful layer, not a complete solution.

Don't let a false sense of coverage stop further review

A redaction function that runs successfully isn’t proof that no personal data reached the prompt, only that the specific patterns it was written to catch didn’t appear in that specific input. Periodically review real (redacted) request logs by hand to check for personal data your patterns are missing.

Test it: confirm redaction actually runs

wp-env run cli wp eval '
$text = "Contact Jane at jane@example.com or 555-123-4567 about her order.";
echo my_plugin_redact_pii( $text );
'

You should see the email and phone number replaced, and the rest of the sentence intact. Then trace one real prompt-construction function in your codebase and confirm redaction runs on the exact string that gets sent, not a separate copy.

Recap

Course 4 handled what an ability hands back to an agent. This lesson handles the other direction: what your own code sends to a model. Select only the fields a prompt actually needs, redact known PII patterns from that selected text immediately before construction, and treat redaction as one useful layer rather than a guarantee, since free-form prose can carry personal data no regex reliably catches.

Resources & further reading

← AI Content Disclosure and Transparency Requirements Bias and Fairness Considerations in AI-Driven Features →