FutrixData logo FutrixData
PII masking

PII Masking for AI Agents — Field-Level Sensitivity Classification

On the agent result path, FutrixData replaces sensitive result values with secret-derived, salted pseudonymous hash strings before returning the row to MCP, Skill, or CLI callers. The substitution uses a secret-backed deterministic hashing algorithm, with the key derived from a local secret stored in OS-level secret storage. Determinism is the point: agents can still join, group, and count without ever seeing raw values.

What PII masking means in an AI context

Traditional masking rewrites a value before storage or before it appears on a UI. PII masking for AI agents moves the rewrite to the data egress — the moment a row leaves your database for an LLM context. The agent still gets a row with the correct format, the right joins, and stable equality semantics; it just gets masked:v1:8f3a1c9b72e04d11 instead of alice@example.com.

This matters because once data enters an agent's context, it can flow on to external models, plugins, log systems, debug captures, and token relay services — places enterprise governance does not reach.

Five-level sensitivity classification (extensible)

FutrixData ships with an L1–L5 sensitivity model. Levels are configurable; defaults are below.

LevelMeaningExamples
L1 PublicNon-sensitive business dataid, status, created_at
L2 InternalInternal identifiers and metadatauser_id, session_id, request_id
L3 ConfidentialIndirect personal info, behavior, locationip_address, user_agent, device_id
L4 SensitiveDirect personal info, financial, medicalemail, phone, salary, date_of_birth
L5 CriticalCredentials, payment instruments, high-sensitivity PIIpassword, credit_card, api_secret, home_address

By default, agents receive L1–L3 in the clear. L4 and L5 are replaced with stable hashes before reaching the agent. The agent-accessible range is editable — you can, for example, restrict an agent to L1–L2 only.

How FutrixData masks on the agent result path

Masking is computed deterministically per value:

  1. Convert the raw field value to a string.
  2. Derive a per-(data source, field) HMAC key from a local secret kept in OS-level secret storage. The local secret is generated at first launch, stays on the host, and never leaves it; the same raw value in a different data source or different field produces a different hash.
  3. Compute HMAC-SHA256 over the raw value with that derived key.
  4. Take the first 16 hexadecimal characters.
  5. Prepend masked:v1: and return the masked value to the agent.

The same input produces the same key-derived output inside an install, so agents can join, group by, count distinct, and reason about equality without ever seeing the raw value. The mapping is one-way without the local secret. Deterministic masking reduces leakage surface — it is not full anonymization. See masking limitations for the inverse.

Two classification paths

FutrixData supports two ways to assign sensitivity levels — both writing to the same classification store.

Compliance angle

GDPR Article 5(1)(c) requires data minimization — processing only what's necessary. The European Data Protection Board has clarified that pseudonymized data remains personal data when it can still be linked through additional information. PII masking does not "make compliance disappear"; it reduces the leakage surface and makes data egress easier to audit and govern. The audit log records which columns were masked on each query, which gives the security team something concrete to attest to.

Examples

Row before and after masking
// Raw row from PostgreSQL
{ "id": 1042, "email": "alice@example.com", "phone": "+14155550123", "status": "active" }

// Row delivered to the agent (default L1–L3 access)
{ "id": 1042, "email": "masked:v1:8f3a1c9b72e04d11", "phone": "masked:v1:b219ac74e1d908f2", "status": "active" }
Schema-only sensitivity classification request (no row data leaves)
{
  "data_source": "prod-postgres",
  "table": "users",
  "fields": [
    { "name": "id",       "type": "bigint" },
    { "name": "email",    "type": "varchar(254)" },
    { "name": "phone",    "type": "varchar(20)" },
    { "name": "status",   "type": "varchar(16)" }
  ]
}

Frequently asked questions

What fields get masked by default?

L4 (email, phone, salary, date_of_birth, etc.) and L5 (password, credit_card, api_secret, home_address) are masked before reaching the agent. L1–L3 are returned in the clear. You can edit the accessible range per agent.

Are masked values reversible?

Not without the local key. Masking is a secret-backed deterministic HMAC-SHA256; the HMAC key is derived per (data source, field) from a local secret stored in OS-level secret storage and never leaves the host. The same input produces the same hash value inside an install, but reversing a hash requires the local key — and because FutrixData truncates to the first 16 hex characters, the collision probability is extremely low and effective reversal is not practical. One important caveat: low-cardinality input spaces (booleans, gender, age in years, small enums) remain vulnerable to enumeration — anyone who can submit known inputs through the gateway, or who holds the local secret, can build a lookup table for such fields. The Console (authorized human) path returns raw rows by default; only the agent result path is masked. See limitations — deterministic masking reduces leakage surface, it does not anonymize a dataset for public release.

Does masking break joins and aggregations?

No. The same input produces the same masked output, so joins, group-bys, count-distinct, and equality predicates still work on masked columns. You lose ordering and substring operations on masked fields — those should run inside the proxy, not in the agent.

Can I customize sensitivity levels?

Yes. Level names, descriptions, example fields, and the agent-accessible range are editable. You can also add per-source or per-table overrides — for example, classify orders.notes as L4 in production and L1 in the staging copy.

Is hashed data still personal data under GDPR?

Generally yes. The European Data Protection Board treats pseudonymized data as personal data when it can be linked back through additional information. Masking is a leakage-surface reduction, not a magic compliance switch — but it is a defensible technical measure under GDPR Article 32.

What's the performance overhead?

Per-row masking is one secret-backed HMAC per sensitive field — sub-millisecond for typical row sizes. The dominant cost on most queries is the database itself and the LLM round-trip, not the masking step.

Does FutrixData send schema metadata to LLM providers?

It can, on the AI Chat / schema knowledge / table-relationship assistance paths — and it is a separate path from query result rows. Schema metadata may include table names, column names, types, indexes, and comments. Result masking does not automatically cover schema metadata, because the schema is the structure the AI needs to understand, not the data. The desktop app exposes a per-data-source toggle for schema-to-LLM transmission, and each transmission is recorded so you can audit it. If your schema itself encodes business secrets (KYC tables, regulatory codes, internal product names), disable the toggle for that data source. See trust for the full data-path map.

Does masking apply to the desktop Console?

No — by design. The Console is the path for authorized humans running queries. The engineer at the keyboard is the inspector. Masking applies on the agent result path (MCP, Skill, agent CLI). If you do not want a given user to see raw values, control access at the data source itself, not at the Console.

Try FutrixData on your own database

Free desktop app on macOS, Windows, and Linux. Self-hosted Enterprise Edition for production deployments.