Skip to content

Stored masks and pseudonymous joins

priva_map stores each readable value beside the exact pseudonymous value that represents it. A Unity Catalog column mask chooses between those two stored columns.

For a masked value, the rule is conceptually:

case
  when is_account_group_member('privacy_admins')
    or is_account_group_member('case_users')
  then raw_value
  else pseudonymous_value
end

Why the mask returns a stored key

Recomputing a key during every reader query would need access to the pepper and hashing function. That would expand secret use into the consumer path and make it easier to construct a candidate-value oracle.

Returning the stored companion key has simpler properties:

  • the materialization path is the only place that needs the secret-backed wrapper;
  • a restricted reader sees the same value already used by protected models;
  • the mask cannot drift because a query used a different canonicalization rule;
  • masked queries do not need UDF execution privilege.

Why joins use key columns directly

Column masks are applied according to the invoker's context. A join on a readable column could therefore compare different representations for different users.

The project avoids that ambiguity. Layer2 and case views join on stored pseudonymous columns such as customer_key, service_key, and service_version_key. They never depend on a masked raw column.

The mask and grant boundary are coupled

The mask function returns raw values to case_users so an authorized case view can resolve fields in the invoker's context. Safety therefore depends on a separate rule: case_users must not have direct SELECT on the mapping tables.

flowchart TD
    caseUser["case user"] --> caseView["approved case view"]
    caseView -->|full pseudonymous tuple| map["priva_map"]
    caseUser -. denied direct SELECT .-> map
    restricted["restricted user"] -->|direct SELECT, masked| map

If the direct grant drifts, a case user could read the whole raw map because the mask recognizes that group as readable. The access tests treat the mask and grant allowlist as one control system for this reason.

Why restricted users can query the map

restricted_users receive direct map access, but every one of the 13 raw-value columns should resolve to its stored pseudonymous partner. This makes the mapping relation useful for controlled key lookup without granting the readable values.

That behavior depends on live Unity Catalog metadata. A successful table build alone does not prove that every mask is attached; dedicated tests inspect the column-mask catalog.

See priva_map models for the 13 pairs and Access control for the exact direct-grant matrix.