Data Masking
Data masking transforms sensitive column values in SELECT results before they reach the developer. Masking is column-level and read-side only — writes (INSERT, UPDATE) pass through unaffected. Masking is orthogonal to table grants: a user with SELECT on a table sees masked values for any masked columns, while a user without SELECT cannot query the table at all.
Overview
Masking rules are defined as part of policies. Each rule maps a column match pattern to a masking preset. When a query result includes a matched column, the agent replaces the raw value with a masked version before forwarding it to the client.
- Column-level — masking targets individual columns, not entire rows or tables.
- Read-side only — INSERT and UPDATE statements pass through unaffected. Only SELECT results are masked.
- Transparent — developers see masked values in their query results without any changes to their queries or client code.
- Policy-driven — masking rules are configured in the console as part of a policy and pushed to agents automatically.
Masking never prevents access to a table. To control which tables a user can query, use table grants. Masking controls what the user sees in the columns they are allowed to read.
Masking Presets
DAAM provides seven built-in masking presets. Each preset is designed for a specific data type and preserves enough structure to confirm the data format while hiding the sensitive content.
| Preset | Input | Output | What Leaks |
|---|---|---|---|
email | [email protected] | j***@e***.com | First char of local part, first char of domain, TLD |
phone | 415-555-1234 | ***-***-1234 | Last 4 digits |
ssn | 123-45-6789 | ***-**-6789 | Last 4 digits |
credit_card | 4111-1111-1111-1111 | ****-****-****-1111 | Last 4 digits |
name | Alice Johnson | A*** J*** | First character of each word |
redact | anything | *** | Nothing — full redaction |
null | anything | NULL | Nothing — value replaced with SQL NULL |
Masked values are returned as strings regardless of the original column type.
If the original column value is NULL, it passes through unchanged. NULL is already the absence of data, so no masking is needed.
Adding Masking Rules
Masking rules are configured as part of a policy in the console. To add a masking rule:
- Navigate to the database and open the policy you want to edit.
- In the Masking Rules section, click Add Rule.
- Enter a match pattern (e.g.
public.users.email) or use the column dropdown to select from discovered columns. - Choose a masking preset from the dropdown.
- Save the policy.
The column dropdown is populated from the agent's schema introspection. The agent automatically discovers your database's tables and columns and reports them to the control plane, so the dropdown always reflects your actual schema.
Once saved, the updated policy is pushed to the agent immediately. New masking rules take effect on subsequent queries — there is no delay or restart required.
You can also type wildcard patterns directly into the match pattern field. Wildcards are useful for applying the same masking preset across multiple tables or schemas without creating a rule for each column individually.
The masking rules editor below shows five rules using different presets. Click a column pattern field to open the searchable dropdown populated from schema introspection data.
Masking Rules
Standard masking transforms query output — developers see masked values but can reference the column freely in SQL.
Strict masking additionally blocks the column from being used in WHERE, GROUP BY, JOIN, HAVING, and other query clauses. This prevents inference attacks where a developer could deduce masked values through query predicates.
Strict-masked columns can only appear in SELECT output, ORDER BY, and RETURNING clauses.
| Pattern (schema.table.column) | Type | Strict | |
|---|---|---|---|
Match Patterns
Masking rules use three-part match patterns in the format schema.table.column. Each segment can be a literal name or a wildcard (*). Matching is case-sensitive.
| Pattern | Matches |
|---|---|
public.users.email | The email column on the users table in the public schema |
public.users.* | All columns on the users table in the public schema |
public.*.email | Any column named email in any table in the public schema |
*.users.email | The email column on the users table in any schema |
*.*.email | Any column named email in any table in any schema |
*.*.* | Every column in every table (use with caution) |
Wildcard patterns let you enforce masking broadly. For example, *.*.email with the email preset ensures that any column named "email" is masked across all tables, including tables added in the future.
Pattern Specificity
When multiple masking rules within a single policy match the same column, more specific patterns override wildcards. For example, public.users.email takes priority over *.*.email when resolving the public.users.email column, while the wildcard still applies to email columns in all other tables.
Overlapping Policies
When a user is assigned to multiple policies on the same database and those policies define masking rules for the same column, the most restrictive preset wins. This ensures that sensitive data is never exposed through a less-restrictive policy.
For example, if one policy shows a column unmasked and another redacts it, the column is redacted. The null and redact presets are the most restrictive since they reveal the least information.
If a masking rule exists in one policy but not another, the rule is included in the effective policy. The absence of a masking rule in one policy does not cancel a rule from another.
Example: Policy A masks public.customers.email with the "email" preset. Policy B masks the same column with "redact". A user assigned to both policies sees public.customers.email masked with "redact" because it is more restrictive (rank 2 vs rank 4).
Computed Columns and Aliases
Masking works with aliased columns. If you write SELECT email AS e FROM users, DAAM resolves the original column name from the database metadata and applies masking rules that match public.users.email, not the alias.
For computed columns (expressions, aggregates, literals), the agent cannot resolve a physical column identity. Only wildcard rules that match the alias name apply. For example, *.*.cnt would match SELECT COUNT(*) AS cnt, but public.users.cnt would not.
Validation
The following rules are enforced when saving masking rules in a policy:
- Match patterns must have exactly three dot-separated segments (schema.table.column).
- Each segment must be a valid name or the wildcard
*. - The masking type must be a known preset (email, phone, ssn, credit_card, name, redact, or null).
- No duplicate match patterns within the same policy.
Invalid masking rules are rejected on save with an error message indicating which rule failed validation.