--- name: Salesforce Redact PII tags: - salesforce - pii - dlp - redaction - egress publishedAt: 2026-06-16 description: | # salesforce / redact-pii **Direction:** egress (`tool_post_invoke`) **Default:** allow (transform-only — never denies) **Package:** `salesforce.egress.pii_redaction` ## What it does Redacts personal contact information from Salesforce tool responses before they reach the caller. It is transform-only — it never denies a call, it only rewrites matching content to `[REDACTED]`. Any non-Salesforce tool, and any request that isn't on the output path, passes through untouched. `Name` and `Account` are intentionally left intact so records stay usable — extend `redact_fields` (e.g. add `Name`, `FirstName`, `LastName`) if full-PII redaction is required. ## Why egress The risk is *reading* PII that lives in Salesforce records (emails, phone numbers, mailing address, birthdate). Those values exist regardless of this gateway, so there is nothing to block at ingress — the leak happens when the content is returned to an MCP client. Masking on the egress (response) path is the only place to catch it. ## Scope / tool matching Applies to any tool whose (lowercased) name starts with `salesforce-`, on the output path (`input.mode == "output"`). If your Salesforce MCP server is registered under a different prefix, adjust the `startswith` check. Confirm the exact tool names with the dump-input debug technique before deploying. ## What gets redacted **By field name** — `Email`, `Phone`, `MobilePhone`, `HomePhone`, `OtherPhone`, `AssistantPhone`, `Fax`, `MailingStreet`, `MailingCity`, `MailingState`, `MailingPostalCode`, `MailingAddress`, `Birthdate` (matched case-insensitively). And **by pattern** in any string value: email addresses, US phone numbers (formatted and raw 10-digit), US SSNs, and 16-digit credit-card numbers. Matches are replaced with `[REDACTED]`. ## Examples ### Redacted (Salesforce tool response) ```jsonc { "input": { "action": "tool_post_invoke", "mode": "output", "resource": { "name": "salesforce-soqlquery", "type": "tool" } } } ``` `allow = true`, with a `transform` supplying the redaction patterns, field names, and `replacement = "[REDACTED]"` for the gateway to apply to the response body, plus `reason = "PII redacted from Salesforce response"` so the redaction is explained in the dashboard. ### Passed through (non-Salesforce tool, or not output path) A response from a non-`salesforce-` tool, or any request not on the output path, returns `allow = true` with no `transform` — unchanged. ## Known limitations - **Regex over text.** Detection is pattern-based, so novel formats may be missed and benign strings that look like a phone/email/card may be over-redacted. Treat this as a high-signal layer, not a complete DLP solution. - **Name/Account preserved by design.** These are not redacted so records stay identifiable; extend `redact_fields` if you need them masked. - **Prefix-scoped.** Scoping is `startswith("salesforce-")`. A server under a different prefix won't be covered until the check is adjusted. - **No identity-based exemptions.** All callers get the same redaction. Add an `input.subject.claims`-gated branch if a break-glass role needs raw values. direction: egress apps: - salesforce industries: [] bundles: - crm schemaVersion: 1.0.0 minimumGatewayVersion: 1.0.0b24 --- ```rego package salesforce.egress.pii_redaction # Transform-only egress policy — never blocks, redacts PII from Salesforce responses. default allow := true transform := { "redact_patterns": [ # Email addresses "[\\w.+\\-]+@[\\w.\\-]+\\.[a-zA-Z]{2,}", # US phone numbers (formatted, requires separators between digit groups) "\\+?\\d{0,3}[\\s.\\-]?\\(?\\d{3}\\)?[\\s.\\-]\\d{3}[\\s.\\-]\\d{4}", # Raw 10-digit phone numbers (word-bounded to avoid matching longer IDs) "\\b\\d{10}\\b", # Social Security Numbers (XXX-XX-XXXX) "\\b\\d{3}-\\d{2}-\\d{4}\\b", # Credit card numbers (16 digits, optional separators) "\\b\\d{4}[\\s\\-]?\\d{4}[\\s\\-]?\\d{4}[\\s\\-]?\\d{4}\\b" ], "redact_fields": [ "Email", "Phone", "MobilePhone", "HomePhone", "OtherPhone", "AssistantPhone", "Fax", "MailingStreet", "MailingCity", "MailingState", "MailingPostalCode", "MailingAddress", "Birthdate" ], "replacement": "[REDACTED]" } if { input.mode == "output" startswith(lower(input.resource.name), "salesforce-") } # Surfaced on the decision event whenever the redaction is in scope, so the # dashboard can explain the rewrite. reason := "PII redacted from Salesforce response" if { input.mode == "output" startswith(lower(input.resource.name), "salesforce-") } ```