Skip to content

proxy: conditional / role-based dynamic data masking policies #381

Description

@babltiga

Background

Proposed for v1.3. Today restricted_columns masking is static — a single fixed value-mask applied identically to everyone who is restricted. There are no masking types (full, partial last-4, hash, email-preserving, format-preserving) and no conditional reveal (mask for analysts, reveal for the data owner / a privileged group). Per-role dynamic masking with reveal conditions is standard in the category (Satori, Cyral, Immuta "reveal policies", Privacera, Bytebase, Hoop). This enhances the existing masking code rather than replacing it, and is distinct from the planned v2.1 data classification and v2.0 column-permission enforcement — it governs how a visible value is rendered, not whether the column is accessible.

Scope

Named masking policies bound to columns, with a masking strategy and an optional reveal condition evaluated per requester.

  • New table masking_policy (Flyway V56+): id, organization_id, datasource_id, column_ref (schema.table.column), strategy (snake_case enum masking_strategy: FULL/PARTIAL/HASH/EMAIL/FORMAT_PRESERVING), strategy_params JSONB (e.g. visible suffix length), reveal_to (role / group / user list — who sees the unmasked value), enabled, timestamps.
  • Enforcement extends the existing result-masking code in the proxy module (the restricted-column matcher already runs at result-read time): resolve applicable policies for the requester, and for each policied column either reveal (requester in reveal_to) or apply the strategy. Backward-compatible — an unconfigured restricted_columns entry keeps today's behaviour.
  • Strategies: FULL (existing), PARTIAL (keep last N), HASH (stable SHA-256), EMAIL (j***@domain), FORMAT_PRESERVING (preserve length/shape). Masking is applied before serialization and before any result snapshot is stored, so masked values never persist unmasked.
  • Audit: record which masking policies applied to a result (policy ids), never the unmasked values.
  • Frontend: a "Masking policies" section on DatasourceSettingsPage — per-column strategy picker, params, reveal-to selector, and a live preview of the masked rendering. Use the existing enum-label helpers (enumOptions) for strategy options (CLAUDE.md i18n rule).

Acceptance criteria

  • Flyway migration V56+ (new table + masking_strategy enum).
  • Reveal condition resolves per requester (role/group/user); non-revealed requesters get the strategy output.
  • All five strategies implemented and unit-tested, including the existing restricted_columns backward-compat path.
  • Masking applied before serialization and before result-snapshot storage (no unmasked persistence).
  • Applied-policy ids audited; unmasked values never logged or stored.
  • Strategy options rendered via enumOptions / enum-label helpers; no inline enum strings; i18n parity green.
  • ApplicationModulesTest / ApiPackageDependencyTest green; coverage ≥ 90%.
  • Frontend validation parity for the policy form; Playwright spec: configure a PARTIAL mask → run SELECT as a non-revealed user → see the masked value; as a revealed user → see the full value.
  • docs/03-data-model.md, docs/05-backend.md, docs/06-frontend.md, docs/07-security.md, docs/12-roadmap.md (add v1.3) and website/ updated.

Pointers

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions