Skip to content

Compliance and Redaction

diegokoes edited this page Jul 5, 2026 · 1 revision

Compliance and PII redaction

An optional layer lets a deployment satisfy company AI-usage policies that forbid sending customer personal data or credentials to a model. It is off by default: with nothing configured, behavior is byte-for-byte unchanged.

Implementation: packages/core/src/compliance/redaction.ts.

Principle: redact at the MCP boundary

Redaction happens inside the fetch_work_item and get_context tool handlers, before any model sees the data. Because that boundary is provider-neutral, the same protection covers Claude, an organization's own LLM, or any future consumer.

It runs after ingestWorkItem (the full-data DB write) and resolveCustomerByEmail (email-domain matching), so:

  • storage keeps the real data on your own infrastructure, and
  • customer auto-matching still works.

Only the copy returned to the model is scrubbed.

Toggle (no schema change)

Per source connection, on the existing config JSONB:

// source_connections.config
{ "redaction": { "enabled": true } }   // absent or false → no redaction

A Freshdesk tenant is roughly one company, so per-connection is the natural granularity.

Or deployment-wide: TACHY_REDACT=true, or the redaction_global runtime setting. The deployment-wide switch also covers the connection-less tools (ingest_context, and retrieved search results that may carry PII saved before redaction was enabled), which have no source connection to read a policy from.

Two layers

  1. Source-agnostic. Scrubs the normalized RawWorkItem fields every adapter produces identically: requesterEmail dropped; requester replaced with the resolved customer slug; title and each messages[].bodyText run through a regex scrub (emails to [EMAIL_n], phone numbers to [PHONE_n]); message authors to [USER_n]. Tokens are stable per work item (the same value maps to the same token), so referential context survives.
  2. Source-specific. Each adapter implements an optional redactRaw() hook on WorkItemSource to scrub the source-specific raw payload, since only the adapter knows its field shape. Both layers share one token map.
Source raw fields scrubbed
Freshdesk email, phone, name; cc_emails / to_emails / fwd_emails / reply_cc_emails; twitter_id / facebook_id; the requester embed (email / mobile / phone / name); company.name; free-text subject / description / description_text; and the values of custom_fields (keys such as cf_devops_work_item are preserved).
GitHub actor login / email / name on user / assignee / assignees / closed_by; free-text title / body.

The "names are useful" workaround

Because the app already resolves the requester's email domain to a known customers row, the model is shown the stable customer slug (for example acme-corp) in place of the person's real name and email. The company signal is preserved; the individual PII is dropped. Retrieval stays effective without exposing a person.

Scope of the regex approach

Redaction is deterministic: structured fields plus email/phone regex. It does not run named-entity recognition, so a person's name typed free-form in a message body is only caught if it appears as an email or phone. All structured PII fields and all emails/phones are covered.

Redaction placeholders ([EMAIL_n], [PHONE_n], [USER_n], and the secret and card placeholders) are intentional and opaque. The agent is instructed never to reconstruct the originals, and never to write secrets or personal data into saved entries or reference docs (CLAUDE.md rule 17).

Note on Freshdesk messages

Freshdesk conversation entries already only expose body_text into the normalized messages. Per-message from_email / to_emails / cc_emails / bcc_emails / support_email are dropped by the adapter mapping and never reach a model, regardless of redaction mode.

Clone this wiki locally