Skip to main content
Column masking lets you define per-column data protection rules in your policy YAML. Masked columns are automatically redacted, hashed, partially hidden, or nullified in both query results and describe_table sample rows. No code changes, no database views, no application-layer middleware — just a YAML file.

Why column masking?

AI models are powerful data analysts, but they don’t need to see raw PII to write correct SQL. With column masking:
  • Emails, SSNs, phone numbers are redacted before the AI ever sees them
  • Query results are masked in real time — the AI gets useful structure without sensitive values
  • Sample rows in describe_table are also masked, so table analysis doesn’t leak PII
  • Masking is enforced server-side — the AI cannot bypass it, regardless of what SQL it generates

Enabling column masking

Column masking is part of the policy engine. Add mask directives to any column in your policy YAML:
Then point Isthmus at the file:
At startup, Isthmus logs how many columns are masked:

Mask types

There are four mask types. Each is designed for a different use case.

redact

Replaces the value with ***. Use for columns the AI never needs to see. Best for: email addresses, API keys, passwords, tokens.

hash

Replaces the value with a deterministic SHA-256 hex string (64 characters). Same input always produces the same hash, so the AI can still detect duplicates, join on hashed values, and count distinct entries — without seeing the original data. Best for: columns where the AI needs to detect patterns (GROUP BY, JOIN, COUNT DISTINCT) without seeing raw values. Names, external IDs, usernames.

partial

Reveals only the last 4 characters, replacing the rest with asterisks. Works correctly with unicode characters. Best for: phone numbers, credit card numbers, account numbers — where partial visibility helps the AI understand the format.

null

Replaces the value with NULL. The AI sees that the column exists but has no data. Best for: columns that must be completely hidden. The AI can still query other columns in the same table.

How masking works

Query results

When a query tool call returns results, Isthmus applies masks to every row before sending the response to the AI:
Without masking:
With email: redact and name: hash:

Describe table sample rows

The describe_table tool returns up to 5 sample rows. These are masked identically — same rules, same mask types:

Column name matching

Masking matches by column name only, not by table. If you mask email, it applies to every column named email in every query result — regardless of which table it comes from, including JOINs, subqueries, and aliases. This is by design. SQL queries with JOINs, CTEs, and subqueries make it impossible to reliably map result column names back to source tables. Rather than building a fragile runtime mapper, Isthmus uses a simple, predictable rule: same column name = same mask.

Conflict detection

Because masking is by column name, Isthmus validates at startup that no column name has conflicting mask types across tables. If two tables define different masks for the same column name, Isthmus rejects the policy file:
The same column name with the same mask across multiple tables is fine:

Full example

A realistic policy YAML with masking:
Note that email appears in both customers and employees with the same mask (redact), which is valid.

Type behavior

Masked values may change type. This is intentional and documented here for completeness:

Choosing the right mask type

Compliance context

Column masking helps satisfy data protection requirements across common regulatory frameworks:
Column masking is one layer of a data protection strategy — not a complete compliance solution. Combine it with a dedicated read-only database role, schema filtering, audit logging, and your organization’s data governance policies.

Interaction with other features

Limitations

  • Column name scope — masks match by column name globally, not per table. You cannot mask email differently in users vs. contacts. This is a deliberate tradeoff: simplicity and predictability over per-table granularity.
  • SQL aliases — if a query uses SELECT email AS contact_email, the result column is named contact_email, and the email mask will not apply. The AI could theoretically use aliases to bypass masking. Mitigate this with a dedicated read-only database role that restricts access to sensitive columns at the PostgreSQL level.
  • AggregationsSELECT COUNT(DISTINCT email) returns an integer count, not email values. Masking does not interfere with aggregations since the masked column is not in the result set.
  • WHERE clauses — masking does not affect query filters. SELECT id FROM users WHERE email = 'alice@example.com' executes against the real data. The AI can still filter by masked columns — it just cannot see the values in results.

Tips

  • Start with redact — it’s the safest default for PII columns.
  • Use hash when the AI needs to detect patterns — GROUP BY, JOIN, or COUNT DISTINCT still work on hashed values.
  • Use partial for phone numbers and account numbers — the last 4 digits help the AI understand the format without exposing the full value.
  • Use null for columns that should be completely invisible — salaries, SSNs, medical data.
  • Masking + business descriptions work together — the AI sees "description": "Primary email address" alongside "***", so it knows what the column is without seeing the values.
  • Mask liberally, describe generously — err on the side of masking more columns. The AI writes better SQL when it understands the schema (via descriptions) than when it sees raw data.
  • Think about JOINs — if you mask email in one table, mask it in all tables. Isthmus enforces this consistency at startup.
  • Combine with database-level controls — for maximum protection, mask columns in the policy YAML and revoke SELECT on those columns for the database role. This provides defense in depth.