# How to Implement Dynamic Data Masking: Five Approaches by Access Path

> Dynamic data masking is chosen by access path, not product. Five approaches — engine-native, in-database, application, BI layer, and Bytebase — and which caller each one masks.

Tianzhou | 2026-05-25 | Source: https://www.bytebase.com/blog/how-to-implement-dynamic-data-masking/

---

Dynamic data masking rewrites sensitive columns at read time. The choice isn't a product — it's where you enforce. Mask the wrong path and your analysts see redacted data while the application's connection still reads cleartext. Pick by which callers you must cover, and across how many engines.

## In the engine. Or on the path.

Masking is enforced in one of two places. Inside the engine, where it rewrites results for every connection to that database. Or on a single access path in front of it — the application's, a human's, a dashboard's — where it rewrites results for that path alone.

The two trade off in opposite directions. Engine enforcement is broad on callers, narrow on engines: it masks every connection, but only to that one database. Path enforcement is the reverse: it masks one caller, and can span every engine in the fleet.

So two things vary — which callers an approach masks, and how many engines it spans. No single approach does both. That is why these approaches combine rather than compete.

## Five places to mask a read

| Approach | Masks which caller | Engine scope | Bypassed by | Unmask audit trail |
| --- | --- | --- | --- | --- |
| Engine-native DDM | Every connection to that engine, incl. the app | One engine | — | Per-engine, varies |
| In-database (views / functions / RLS) | Every SQL caller on that engine | One engine | A different code path | Roll your own |
| Application / API layer | The app's own responses | App-dependent | Direct SQL, BI tools, `psql` | App logs only |
| BI / semantic layer | That tool's users | That tool only | Direct SQL, other BI tools | Tool-specific |
| Bytebase | Human ad-hoc path, via the SQL Editor | Every engine | The app's direct connection | Central, built in |

### Engine-native dynamic data masking

The masking runs inside the engine. SQL Server ships mask types and granular `UNMASK`. Snowflake has masking policies bound to columns. Oracle has Data Redaction. BigQuery, MySQL Enterprise, and the PostgreSQL Anonymizer extension cover the rest. The engine rewrites the result for every connection, by role.

Because enforcement sits in the engine, it catches the caller nothing else does: the application's own service account. That connection is always on and usually over-privileged. Native DDM masks it without a code change.

The limit is reach. One engine, one feature set. Run Postgres, MySQL, and Snowflake side by side and you maintain three masking models with three syntaxes and three audit formats. Policy does not carry across the fleet.

### In-database, by hand

Without a native feature, teams build their own: masked views with `CASE` on `current_user`, `SECURITY DEFINER` functions, row-level security paired with column logic. It reaches every SQL caller on that engine, the same as native DDM.

The cost is fragility. A second code path defeats it — a query against the base table instead of the view, a role the `CASE` doesn't account for, a function that leaks under a planner change. You own the logic, the tests, and every edge case.

### Application or API layer

Mask in the application. Role-aware serializers, field-level authorization in the API, a redaction step before data leaves the service. The database returns real values; the app decides what the user sees.

This protects access through the app, and only through the app. Direct SQL, a BI tool, a `psql` session — each reads unmasked. Policy also scatters across services, so the rule for one column lives in as many places as there are codebases that read it.

### BI or semantic layer

Enforce masking as the query leaves the modeling layer. Looker `access_filter`, the dbt semantic layer, Cube. The dashboard user sees masked fields.

The reach stops at the tool. Another BI tool, an export job, or a direct query reads the unmasked source. Useful when the only exposure is a dashboard; insufficient when it isn't.

### Bytebase

The four approaches above leave the same gap from different sides. Native DDM fragments the policy across engines. Hand-built masking scatters it across views and functions you maintain. Application and BI masking scatter it across codebases and tools. None of them is a single governed place to say what is sensitive and who may read it.

Bytebase is that place, for the human path. Analysts, DBAs, support engineers, and data scientists query through the SQL Editor. Results come back masked by role, at column-level granularity, across every connected engine. One policy model, every database.

And the policy carries a workflow the engines don't ship. Request. Approve. Audit. A reader who needs the real values files a request; an approver grants it to a scope and a time window; the grant and every unmasked query land in one audit log. The policy itself is configured as code through the Bytebase Terraform provider, so the rules version, review, and deploy from one source — not clicked into each engine's console, hand-written into each view, or duplicated across each service. Native DDM stops at the mask; the request flow, the audit trail, and the policy-as-code sit on top of it.

The limit is the path. Masking applies to reads that flow through Bytebase. An application that connects straight to the database with its own connection string bypasses Bytebase and reads cleartext. This is the human-access control, not the application-access control.

## Match the control to the caller

Pick the path, then the approach follows.

Worried about an over-privileged application service account, or developers querying through the app? Put the control where the app reads — the engine, or the application layer.

Worried about humans querying production ad-hoc — analysts, DBAs, support, on-call? Put the control on the human path. Bytebase masks it across every engine, with an unmask audit trail, without touching the application's connection.

Sensitive data exposed only inside a dashboard? The BI layer covers it.

The question is never which tool is best. It's which caller you are masking.

## Two paths. Two controls.

Most estates run two callers at once. An application connects with a service account and never logs off. Humans connect to investigate, debug, and report. Different callers, different paths — and one control rarely covers both.

Native DDM closes the application path. It masks every connection to that engine, service account included. Bytebase closes the human path — the ad-hoc queries native DDM covers only one engine at a time — and records who unmasked what, where.

Run them together, and keep the policy consistent. A column masked for the application should never be readable by an analyst. Two controls protect two callers; the policy behind them should read as one.

## Define the policy once

Decide what is sensitive by column and by role, not by tool. Then apply that definition on the path your people actually use. Start with the [data masking docs](https://docs.bytebase.com/security/data-masking/overview) to set column-level policy across your fleet.