Privacy

PII Redaction Before Prompts Leave Your Server

⏱ 14 min

Self-hosting removes one specific risk, a third-party AI vendor receiving your data. It doesn’t remove every risk around personal data in a prompt. Your own inference server still has logs, your own model server can still be misconfigured to persist request history, and a prompt built carelessly from a full database row can still hand a local model far more personal data than the task actually required. Course 16 covered regex-based redaction for cloud-provider prompts. This lesson applies the same discipline to a self-hosted setup, and takes it seriously exactly because “it’s all on our own infrastructure” is an easy, dangerous excuse to skip the redaction step altogether.

What you'll learn in this lesson
Why self-hosting doesn't remove the need to redact
Local logs, local model server histories, and internal-only misuse are all still real.
A concrete regex-based redaction function
Emails and phone numbers, applied immediately before a prompt is built.
Where redaction belongs relative to your custom provider call
Before the text ever reaches generate_text() or your embedding call.
The real, honest limits of this approach
What regex reliably catches, and what it reliably misses.
Prerequisites

Course 16’s Handling PII Before It Reaches an AI Model lesson, and this course’s Lesson 4 custom provider. This lesson extends that pattern for a self-hosted context rather than re-deriving it from scratch.

Step 1: Why “it’s all local” isn’t a reason to skip this

It’s tempting to treat self-hosting as a reason redaction matters less: no third-party vendor ever sees the prompt, so what’s the risk? Three real ones remain. Your inference server (Ollama, vLLM, or llama.cpp) may write request logs to disk depending on its configuration, and those logs are a new place personal data now lives that didn’t exist before you self-hosted. A local model server accessible to more than one internal team means “internal” isn’t the same as “only the one person who needs this specific data.” And a prompt built from a full object rather than deliberately selected fields carries the same over-exposure risk locally that it would against a cloud API, the destination changed, the underlying discipline didn’t.

Self-hosting changes who could see the data, not whether you should minimize it

Treat local infrastructure with the same prompt-construction discipline as a cloud provider call. The privacy benefit of self-hosting comes from controlling where data goes, not from an assumption that anything running on your own servers is automatically safe to send in full.

Step 2: The redaction function

The same regex-based approach from Course 16 applies here, unchanged in substance:

// File: my-plugin/includes/class-pii-redactor.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 prompt that reaches your Lesson 4 provider, using the same discipline of selecting only the fields a prompt actually needs first:

// File: my-plugin/class-local-summary-feature.php
function my_plugin_summarize_ticket_locally( int $ticket_id ) {
	$ticket = my_plugin_get_ticket( $ticket_id );

	// Select only what the task needs, then redact that selection.
	$safe_body = my_plugin_redact_pii( $ticket->subject . "\n" . $ticket->body );

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

	return wp_ai_client_prompt( $prompt )
		->using_model_preference( 'llama3.1' )
		->generate_text();
}
Select fields deliberately, then redact the selection
Redaction is a second layer, not a substitute for not passing a whole object through.
Redact the exact variable used in the request
A redacted copy used only for logging while the original still reaches generate_text() defeats the purpose.
Redact before it reaches your local inference server's own logs, too
If Ollama, vLLM, or llama.cpp is configured to log request bodies, that log is now a second place this data lives.

Step 3: Know exactly what this catches, and what it doesn’t

Regex-based redaction is a real, useful layer, not a complete solution

This pattern reliably catches structured, predictable formats: standard email addresses and common phone number layouts. It is much less reliable against personal data embedded in free-form prose, a name, a home address, a birthdate written out in a sentence, or any personal detail that doesn’t follow a fixed structural pattern a regex can target. “Contact my daughter Maya about the delivery” will pass straight through unredacted, the name and the relationship both remain. Treat this function as one layer that catches the easy, structured cases, not a guarantee that no personal data reaches your local model. For features that routinely process free text likely to contain names or addresses, combine this with narrow field selection, an allowlist of only certain structured input, or, where the risk is high enough, simply not processing that category of free text with an AI model at all.

Test it: confirm redaction runs, then look for what it misses

wp-env run cli wp eval '
$text = "Contact Jane at jane@example.com or 555-123-4567 about her order, ask for her sister Maya if she is unavailable.";
echo my_plugin_redact_pii( $text );
'

Confirm the email and phone number are replaced. Then read the rest of the output carefully: both names are still there, exactly as the limits above describe. That’s the real test of this lesson, not whether the function runs without error, but whether you can look at its actual output and correctly identify what it still let through.

Recap

Self-hosting removes a third-party vendor from the picture, it doesn’t remove the need to minimize and redact personal data before it reaches a prompt. Select only the fields a task actually needs, redact known structured patterns like emails and phone numbers immediately before constructing the request to your Lesson 4 provider, and treat that redaction as one real, useful layer rather than a complete guarantee, since free-form prose carrying a name or address will pass through unchanged. Periodic manual review of real (redacted) prompts remains the only honest check on what your patterns are missing.

Resources & further reading

← Local RAG With a Self-Hosted Model EU AI Act and GDPR Alignment for Self-Hosted AI →