diff --git a/gateway/1.13/modules/ROOT/pages/policies-included-mcp-pii-detector.adoc b/gateway/1.13/modules/ROOT/pages/policies-included-mcp-pii-detector.adoc index 1369a1d03d..46b8db76d4 100644 --- a/gateway/1.13/modules/ROOT/pages/policies-included-mcp-pii-detector.adoc +++ b/gateway/1.13/modules/ROOT/pages/policies-included-mcp-pii-detector.adoc @@ -3,12 +3,12 @@ ifndef::env-site,env-github[] include::_attributes.adoc[] endif::[] :imagesdir: ../assets/images -:keywords: api gateway, flex gateway, gateway, policy +:keywords: api gateway, flex gateway, gateway, policy, mcp, pii, presidio [width="100%", cols="5,15"] |=== >s| Policy Name | MCP PII Detector Policy ->s|Summary | Blocks elicitation responses containing personally identifiable information (PII) from reaching MCP servers +>s|Summary | Detects, masks, or blocks personally identifiable information (PII) in MCP traffic >s|Category | MCP >s|First Omni Gateway version available | v1.9.3 >s|Release Notes| xref:release-notes::gateway/policies/mcp-pii-detector-release-notes.adoc[] @@ -17,9 +17,11 @@ endif::[] == Summary -The MCP PII Detector policy detects personally identifiable information (PII) in Model Context Protocol (MCP) JSON-RPC traffic on both the request and response sides. +The MCP PII Detector policy detects personally identifiable information (PII) in Model Context Protocol (MCP) JSON-RPC traffic in both directions: the request direction (client to MCP server) and the response direction (MCP server to client). The policy scans and enforces each direction independently. -When PII is detected, you can configure the policy to reject the request or response, log findings, or log masked findings. The policy reports a policy violation when PII is detected. +The policy always runs a built-in regex engine. You can additionally configure an external https://microsoft.github.io/presidio/[Microsoft Presidio^] Analyzer service to run alongside the regex engine, which extends detection to entity types that have no local regex equivalent, such as person names and locations. + +When PII is detected, the policy can reject the traffic, log findings, or log findings and mask the PII in the response. You can set a single default action, or set per-entity actions through the `rules` configuration. The policy reports a policy violation when PII is detected. == Configuring Policy Parameters @@ -30,24 +32,125 @@ The MCP PII Detector policy isn't supported in Local Mode. include::partial$policy-title-headers.adoc[tag=ui] [%header%autowidth.spread,cols="a,a,a"] - |=== | Element | Required | Description -| PII Types | Yes | Array of built-in PII types to detect. Supported values are *Email*, *US SSN*, *Credit Card*, and *Phone Number*. For more information, see <>. -| Custom PII Patterns | No | Array of custom regex patterns to detect additional sensitive values. Define a name and regex pattern for each array entry. For example, use `name: AWS Access Key` and `pattern: AKIA[0-9A-Z]{16}` to detect AWS access keys. -| Action | No | Action to take when PII is detected. Supported values are *Reject*, *Log*, and *Log and mask*. Default is *Log*. +| PII Types | Yes | Array of PII types to detect. The regex engine recognizes only the built-in types (*Email*, *US SSN*, *Credit Card*, and *Phone Number*). When Presidio is configured, the full list is also sent to the Analyzer, so you can also use Presidio recognizer names, such as `EMAIL_ADDRESS`, `PERSON`, `LOCATION`, or `IBAN_CODE`. For more information, see <>. +| Custom PII Patterns | No | Array of custom patterns to detect additional sensitive values. The regex engine always uses these custom patterns. When Presidio is configured, each pattern is also sent to the Analyzer as an ad-hoc recognizer. For more information, see <>. +| Action | No | Default action to take when PII is detected, applied to both directions unless overridden by a rule. Supported values are *Reject*, *Log*, and *Log and mask*. Default is *Log*. For more information, see <>. +| Per-Entity Rules | No | Array of per-entity action overrides. When present, the first matching rule for an entity determines its action, and unmatched entities fall back to the top-level action. For more information, see <>. +| Allow List | No | Array of literal strings to exclude from detection (case-sensitive exact match). Useful for known non-PII values that match a pattern, such as `test@example.com` or `000-00-0000`. +| Presidio Integration | No | Presidio Analyzer connection settings. Configuring this block enables Presidio detection alongside the regex engine. For more information, see <>. +|=== + +[[custom-patterns]] +=== Custom PII Patterns + +Each entry in *Custom PII Patterns* defines a pattern to detect values beyond the built-in types. When Presidio is configured, the policy sends each pattern to the Analyzer as an ad-hoc recognizer, which is a custom detection rule that Presidio applies only for that request. This means both engines detect the pattern. + +[%header%autowidth.spread,cols="a,a,a"] +|=== +| Field | Required | Description +| `name` | Yes | A descriptive name that identifies this pattern in detection results. Used as the entity type for regex-engine matches and as the recognizer name sent to the Analyzer. +| `pattern` | Yes | A regular expression pattern to match sensitive data. For example, use `AKIA[0-9A-Z]{16}` to detect AWS access keys. +| `supportedLanguage` | No | ISO 639-1 language code (for example, `en`) that this pattern applies to when sent to the Analyzer. Ignored by the regex engine. Defaults to `presidio.language`. +| `context` | No | Array of context words that boost this pattern's detection score in the Analyzer. Ignored by the regex engine. +| `score` | No | Base confidence score (`0.0`–`1.0`) for matches from this pattern in the Analyzer. Ignored by the regex engine. Default is `0.5`. +|=== + +[[per-entity-rules]] +=== Per-Entity Rules + +Each entry in *Per-Entity Rules* overrides the top-level action for a specific entity type. Rules apply to findings from both the regex engine and Presidio. When rules are present, the first matching rule for an entity determines its action, and entities that no rule matches fall back to the top-level `action`. + +[%header%autowidth.spread,cols="a,a,a"] +|=== +| Field | Required | Description +| `entity` | Yes | Entity type this rule applies to. Accepts either spelling, for example `Email` or `EMAIL_ADDRESS`, or `PERSON`. Built-in types are matched regardless of spelling, so a rule written as `Email` also matches a Presidio-origin `EMAIL_ADDRESS` finding. +| `action` | Yes | Action to take for this entity. Supported values are *Reject*, *Log*, and *Log and mask*. +| `direction` | No | Which direction this rule applies to: `request`, `response`, or `both`. Default is `both`. Combining `direction: request` (or `both`) with `action: Log and mask` has no effect in the request direction. For more information, see <>. +|=== + +[[presidio]] +=== Presidio Integration + +Configuring the *Presidio Integration* block enables the Presidio engine alongside the always-on regex engine. Omit the block to keep the policy in regex-only mode. + +[%header%autowidth.spread,cols="a,a,a"] |=== +| Field | Required | Description +| `analyzerUrl` | Yes | Base URL of the Presidio Analyzer service, for example `http://presidio-analyzer:3000`. The policy calls `POST {analyzerUrl}/analyze`. +| `apiKey` | No | Presidio has no built-in authentication. This key authenticates against the authentication service deployed in front of the Analyzer. When set, the key is sent as an `Authorization: Bearer` header on every Analyzer call. Omit if no authentication is required. +| `language` | No | ISO 639-1 language code for detection, for example `en`, `es`, or `de`. Passed to the Analyzer's `language` parameter. Default is `en`. +| `scoreThreshold` | No | Minimum confidence score (`0.0`–`1.0`) for a detection to count as PII. Findings below this threshold are ignored. Default is `0.5`. +| `contextWords` | No | Array of context words that boost detection confidence, passed to the Analyzer's `context` parameter. For example, `["employee", "patient", "customer"]` increases the likelihood of detecting names and IDs. +| `onServerError` | No | Behavior when the Analyzer is unreachable or times out. *Ignore* logs a warning, emits `X-PII-Scan: fallback-regex`, and proceeds using only the regex engine's findings for that direction. *Reject* rejects traffic with HTTP 503. Default is *Ignore*. +| `timeout` | No | HTTP timeout in milliseconds for Analyzer calls. If exceeded, the call is treated as unreachable and subject to `onServerError`. Adjust appropriately for remote and co-located-off-cluster Presidio deployments with higher round-trip latency. Default is `1500`. +|=== + +=== Configuration Example + +This example detects built-in and Presidio entity types, masks emails in the response direction, always rejects credit cards, and falls back to the regex engine if the Analyzer is unreachable: + +[source,yaml] +---- +- policyRef: + name: mcp-pii-detector-policy-flex + config: + entities: + - EMAIL_ADDRESS + - PERSON + - CREDIT_CARD + action: Log + customPatterns: + - name: "Employee ID" + pattern: "EMP-[0-9]{6}" + rules: + - entity: EMAIL_ADDRESS + action: Log and mask + direction: response + - entity: CREDIT_CARD + action: Reject + direction: both + allowList: + - "test@example.com" + presidio: + analyzerUrl: "http://presidio-analyzer:3000" + language: en + scoreThreshold: 0.7 + onServerError: Ignore + timeout: 1500 +---- == How This Policy Works -The MCP PII Detector policy is symmetric: it scans both request and response sides of MCP JSON-RPC traffic for PII. It recursively inspects all string values in both `params.*` (for requests) and `result.*` (for responses). +The MCP PII Detector policy is symmetric: it scans both the request and response sides of MCP JSON-RPC traffic for PII. It recursively inspects all string values in both `params.*` (for requests) and `result.*` (for responses). The policy scans: -* *JSON-RPC requests*: All string values under `params.*` from client to server -* *JSON-RPC responses*: All string values under `result.*` from server to client, including nested objects and arrays -* *Elicitation responses*: JSON-RPC responses sent from client back to server (MCP elicitation pattern) -* *SSE streams*: Each `data:` payload in Server-Sent Events (MCP Streamable HTTP transport) +* *JSON-RPC requests*: All string values under `params.*` from client to server. +* *JSON-RPC responses*: All string values under `result.*` from server to client, including nested objects and arrays. +* *Elicitation responses*: JSON-RPC responses sent from client back to server (MCP elicitation pattern). +* *SSE streams*: Each `data:` payload in Server-Sent Events (MCP Streamable HTTP transport). + +Non-JSON bodies, binary content, and non-`POST` requests pass through unscanned. A request with a malformed or absent `Content-Type` header is treated as non-JSON and passed through unscanned. + +=== Detection Engines + +The policy always runs a built-in regex engine. When you configure the `presidio` block, the Presidio Analyzer runs in addition to the regex engine. In every scanned direction: + +. The regex engine runs, built from the built-in types in `entities` and from `customPatterns`. +. If Presidio is configured, the Analyzer also scans the same values, using the full `entities` list and the `customPatterns` (forwarded to the Analyzer). +. The two engines' findings are merged and deduplicated by entity type and matched value. A Presidio finding is skipped if the regex engine already recorded a finding with the same canonical entity type and value. +. Any finding whose matched value is in the `allowList` is dropped from the merged set. +. Each remaining finding's action is resolved the same way, regardless of which engine produced it. The first `rules` entry matching both the finding's entity type and the direction wins, and if no rule matches, the finding falls back to the top-level `action`. +. The overall action for the direction is the most severe action across all findings (`Reject` > `Log and mask` > `Log`). A single reject-worthy finding upgrades the whole direction. + +Because both engines feed the same resolution logic, `rules` and `action` apply uniformly whether a finding came from the regex engine, from Presidio, or from both. + +The policy detects overlapping PII patterns and keeps the longest (most specific) match. For example, a credit card number with delimiters that could also match an SSN pattern is detected as a credit card. + +[[action-enforcement]] +=== Action Enforcement [%header,cols="1,2,2,2"] |=== @@ -57,32 +160,43 @@ The policy scans: |Log and Mask |Request processing -|Blocks the request with HTTP 403 error response. The upstream server never receives the request. -|Forwards the request and logs findings with actual PII values. -|Forwards the request and logs findings with masked values. +|Blocks the request with an HTTP 403 error response. The upstream server never receives the request. +|Forwards the request and logs the finding. +|No-op in the request direction. Forwards the request unmodified and logs the finding. |Response processing (application/json) -|Blocks the response with HTTP 403 error response. The client doesn't receive the original response containing PII. -|Forwards the response and logs findings with actual PII values. -|Rewrites every string under `result` with masked values and forwards the sanitized response. +|Blocks the response with an HTTP 403 error response. The client doesn't receive the original response containing PII. +|Forwards the response and logs the finding. +|Rewrites every detected PII span under `result` with a masked value and forwards the sanitized response. -|Elicitation responses (client → server) +|Elicitation responses (client to server) |Rewrites the result to indicate the response was declined and forwards the sanitized body to the server. -|Forwards the response and logs findings. -|Forwards the response and logs masked findings. +|Forwards the response and logs the finding. +|No-op. Forwards the response unmodified and logs the finding. |SSE streams (text/event-stream) |Entire stream is replaced with a single JSON-RPC error envelope and Content-Type changes to application/json. -|Forwards the stream unchanged and logs findings. -|Each event's result strings are rewritten with masked variants while preserving SSE framing. +|Forwards the stream unchanged and logs the finding. +|Rewrites each event's result strings with masked values while preserving SSE framing. |=== -The policy detects overlapping PII patterns and keeps the longest (most specific) match. For example, a credit card number with delimiters that could also match an SSN pattern is detected as a credit card. +=== Evidence Headers + +Whenever any PII is found in either direction, the policy emits the following headers on the response, regardless of which engine produced the findings: + +[%header%autowidth.spread,cols="a,a"] +|=== +| Header | Value +| `X-PII-Detected` | `true`. +| `X-PII-Entities` | CSV of `:`, for example `EMAIL_ADDRESS:2,CREDIT_CARD:1`, using Presidio-style entity names. +| `X-PII-Action` | The resolved action for the direction: `blocked`, `redacted`, or `audited`. +| `X-PII-Scan` | `fallback-regex`. Emitted only when Presidio is configured but the Analyzer was unreachable in this direction and `onServerError: Ignore` proceeded using the regex engine's findings alone. A request-direction fallback is carried forward and attached to the corresponding response. +|=== [[pii-types]] == PII Types -When you configure an MCP PII Detector policy, you can choose which types of PII to detect: +When you configure an MCP PII Detector policy, you choose which types of PII to detect. The regex engine recognizes the built-in types listed in the following table. When Presidio is configured, you can additionally list any recognizer name that the Analyzer supports, such as `PERSON`, `LOCATION`, or `IBAN_CODE`, and the available set depends on the Analyzer's loaded recognizers. [%header,cols='1a,5a'] |=== @@ -127,34 +241,34 @@ This example shows a request containing PII and the corresponding log output for Log:: + -- -When the action is set to *Log*, the policy logs detect PII with the actual values: +When the action is set to *Log*, the policy forwards the request and logs the detected entity types and counts: [source,text] ---- -[accessLog] PII detected in request (log only): [{"pii_type": "Email", "value": "john.doe@example.com", "start": 0, "end": 20}, {"pii_type": "Phone", "value": "(555) 123-4567", "start": 0, "end": 14}, {"pii_type": "SSN", "value": "123-45-6789", "start": 0, "end": 11}] +[accessLog] PII detected in request (log only): EMAIL_ADDRESS:1,PHONE_NUMBER:1,US_SSN:1 ---- -- Log and Mask:: + -- -When the action is set to *Log and mask*, the policy logs detect PII with masked values: +In the request direction, *Log and mask* behaves the same as *Log*: masking has no effect in the request direction. The policy forwards the request unmodified and logs the detected entity types and counts: [source,text] ---- -[accessLog] PII detected in request (log only): [{"pii_type": "Email", "masked_value": "****@example.com", "start": 0, "end": 20}, {"pii_type": "Phone", "masked_value": "+1******567", "start": 0, "end": 14}, {"pii_type": "SSN", "masked_value": "***-**-*789", "start": 0, "end": 11}] +[accessLog] PII detected in request (log only): EMAIL_ADDRESS:1,PHONE_NUMBER:1,US_SSN:1 ---- -- Reject:: + -- -When the action is set to *Reject*, the policy blocks the request, logs masked findings, and returns an HTTP 403 error response: +When the action is set to *Reject*, the policy blocks the request, logs the finding, and returns an HTTP 403 error response: Log output: [source,text] ---- -[accessLog] PII detected in request and declined: [{"pii_type": "Email", "masked_value": "****@example.com", "start": 0, "end": 20}, {"pii_type": "Phone", "masked_value": "+1******567", "start": 0, "end": 14}, {"pii_type": "SSN", "masked_value": "***-**-*789", "start": 0, "end": 11}] +[accessLog] PII detected in request and declined: EMAIL_ADDRESS:1,PHONE_NUMBER:1,US_SSN:1 ---- Error response: @@ -171,16 +285,16 @@ Error response: "phase": "request", "policy": "mcp-pii-detector", "findings": [ - { "pii_type": "Email", "masked_value": "****@example.com" }, - { "pii_type": "Phone", "masked_value": "+1******567" }, - { "pii_type": "SSN", "masked_value": "***-**-*789" } + { "pii_type": "EMAIL_ADDRESS" }, + { "pii_type": "PHONE_NUMBER" }, + { "pii_type": "US_SSN" } ] } } } ---- -The upstream server never receives the original request containing PII. +The upstream server never receives the original request containing PII. The error `findings[]` array carries only the entity type, never the matched value, masked or raw. NOTE: Request-side rejections use error code `-32602` (Invalid params) to indicate a client error, while response-side rejections use error code `-32603` (Internal error) to indicate the server response could not be safely returned. -- @@ -211,18 +325,18 @@ This example shows a server response containing PII and the corresponding behavi Log:: + -- -When the action is set to *Log*, the policy forwards the response and logs detected PII with actual values: +When the action is set to *Log*, the policy forwards the response and logs the detected entity types and counts: [source,text] ---- -[accessLog] PII detected in server response (log only): [{"pii_type": "Email", "value": "john.doe@example.com", "start": 14, "end": 34}, {"pii_type": "SSN", "value": "123-45-6789", "start": 41, "end": 52}] +[accessLog] PII detected in server response (log only): EMAIL_ADDRESS:1,US_SSN:1 ---- -- Log and Mask:: + -- -When the action is set to *Log and mask*, the policy rewrites the response with masked values and logs masked findings: +When the action is set to *Log and mask*, the policy rewrites the response with masked values and logs the finding: Response sent to client: [source,json] @@ -234,7 +348,7 @@ Response sent to client: "content": [ { "type": "text", - "text": "User contact: ****@example.com, SSN: ***-**-*789" + "text": "User contact: ********@example.com, SSN: ***-**-*789" } ] } @@ -244,19 +358,21 @@ Response sent to client: Log output: [source,text] ---- -[accessLog] PII detected in server response (masked in body): [{"pii_type": "Email", "masked_value": "****@example.com", "start": 14, "end": 34}, {"pii_type": "SSN", "masked_value": "***-**-*789", "start": 41, "end": 52}] +[accessLog] PII detected in server response (masked in body): EMAIL_ADDRESS:1,US_SSN:1 ---- + +The email local part is fully masked with asterisks while the domain is preserved, and the SSN keeps only its last three digits and its separators. -- Reject:: + -- -When the action is set to *Reject*, the policy blocks the response, logs masked findings, and returns an HTTP 403 error: +When the action is set to *Reject*, the policy blocks the response, logs the finding, and returns an HTTP 403 error: Log output: [source,text] ---- -[accessLog] PII detected in server response and declined: [{"pii_type": "Email", "masked_value": "****@example.com", "start": 14, "end": 34}, {"pii_type": "SSN", "masked_value": "***-**-*789", "start": 41, "end": 52}] +[accessLog] PII detected in server response and declined: EMAIL_ADDRESS:1,US_SSN:1 ---- Error response: @@ -273,8 +389,8 @@ Error response: "phase": "response", "policy": "mcp-pii-detector", "findings": [ - { "pii_type": "Email", "masked_value": "****@example.com" }, - { "pii_type": "SSN", "masked_value": "***-**-*789" } + { "pii_type": "EMAIL_ADDRESS" }, + { "pii_type": "US_SSN" } ] } } @@ -287,7 +403,7 @@ The client does not receive the original response containing PII. === Elicitation Response Example -When a client sends a JSON-RPC response back to the server (MCP elicitation pattern) containing PII, and the action is set to *Reject*, the policy rewrites the result: +When a client sends a JSON-RPC response back to the server (MCP elicitation pattern) containing PII, and the action is set to *Reject*, the policy rewrites the result rather than returning an HTTP error, because there is no request awaiting a reply: Original elicitation response: [source,json] @@ -296,12 +412,10 @@ Original elicitation response: "jsonrpc": "2.0", "id": 1, "result": { - "content": [ - { - "type": "text", - "text": "User SSN is 123-45-6789" - } - ] + "action": "accept", + "content": { + "email": "john.doe@example.com" + } } } ---- @@ -342,24 +456,70 @@ With a custom pattern configured (`name: AWS Access Key`, `pattern: AKIA[0-9A-Z] [source,text] ---- -[accessLog] PII detected in request (log only): [{"pii_type": "AWS Access Key", "value": "AKIAIOSFODNN7EXAMPLE", "start": 0, "end": 20}] +[accessLog] PII detected in request (log only): AWS Access Key:1 +---- + +== Presidio Analyzer Integration + +When the `presidio` block is configured, the policy calls a co-deployed https://microsoft.github.io/presidio/[Microsoft Presidio^] Analyzer service in addition to the regex engine. Presidio extends detection to entity types that have no local regex equivalent, such as `PERSON` and `LOCATION`, and applies named-entity recognition that a static regex cannot. + + +=== Failure Handling + +If the Analyzer is unreachable (network failure, timeout, non-2xx response, or malformed response) in a scanned direction, the policy applies the configured `onServerError` behavior: + +* *Ignore* (default): The policy logs a warning, proceeds using only the regex engine's findings for that direction, and emits the `X-PII-Scan: fallback-regex` header. Because the regex engine always runs, this is a genuine fallback rather than a cold start. Entity types with no regex equivalent, such as `PERSON`, go undetected for the duration of the outage. +* *Reject*: The policy rejects the traffic with HTTP 503 and does not attempt a fallback scan. + +Example log output for `onServerError: Ignore`: + +[source,text] +---- +Presidio Analyzer unreachable on request leg (Presidio service unavailable: ...); onServerError: Ignore, proceeding with the regex engine's findings only +---- + +Example error response for `onServerError: Reject`: + +[source,json] +---- +{ + "jsonrpc": "2.0", + "id": 5, + "error": { + "code": -32603, + "message": "Internal error", + "data": { + "reason": "pii_scan_unavailable", + "policy": "mcp-pii-detector" + } + } +} ---- +The `Reject` response is returned with HTTP 503 Service Unavailable. + +[TIP] +==== +Deployments that use `onServerError: Reject` should generally set a tighter `timeout` than the 1500 ms default: a long timeout before rejecting compounds a fail-closed outage with added latency on every request during it. +==== + == Server-Sent Events (SSE) Considerations When the MCP server uses Server-Sent Events (text/event-stream) for streaming responses, the policy buffers and processes the entire stream: -* *Log*: The stream is forwarded unchanged with raw PII values logged. +* *Log*: The stream is forwarded unchanged and the finding is logged. * *Log and mask*: Each event's result strings are rewritten with masked values while preserving SSE framing (event names, IDs, comments). * *Reject*: The entire stream is replaced with a single JSON-RPC error envelope, and the Content-Type changes to application/json with HTTP 403 status. +Detection runs once per event, and the overall resolved action for the response is the most severe action across every event. If the Analyzer fails on any single event with `onServerError: Reject`, the entire response is rejected. + IMPORTANT: SSE buffering is suitable for short-lived tool-call responses but might not be appropriate for long-lived server-pushed streams. For notification streams or progress updates, use the *Log* action to avoid buffering. NOTE: When *Reject* is applied to an SSE stream, the response Content-Type changes from text/event-stream to application/json. Clients that strictly require SSE formatting might need to handle this change. == Compliance and Data Flow -This table shows where raw PII values appear based on the configured action: +This table shows where raw PII values can appear based on the configured action: [%header,cols="1,2,2"] |=== @@ -368,19 +528,19 @@ This table shows where raw PII values appear based on the configured action: |Access Log |Reject -|Masked (findings in error.data.findings[].masked_value) -|Masked +|Never. The body is replaced with an error envelope whose `findings[]` carries only entity types. +|Never. Only entity types and counts are logged. |Log and mask -|Masked (rewrites response body) or unchanged (forwards request unchanged) -|Masked +|Masked in the response direction (body rewritten), and unchanged in the request direction (has no effect, forwarded as-is). +|Never. Only entity types and counts are logged. |Log -|Forwarded unchanged (raw PII flows through) -|Raw +|Forwarded unchanged (raw PII flows through). +|Never. Only entity types and counts are logged. |=== -IMPORTANT: *Reject* is the strict mode that never writes raw PII values anywhere (wire or log). If you need raw values in logs for diagnostics, use the *Log* action instead. +IMPORTANT: The access log never contains PII values under any action, and error envelopes never contain PII values. Raw PII is only ever present in the proxied traffic itself under the *Log* action (and, in the response direction, in the masked form under *Log and mask*). NOTE: For the A2A PII Detector policy, the *Log and mask* action only masks the access log, not the request body. The MCP PII Detector's *Log and mask* action rewrites response bodies but forwards request bodies unchanged. @@ -388,4 +548,4 @@ NOTE: For the A2A PII Detector policy, the *Log and mask* action only masks the * xref:policies-included-a2a-pii-detector.adoc[A2A PII Detector Policy] * xref:policies-included-llm-pii-detection.adoc[LLM PII Detection Policy] - +* https://microsoft.github.io/presidio/[Microsoft Presidio documentation^]