What Changes in Prompt-Level PII Handling During a Migration
10 min read · updated August 11, 2026
“The provider handles PII” is one of the assumptions a migration tests hardest, because it is usually describing a platform feature that the new provider either does not have or implements with different entity types, a different placement and different exceptions.
Safety filtering is not PII handling
The first thing to establish is what you were actually relying on. The content filtering that most inference APIs apply by default classifies text into harm categories — sexual, violence, hate, self-harm and similar — and returns severity levels or blocks the request. It has nothing to say about a customer’s national insurance number. A workflow that was signed off on the basis of “the API filters content” may never have had PII handling at all, and discovering that during a migration is better than discovering it during an incident, but it is not a migration problem.
Actual PII handling, where it exists, is a separate opt-in layer that sits between your request and the model, configured outside the inference call. That structural fact is the one that matters: it means the capability belongs to the platform, not to the model, so changing models within a platform keeps it and changing platforms loses it.
What a platform redaction feature actually does
Amazon Bedrock Guardrails is the clearest published example, and its shape is worth knowing in detail even if you are moving away from it, because it defines the behaviour you have to reproduce. Its sensitive information filters documentation specifies a policy with two halves: a list of built-in PII entity types and a list of custom regular expressions. Each entry takes an action of BLOCK, ANONYMIZE or NONE, and the input and output directions can be configured separately via inputAction and outputAction.
Under ANONYMIZE, the detected span is replaced with the entity type in braces — {NAME}, {EMAIL}. Under BLOCK, the whole request or response is refused and a message you configured is returned instead. Three details in that design change how your application has to behave:
- Block and refusal are different events that look the same. A blocked request returns your configured message, not a model error. If your metrics count “model declined” by matching response text, a policy change silently moves traffic between two categories you thought you were measuring.
- Detection is probabilistic and context-dependent. The documentation says so directly, and advises supplying more surrounding context because a bare string of digits is ambiguous. Recall therefore differs between implementations, and a change in recall is a change in residual risk rather than a configuration detail.
- Direction matters. If the old configuration masked outputs as well as inputs and the new one does not, PII the model reproduces from retrieved context now reaches your UI, your logs and your analytics pipeline.
Where it keeps the original value on purpose
This is the part worth reading twice, because it means a workflow that trusted provider-side masking may already be storing unmasked data. The same documentation records two deliberate exceptions.
First, model invocation logging. If you enabled invocation logs to CloudWatch or S3, the input field in those logs contains the original, unmodified request regardless of guardrail intervention. The masking applies to what the model sees, not to what the platform records. Anyone with read access to that log group has the raw prompt.
Second, the trace output. The match field of the PII entity filter returned in API responses — including in the trace object — contains the original PII value rather than the masked one, and the documentation states this is by design so your application can act on the detection. Which means the redacted-looking response object you serialise into your own request log carries the very value you thought had been removed.
The general lesson survives the specifics: a provider-side redaction feature protects the model’s input, and the platform’s own observability is usually outside that boundary. Testing this is straightforward and belongs in your suite — asserting that redaction happens before anything is printed on a failure path catches most of it.
The entity taxonomy does not map
Built-in entity lists are jurisdictional and idiosyncratic. The Bedrock list, for example, distinguishes US_SOCIAL_SECURITY_NUMBER, CA_SOCIAL_INSURANCE_NUMBER, UK_NATIONAL_INSURANCE_NUMBER and UK_NATIONAL_HEALTH_SERVICE_NUMBER as separate types, alongside general types such as NAME, ADDRESS, AGE, USERNAME and PASSWORD, and IT types including IP_ADDRESS, MAC_ADDRESS and cloud access keys. Another implementation will draw those boundaries differently, and some categories will simply be absent.
Your records of processing and your DPIA name categories of personal data in your vocabulary. The mapping between that vocabulary and each platform’s entity list is a document you owe, and the migration is the moment to write it, because it is also the artefact that tells you which categories the new platform cannot detect at all and therefore which ones you now have to handle yourself. Custom regex entries cover structured identifiers like account or booking numbers reasonably well — Bedrock notes its regex filter does not support lookaround — but nothing regex-shaped will find a name.
Move the control to your side
The conclusion this pushes towards is not “pick the platform with the better filter”. It is that a control implemented in the provider is a control you re-implement at every migration, on their taxonomy, with their exceptions, and with no way to test it identically before and after. A redaction step you own runs the same way on both sides of a cutover, can be unit-tested against your own fixtures, and keeps the raw value out of the provider’s logs as well as its model. Migrating a redaction pipeline that sits in front of the call covers what that step has to get right, and retention terms cover the part of the risk that redaction does not reach.