Skip to Content

Data masking

With data masking, you can replace sensitive member values with masked alternatives instead of hiding them entirely. Users who don’t have full access to a member will see a transformed value (e.g., ***, -1, NULL) rather than receiving an error.

Data masking extends member-level security. While member-level security controls whether a member is accessible, data masking controls what value is returned when a member is not fully accessible.

Defining masks

You can define a mask parameter on any dimension or measure. The mask specifies the replacement value used when the member is masked by an access policy.

Masks can be:

  • Static values: numbers, booleans, or strings
  • SQL expressions: dynamic masks using sql that can reference cube columns
cubes: - name: orders # ... dimensions: - name: secret_code sql: secret_code type: string mask: sql: "CONCAT('***', RIGHT({CUBE}.secret_code, 3))" - name: revenue sql: revenue type: number mask: -1 - name: is_vip sql: is_vip type: boolean mask: FALSE measures: - name: count type: count mask: 0
cube(`orders`, { // ... dimensions: { secret_code: { sql: `secret_code`, type: `string`, mask: { sql: (CUBE) => `CONCAT('***', RIGHT(${CUBE}.secret_code, 3))` } }, revenue: { sql: `revenue`, type: `number`, mask: -1 }, is_vip: { sql: `is_vip`, type: `boolean`, mask: false } }, measures: { count: { type: `count`, mask: 0 } } })

If no mask is defined on a member, the default mask value is NULL. You can customize the default mask for each data type using environment variables:

  • CUBEJS_ACCESS_POLICY_MASK_STRING — default mask for string members
  • CUBEJS_ACCESS_POLICY_MASK_NUMBER — default mask for number members
  • CUBEJS_ACCESS_POLICY_MASK_BOOLEAN — default mask for boolean members
  • CUBEJS_ACCESS_POLICY_MASK_TIME — default mask for time members

Configuring masking in access policies

Use the member_masking parameter in an access policy to specify which members should return masked values. The member_masking parameter works alongside member_level — it requires member_level to be defined in the same policy.

Members listed in member_level get full access (real values). Members not in member_level but included in member_masking get masked values. Members in neither are denied entirely.

cubes: - name: orders # ... dimensions: - name: status sql: status type: string - name: secret_code sql: secret_code type: string mask: sql: "CONCAT('***', RIGHT({CUBE}.secret_code, 3))" - name: revenue sql: revenue type: number mask: -1 measures: - name: count type: count mask: 0 access_policy: - group: manager member_level: includes: - status - count member_masking: includes: "*"
cube(`orders`, { // ... dimensions: { status: { sql: `status`, type: `string` }, secret_code: { sql: `secret_code`, type: `string`, mask: { sql: (CUBE) => `CONCAT('***', RIGHT(${CUBE}.secret_code, 3))` } }, revenue: { sql: `revenue`, type: `number`, mask: -1 } }, measures: { count: { type: `count`, mask: 0 } }, access_policy: [ { group: `manager`, member_level: { includes: [ `status`, `count` ] }, member_masking: { includes: `*` } } ] })

With this policy, users in the manager group will see:

MemberValue
statusReal value (full access via member_level)
countReal value (full access via member_level)
secret_codeMasked via SQL expression: ***xyz
revenueMasked: -1

Policy evaluation with masking

Masking follows the same pattern as row-level security: it is applied at both cube and view levels. When a cube is accessed through a view, both the cube’s and the view’s masking policies are evaluated.

If any applicable policy grants full access to a member (via member_level), the member is not masked. A member is only masked when no applicable policy grants it full access and at least one policy includes it in member_masking.

Common patterns

Mask all non-public members

Use member_masking: { includes: "*" } to mask all members that aren’t explicitly included in member_level:

access_policy: - group: "*" member_level: includes: - status - created_at member_masking: includes: "*"
access_policy: [ { group: `*`, member_level: { includes: [`status`, `created_at`] }, member_masking: { includes: `*` } } ]

Full access for privileged groups, masking for others

Define one policy that masks everything for the default group and another that grants full access to a privileged group:

access_policy: - group: "*" member_level: includes: [] member_masking: includes: "*" - group: admin member_level: includes: "*" row_level: allow_all: true
access_policy: [ { group: `*`, member_level: { includes: [] }, member_masking: { includes: `*` } }, { group: `admin`, member_level: { includes: `*` }, row_level: { allow_all: true } } ]

Was this page useful?