Skip to main content
Version: 1.8.0

Control PII in AI traffic

Personal data can enter an AI system in a prompt, a tool result, or a model response. DVARA finds that data on the request path and applies the action you choose before the call reaches a provider. It can also inspect the response before your application receives it.

Start with LOG to learn what your traffic contains. Move to BLOCK or REDACT when you are confident that the detector matches the data you intend to control. Use TOKENIZE only when your application must recover the original value later.

Choose the distribution​

CapabilityOpen Source distributionEnterprise platform
Regex and checksum detectionYesYes
LOG, BLOCK, and REDACTYesYes
Reversible TOKENIZENoYes, with a token store and key material
Response and streaming inspectionYesYes
Custom workspace patternsYes, through file configurationYes, managed through workspace settings
Optional Presidio and embedded detectorsExtension point onlyAvailable when the selected detector is packaged and configured
LLM trafficYesYes
MCP and A2A trafficNoYes

The Open Source distribution contains the deterministic regex detector. Presidio and embedded-detector settings bind in the runtime, but a setting on its own does not install a detector. Presidio becomes active only when you give it an endpoint and run a compatible analyzer service.

The Phileas embedded scanner is different from 1.8.1: it ships inside the Enterprise Gateway image, so nothing has to be built or mounted to use it. It stays off until you turn it on, with either dvara.llm-gateway.pii.embedded.enabled (redaction and tokenization) or dvara.llm-gateway.guardrail.embedded.enabled (detection only). Turn on one, not both — the same value would otherwise be caught twice, and the Gateway warns about it at startup. Flightdeck does not carry the library.

If you build your own embedding application and want Phileas there, add the dependency yourself:

<dependency>
<groupId>ai.philterd</groupId>
<artifactId>phileas</artifactId>
<version>3.3.0</version>
</dependency>

Check the startup report before you rely on any of these layers. It names the ones that are actually running.

Choose the action​

PII inspection is enabled by default. The default action is LOG, so a new installation records detections but still sends the original value to the provider.

ActionProvider-bound requestNewly detected response PII
LOGRecord the detection and send the value unchangedRecord the leak and return the value unchanged
BLOCKRefuse the request with HTTP 400Record the leak and refuse the response with HTTP 400
REDACTReplace the value with [REDACTED_TYPE]; store nothingReplace the value with [REDACTED_TYPE]; store nothing
TOKENIZEReplace the value with a recoverable tokenRemove the value with [REDACTED_TYPE]; do not mint a response token

REDACT is permanent removal. TOKENIZE is reversible pseudonymisation. They are separate actions in 1.8 and should not be described as interchangeable.

Workspaces that run Claude Code

From 1.8.5 Claude Code can call DVARA through /v1/messages. Claude Code's own prompt carries an email address, so the email detector matches on every call. With LOG that is an audit entry and nothing else. REDACT or TOKENIZE edits Claude Code's own prompt before it reaches the model, and BLOCK refuses every call. For a workspace that runs Claude Code, keep the email action at LOG, or expect that edit. See Govern Claude Code.

Block PII in Open Source​

Start with the Open Source distribution quickstart. Restart the gateway with the PII action set to BLOCK:

DVARA_LLM_GATEWAY_PII_DEFAULT_ACTION=BLOCK \
DVARA_AUDIT_FILE_PATH="$PWD/var/audit.jsonl" \
DVARA_AUDIT_HMAC_SECRET='<generated-hmac-secret>' \
java -jar dvara-gateway-server-1.8.5-app.jar

The example uses the built-in Mock provider. Send a synthetic SSN to the local gateway:

curl -s -w '\nHTTP %{http_code}\n' \
http://localhost:8080/v1/chat/completions \
-H "Authorization: Bearer $DVARA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"model": "mock/approved",
"messages": [
{"role": "user", "content": "The test SSN is 123-45-6789."}
]
}'

The Open Source quickstart mints DVARA_API_KEY and stores only its fingerprint in gateway.yaml.

DVARA refuses the request before provider dispatch:

{
"error": {
"message": "Request blocked: PII detected (SSN)",
"type": "pii_violation",
"code": "pii_detected",
"trace_id": "01K4V5R9X21MDN8W5A6E7ZQ3TC"
}
}
HTTP 400

The trace ID changes on every request. Do not retry this response without changing the content or the workspace action.

Verify the block​

Find the decision in the local audit file:

jq 'select(.eventType == "PII_DETECTED") |
{eventType, workspaceId, source: .payload.source,
count: .payload.entity_count, types: .payload.entity_types}' \
var/audit.jsonl

The matching record looks like this:

{
"eventType": "PII_DETECTED",
"workspaceId": null,
"source": "request",
"count": 1,
"types": "SSN"
}

The audit payload records types and counts, not the detected value. The full JSONL line also carries the sequence, previous hash, HMAC, and signing time.

Remove PII without blocking the call​

Restart the Open Source distribution with:

DVARA_LLM_GATEWAY_PII_DEFAULT_ACTION=REDACT

With the same request, the provider receives:

The test SSN is [REDACTED_SSN].

The original value is discarded. It is not written to a token store and cannot be restored. The request continues, and the audit trail contains PII_REDACTED.

This action needs no database, encryption key, or Enterprise service.

PII in replayed assistant tool-call arguments is governed too. With REDACT, this request continues with the SSN replaced inside the JSON argument string while preserving the tool name and call ID:

curl -s http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "mock/approved",
"messages": [
{"role": "user", "content": "Store the approved test record."},
{
"role": "assistant",
"tool_calls": [{
"id": "call_approved_record",
"type": "function",
"function": {
"name": "store_record",
"arguments": "{\"customer_id\":\"CUST-2048\",\"ssn\":\"123-45-6789\"}"
}
}]
}
]
}'

The provider receives "ssn":"[REDACTED_SSN]", and the audit trail records PII_REDACTED with source: request. DVARA also scans text in multimodal message blocks and tool results; non-text image and audio payloads are not decoded by the regex detector.

Use reversible tokenization​

Choose TOKENIZE only when the model must work with a stable placeholder and your application must recover the original value later. It requires the Enterprise token store and its key material.

On the request path, DVARA stores the original value under the resolved workspace and sends an opaque token to the provider. Automatic response detokenization is off by default. When enabled, it works only for non-streaming responses and restores only tokens created from that request. It never restores new PII produced by the model.

Manual detokenization and token-store purge in Flightdeck require approval from a different PII manager. The original requester executes the exact approved input or workspace before the 15-minute approval expires. This prevents one signed-in user from authorizing their own disclosure or irreversible erasure.

The Open Source distribution has no token store. If it is configured with TOKENIZE, it refuses a request containing PII with HTTP 403 and code: pii_tokenize_unavailable. It does not silently fall back to REDACT or send the raw value:

{
"error": {
"message": "pii.action=TOKENIZE needs a reversible token store, which is not part of this build. Use REDACT for irreversible removal, which needs no key material and always works.",
"type": "pii_violation",
"code": "pii_tokenize_unavailable",
"trace_id": "01K4V6A7F2B8Q4P0W1N9X3C5ZD"
}
}

In the Enterprise platform, an unavailable token store follows pii.degraded-action. The default is BLOCK. A blocked request returns HTTP 403 with the retained compatibility code pii_redact_unavailable and attempted_action: TOKENIZE. The audit event also retains the name PII_REDACT_DEGRADED; use its attempted_action and degraded_action fields when building alerts.

Control PII in model responses​

Response scanning is on by default. A newly detected value is handled according to the same workspace action, with one important boundary: TOKENIZE never creates a recoverable token for new model output.

  • LOG records PII_OUTPUT_LEAK and returns the response unchanged.
  • BLOCK records the leak and refuses a non-streaming response with HTTP 400 and code: pii_detected. The provider has already answered, but its content is not delivered to the caller.
  • REDACT and TOKENIZE record the leak and replace the new value with an irreversible placeholder.

This distinction prevents a response-side detector from giving the caller a recovery token for a value the model introduced.

For a non-streaming response blocked after provider dispatch, the caller receives:

{
"error": {
"message": "Response blocked: PII detected in the model response (SSN)",
"type": "pii_violation",
"code": "pii_detected",
"trace_id": "01K4V6KG4T1H9W8M7R2C0N5BQP"
}
}

The response status is HTTP 400. Search the audit trail for PII_OUTPUT_LEAK with the same workspace and time window to verify why delivery was refused.

Govern streamed responses​

Streaming inspection is on by default. In 1.8, the behavior depends on whether the selected action can withhold content:

  • LOG relays text as it arrives and records the final inspection summary.
  • BLOCK, REDACT, and TOKENIZE hold the complete response, inspect it once, and then release safe content or refuse it.
  • A blocked stream ends with finish_reason: content_filter and emits PII_BLOCKED_STREAMING.

The old scan-window and overlap settings are retained for configuration compatibility but do not control this whole-response enforcement path. The held response is limited to 1,000,000 characters by default. If it exceeds the bound, DVARA refuses deferred delivery instead of releasing partially inspected text and records STREAM_TOO_LARGE_TO_SCAN.

Review built-in detection​

The Open Source detector uses 16 built-in pattern definitions across 15 entity families, plus workspace-defined patterns. Phone numbers account for two definitions: US formats and international E.164.

GroupTypes
Contact and identityEmail, US and international phone, person name, date of birth
Government identifiersUS SSN, US passport, driver's licence, India Aadhaar, India PAN
FinancialCredit card, IBAN
HealthMedical record number, DEA number, NPI number
NetworkIPv4 address
CustomWorkspace-defined regular expressions

Credit cards and NPI numbers use Luhn validation, DEA numbers use their checksum, and Aadhaar uses Verhoeff validation. PAN is checked structurally. These checks reduce false matches from arbitrary digit strings.

Optional detectors can add broader names, locations, and unstructured entities. Their presence depends on the selected Enterprise artifact or on an embedding application supplying an implementation. A configured endpoint or filter list is not proof that the detector is running.

Add Presidio detection in Enterprise​

Use Presidio when the deterministic patterns are not enough for names, locations, organizations, or other context-dependent entities. Run a compatible Presidio Analyzer service, then set its /analyze endpoint:

dvara:
llm-gateway:
pii:
presidio:
endpoint: http://presidio-analyzer:3000/analyze
language: en
score-threshold: 0.5
timeout-seconds: 5

The endpoint is the switch: when it is present in the Enterprise platform, Presidio runs alongside the regex detector. You do not need to change provider from its regex default. DVARA merges overlapping findings and keeps the higher-confidence result.

Presidio is an additional, fail-open detection layer. If the analyzer times out, cannot be reached, or returns an invalid response, the request still receives the regex detector's result. Monitor the gateway warning and do not treat the configured endpoint as proof that NER inspected a request.

SettingDefaultMeaning
presidio.endpointunsetAnalyzer /analyze URL; an unset endpoint leaves Presidio off
presidio.languageenLanguage sent to the analyzer
presidio.score-threshold0.5Lowest accepted confidence score
presidio.timeout-seconds5HTTP timeout for one analysis call
presidio.cache-max-size1000Most texts whose analyzer result each Gateway pod keeps in memory; 0 or less turns the cache off
presidio.cache-ttl-seconds300Seconds a cached result is reused before the analyzer is asked again; a read does not extend it; 0 or less turns the cache off

Presidio analyzer results are cached locally by input text, so the same text is not sent to the analyzer again within the TTL. To send every text to the analyzer, set either cache setting to 0.

From 1.8.4 a failed analyzer call is never cached. A timeout, an error status or an unreadable answer still fails open for that scan, and the next scan of the same text asks the analyzer again. Up to 1.8.3 the failure was cached as "no PII", so that text skipped Presidio for the whole TTL.

At startup the Gateway logs the cache state: Presidio result cache on: up to 1000 texts for 300s each, or Presidio result cache off with the two settings.

Built-in US and international phone, card, SSN, and IPv4 patterns require a standalone numeric value rather than digits embedded in a longer identifier. A four-part version such as 10.2.30.4 is not treated as an IPv4 address. Workspace custom patterns still match exactly what the operator writes.

Set installation defaults​

Use these installation-wide settings:

dvara:
llm-gateway:
pii:
enabled: true
provider: regex
default-action: LOG
scan-responses: true
auto-detokenize-response: false
scan-streaming-responses: true
strip-before-cache: true
streaming-max-held-characters: 1000000
SettingDefaultMeaning
enabledtrueRun request inspection
providerregexUse the built-in deterministic detector
default-actionLOGRecord detections without changing traffic
scan-responsestrueInspect non-streaming responses
auto-detokenize-responsefalseRestore request-minted tokens on supported non-streaming responses when the action is TOKENIZE
scan-streaming-responsestrueInspect streamed responses
strip-before-cachetrueRemove PII before building or storing cache content
streaming-max-held-characters1000000Bound a response held for deferred enforcement

Cache protection always uses irreversible placeholders. It does not preserve a recoverable token, even when the workspace action is TOKENIZE.

Override settings by workspace​

In the Enterprise platform, configure a workspace from its Data Protection page. The stored workspace settings take precedence over installation defaults. Legacy metadata is used only for fields that do not have a stored value.

The main workspace fields are pii.enabled, pii.action, pii.scan-responses, pii.auto-detokenize-response, pii.degraded-action, and pii.custom-patterns.

Custom patterns must be a JSON object, not a JSON string:

{
"pii.custom-patterns": {
"employee_id": "EMP-\\d{6}",
"internal_project": "PROJ-[A-Z]{2,4}-\\d{4}"
}
}

Each pattern is reported as CUSTOM. Flightdeck rejects an invalid regular expression when you save it. Test valid patterns with representative safe and unsafe text before changing the action from LOG.

Upgrade safely to 1.8​

In 1.7, REDACT created a recoverable token. In 1.8, REDACT removes the value permanently and TOKENIZE owns the old reversible behavior.

The Enterprise upgrade migrates stored workspace actions from REDACT to TOKENIZE, including the legacy workspace metadata value. This preserves the behavior those workspaces already selected.

The installation-wide dvara.llm-gateway.pii.default-action is not migrated. If it was REDACT and you need the old reversible behavior, change it to TOKENIZE and verify the token store and key material before serving traffic. Leave it as REDACT when permanent removal is the intended result.

After the upgrade, test one request for every action in use and confirm the expected audit event before completing rollout.

Monitor PII audit events​

EventMeaning
PII_DETECTEDRequest PII was logged or blocked
PII_REDACTEDRequest PII was removed permanently
PII_TOKENIZEDRequest PII was replaced with a recoverable token
PII_OUTPUT_LEAKPII was detected in a model response
PII_RESPONSE_DETOKENIZEDRequest-created tokens were restored in a non-streaming response
PII_REDACT_DEGRADEDTokenization could not run; inspect attempted_action and degraded_action
PII_BLOCKED_STREAMINGA streamed response was refused by the PII action

For MCP traffic, the equivalent events start with MCP_PII_. A2A enforcement records its decisions on the A2A audit path. See Agentic governance for those planes.

Continue the governance path​

  • Add Policy as Code when model, token, or tool rules should run before PII enforcement.
  • Add guardrails for prompt injection, content safety, and response checks.
  • Use audit and observability to query evidence across the Enterprise platform.