---
name: HubSpot Redact PII
tags:
  - hubspot
  - pii
  - dlp
  - redaction
  - egress
publishedAt: 2026-06-16
description: |
  # hubspot / redact-pii

  **Direction:** egress (`tool_post_invoke`)
  **Default:** allow (transform-only — never denies)
  **Package:** `hubspot.egress.redact_pii`

  ## What it does

  Redacts sensitive contact information from HubSpot 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-HubSpot tool, and any
  request that isn't on the output path, passes through untouched.

  ## Why egress

  The risk is *reading* PII that lives in HubSpot CRM records (phone numbers,
  emails, etc.). 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 `hubspot-`, on the
  output path (`input.mode == "output"`). Confirm the exact tool names and the
  server-name prefix your gateway emits with the dump-input debug technique
  before relying on this in production; if your HubSpot MCP server is registered
  under a different prefix, adjust the `startswith` check.

  ## What gets redacted

  Redaction works two ways. **By field name** — the structured fields `phone`,
  `mobilephone`, `fax`, `email`, and `hs_email_domain`. And **by pattern** in any
  string value:

  - Formatted phone numbers (with separators, e.g. `555-666-7777`, `(555) 666.7777`, `+1-555-666-7777`)
  - Raw 10-digit phone numbers (`\b\d{10}\b`, bounded so it won't match inside longer HubSpot IDs)
  - Email addresses
  - US SSNs (`XXX-XX-XXXX`)

  Matches are replaced with `[REDACTED]`.

  ## Examples

  ### Redacted (HubSpot tool response)

  ```jsonc
  {
    "input": {
      "action": "tool_post_invoke",
      "mode": "output",
      "resource": { "name": "hubspot-list-objects", "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 HubSpot response"` so the
  redaction is explained in the dashboard.

  ### Passed through (non-HubSpot tool, or not output path)

  A response from a non-`hubspot-` 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 and
    non-standard shapes may be missed, and benign strings that look like a phone
    or email may be over-redacted. Treat this as a high-signal layer, not a
    complete DLP solution.
  - **Prefix-scoped.** Scoping is `startswith("hubspot-")`. A HubSpot MCP server
    registered 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:
  - hubspot
industries: []
bundles:
  - crm
schemaVersion: 1.0.0
minimumGatewayVersion: 1.0.0b24
---

```rego
package hubspot.egress.redact_pii

# Transform-only policy — never blocks, only redacts sensitive fields from HubSpot responses
default allow := true

transform := {
    "redact_patterns": [
        # Formatted phone numbers — requires at least one separator between digit groups
        # Matches: 555-666-7777, (555) 666.7777, +1-555-666-7777
        # Does NOT match raw digit strings like 5556667777 (handled below)
        "\\+?1?[\\s.\\-]?\\(?\\d{3}\\)?[\\s.\\-]\\d{3}[\\s.\\-]\\d{4}",
        # Raw 10-digit phone numbers (exactly 10 consecutive digits)
        # \b ensures it won't match inside longer numbers like 12-digit HubSpot IDs
        "\\b\\d{10}\\b",
        # Email addresses
        "[\\w.+\\-]+@[\\w.\\-]+\\.[a-zA-Z]{2,}",
        # SSNs (XXX-XX-XXXX)
        "\\b\\d{3}-\\d{2}-\\d{4}\\b"
    ],
    "redact_fields": ["phone", "mobilephone", "fax", "email", "hs_email_domain"],
    "replacement": "[REDACTED]"
} if {
    input.mode == "output"
    startswith(lower(input.resource.name), "hubspot-")
}

# Surfaced on the decision event whenever the redaction is in scope, so the
# dashboard can explain the rewrite.
reason := "PII redacted from HubSpot response" if {
    input.mode == "output"
    startswith(lower(input.resource.name), "hubspot-")
}
```
