PII Redaction

June 19, 2026 · View on GitHub

Enable with ENABLE_PII_REDACTION=true. Masks likely PII in read-only query results before returning them.

How It Works

Detection combines:

  • Built-in column name list: email, ssn, phone, first_name, address, credit_card, password, api_key, token, etc.
  • Regex scanning of values: email, US phone, SSN, IPv4, Luhn-valid credit card numbers

Mask Examples

InputOutput
jane.doe@example.comj***@e***.com
415-555-0134***-***-0134
123-45-6789***-**-6789
4111 1111 1111 1111****-****-****-1111
192.168.1.42***.***.***.42
Generic PII column valueJ******** (first char + up to 8 asterisks)

Value-level pattern masks run first, so a column matching the heuristic whose value contains an email gets the richer email mask.

Redaction only runs on read-only query results. Schema/table listing and write-operation summaries are unaffected.

Extending Column Detection

Both vars require ENABLE_PII_REDACTION=true and are additive to the built-in list.

PII_EXTRA_COLUMNS — comma-separated substrings, case-insensitive, matched against lowercased column name:

PII_EXTRA_COLUMNS=image_url,signed_url,internal_note

PII_EXTRA_COLUMN_PATTERNS — semicolon-separated JS regex bodies (no / delimiters, compiled with i flag):

PII_EXTRA_COLUMN_PATTERNS=^(signed|protected)_.*;^.*_token$

Use patterns when a substring match is too broad — anchors, alternation, character classes, etc. Invalid patterns are logged to stderr and skipped.

A column is flagged if it matches ANY built-in substring OR any PII_EXTRA_COLUMNS substring OR any PII_EXTRA_COLUMN_PATTERNS regex.

Hardening Guards

When ENABLE_PII_REDACTION=true, three additional pre-execution guards activate:

PII_ALLOW_SELECT_STAR (default: false)

Rejects queries with SELECT * or t.*. Forces explicit column projection so PII columns can't slip through via wildcard. COUNT(*) and aggregates are unaffected.

Set true to allow wildcards (value-level redactor still runs).

PII_ALLOW_REFERENCES (default: false)

Rejects any query referencing a PII-matched column — in projection, WHERE, JOIN ON, GROUP BY, HAVING, ORDER BY, subqueries, CTEs, INSERT ... SELECT. Closes the alias bypass (SELECT CONCAT(first_name, ' ', last_name) AS NAME would otherwise return cleartext). Error message names the offending column(s) so the LLM can self-correct.

Set true to disable (e.g. for ETL pipelines that legitimately need PII columns).

PII_ALLOW_INTROSPECTION (default: false)

Controls schema introspection behavior:

  • Filtered (runs, PII rows dropped): SHOW COLUMNS, SHOW FULL COLUMNS, SHOW FIELDS, DESCRIBE, DESC, EXPLAIN <table>, SHOW INDEX, SHOW INDEXES, SHOW KEYS
  • Pass-through (runs unchanged): SHOW TABLES, SHOW TABLE STATUS, SHOW DATABASES, SHOW SCHEMAS, SHOW CHARACTER SET, SHOW COLLATION
  • Rejected: SHOW CREATE TABLE, SHOW CREATE VIEW, SELECT on information_schema or mysql

Use SHOW COLUMNS / DESCRIBE or the mysql://tables/{name} MCP resource instead of SHOW CREATE TABLE.

Set true to bypass entirely and return raw results.

PII_BLOCK_INTROSPECTION (default: false)

Stricter mode — hard-blocks ALL introspection statements including the pass-through kinds above. Matches pre-2.0.3 behavior. Ignored when PII_ALLOW_INTROSPECTION=true.

Known Limitations

  • Column aliases (when PII_ALLOW_REFERENCES=true): SELECT first_name AS fn returns key fn, bypassing column-name heuristic. Leave PII_ALLOW_REFERENCES=false to prevent this.
  • Numeric-typed PII: SSNs/phones stored as BIGINT/INT pass through — walker only inspects string values.
  • JSON columns: Regex-visible PII inside JSON strings (emails, phones, etc.) is masked, but nested fields like first_name inside JSON are not.
  • International formats: IPv6, non-US phones (E.164 +44…), IBANs, EU national IDs, international postal codes — not redacted.
  • Non-Luhn card numbers: 13–19 digit runs failing Luhn check are left alone (intentional, to avoid clobbering order IDs).
  • SHOW CREATE TABLE / information_schema SELECTs: Rejected by default. Use SHOW COLUMNS or the MCP resource instead.
  • Filtered introspection leaks column count/types of non-PII columns — by design (discovery is the point).
  • Write-operation summaries: Insert successful… strings are not scanned.
  • Binary/Buffer payloads and Date instances: Passed through as-is; embedded PII not inspected.