DTwo Policy Store

Salesforce Redact PII

Redacts personal contact information from Salesforce tool responses before they reach the caller.

Direction
egress
Rego package
salesforce.egress.pii_redaction
App
salesforce
Bundle
crm
Published
Minimum gateway
1.0.0b24
Schema version
1.0.0
Checksum
sha256:c3aa10e98fcac51fcdab8d099177f8e0d2275eb0251313d68e7fb992d091bed3

salesforcepiidlpredactionegress

What this policy does

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 nameEmail, 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)

{
  "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.

Policy source (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-")
}

Canonical source: policy.md on GitHub · raw · raw on this site (.md)

Used in these guides