Data masking
Data masking
Section titled “Data masking”Data masking hides sensitive values at search time — the data is stored unmodified, but users with a restricted role see masked values instead of the real ones. It is for access control, not for scrubbing the stored data: a test or support role can use realistic, masked data without ever seeing the actual phone numbers, email addresses or IDs.
Two ways to hide data. The rules in this guide are search-time: the stored data is untouched and masking is reversible per role. If you need the sensitive value to not exist at rest at all (compliance, storage-scrub requirements), use the agent’s collection-time
datamasktransformer instead — it rewrites the value at ingest and is irreversible. See Search-time vs ingest-time masking below; most access-control needs use the search-time rules.
How it works
Section titled “How it works”Three steps, in order:
- Define a masking rule for the sensitive data — which sourcetype, which fields (or which regex groups), and what to replace them with.
- Restrict a role to the masking rule, so anyone in that role sees the masked form.
- Assign the role to the users who should see only masked data.
Masking is applied when a query runs, so the same index serves both the full-access operator and the masked support role, with no copy of the data.
Masking rules
Section titled “Masking rules”Configure → Data Masking lists the rules. A rule has:
- a name and description;
- a sourcetype — which data the rule applies to;
- a match type — Field (mask a named field) or Regex (mask the text that a regular expression matches);
- a strategy — what to do with the matched value.
For regex rules, the regex uses named groups (e.g.
(?<k1>…)'@'(?<k2>…)); each group is mapped to a masking strategy in order.
Fields you cannot mask
Section titled “Fields you cannot mask”A rule targets a named field, and the platform’s built-in fields —
host, sourcetype, repo, origin and _time — cannot be masked. They
are stored outside the document body that the masking pipeline reads, so a rule
naming one would have no effect. Sondar refuses such a rule when you save it
rather than accepting one that would quietly do nothing.
If you need to pseudonymise something like a hostname, mask the ordinary field
that carries it (for example a host_ip or hostname field extracted from your
events) rather than the built-in host.
The _raw event text is a separate case and is maskable — use the rule’s
regex over _raw option rather than a field target.
Built-in strategies
Section titled “Built-in strategies”For common sensitive kinds, pick the built-in type and the platform knows what to mask. The built-in catalog is US/global-first (the default strategy when a new rule is created is Email):
- Email, Bank card;
- SSN, Credit card, US phone number, IP address;
- Name, Address, Passport number, Driver license, Bank account number, MAC address, Zip code, Date of birth;
- API key, Bearer token, AWS access key, Private key, JWT — the credentials/token catalog (added 19 Aug 2026), for the values US operators most need to hide;
- Hash — a keyed pseudonym, for correlating events by a sensitive identifier without storing or showing it.
The masking format differs by kind: email keeps the first character of the
local part, SSN/passport/driver license/bank account keep the last four digits,
name keeps the first character, MAC keeps the first octet, zip code keeps the
leading digit, date of birth is masked entirely, IP masks the final octet (the
final hextet for IPv6, a whole-value bracketed address inside its brackets) and
address keeps the leading street prefix. The credentials/token types (API key,
bearer token, AWS access key, private key, JWT) each mask everything but the
last four characters — e.g. sk-abc123 → *****c123 — so the identifying
tail is visible but the secret is not. Hash replaces the value with a
deterministic pseudonym — the first 16 hex chars of an HMAC-SHA-256 computed
under a key unique to your instance. Two occurrences of the same value map to
the same pseudonym, so you can still correlate events without the value itself.
What the pseudonym does and does not give you:
-
It resists enumeration, because it is keyed. A plain hash would not. The identifiers this feature masks come from small sets — a US Social Security number has about 10⁹ possibilities, a phone number 10¹⁰, a date of birth under 10⁵ — so anyone holding plain hashes could hash every possible value once and read the originals straight off. The instance key removes that: without it the dictionary cannot be built.
-
Pseudonyms are local to the instance. The key is generated on first start and kept with your data, at
<path.data>/data-masking.key— inside the volume, so it survives restarts and upgrades. The same value therefore hashes differently on a different install, and pseudonyms cannot be matched across two instances. Back the key up with the data: restoring a volume without it changes every pseudonym, and there is no way to recover the old mapping. -
On more than one node, point every node at the same key. The default location is per-node, so each node would otherwise generate its own and the same value would pseudonymise differently depending on which node answered. Set
sondar.data_masking.key_fileto a shared path — a mounted Secret, or a ReadWriteMany volume:sondar.data_masking.key_file: /usr/share/sondar/share/data-masking.keyThe Helm chart does this for you when
sondardb.shareData.enabledis true, which is the default. When a node generates a fresh key it says so at WARN in the log, naming this setting. -
It is pseudonymisation, not anonymisation. Anyone who holds the instance key can rebuild the mapping for a small identifier space. Treat the key as a secret of the same weight as the data it protects.
Reducing false positives (19 Aug 2026)
Section titled “Reducing false positives (19 Aug 2026)”Like Datadog’s Sensitive Data Scanner, a field rule can carry two accuracy levers, both optional:
- Keyword — the field value is masked only when it contains this keyword.
Use it when a value is only sensitive in context (e.g. mask a token only when
it starts with
sk-), so noisy values are left alone. - Suppressions — values listed here are never masked. The match is an
exact, case-sensitive comparison of the whole field value — not a prefix,
suffix or domain match. Listing
example.comdoes not suppresshost.example.com; list each value you want left alone. This errs toward masking more than you asked for rather than less, which is the safe direction, but it does mean a suppression that looks like a domain pattern will simply never fire.
Both levers apply to rules that match a named field. A rule that matches by regular expression masks every match in the value, and the keyword and suppression lists are not consulted — the same on either search engine. If you need a suppression on a regex rule, narrow the expression instead.
IP masking fails closed. A value that does not parse as an IP address — a log line with a component prefix or suffix (
10.0.0.1 [cached],[main] 10.0.0.1), an email address, arbitrary text — is masked entirely, so an address cannot hide inside surrounding characters. Brackets only count as part of an address when they enclose the whole value (RFC 3986[addr]:port); an IPv6 zone index (fe80::1%eth0, and VLAN sub-interfaces likefe80::1%eth0.100) is a valid address and is masked as one — the mask boundary is the last separator of the address part, so a%zonesuffix is masked through the end rather than destroyed.This fail-closed behavior is specific to the IP type. The other built-in strategies mask by position, not by parsing, and degrade differently when the value is embedded in surrounding text: the mask window shifts right, so everything from the offset onward is replaced and the sensitive part ends up more hidden, not less — what becomes visible is surrounding text, never the secret. “Fails closed” is one type’s guarantee, not the catalog’s default.
Upgrade note (15 Aug 2026): this is a behavior change. Values that used to be partially masked (e.g.
host:8080→host:****,not-an-ip→ unchanged) are now fully masked, and this is not reversible on already-masked data — verify the strategy is pointed at address-holding fields, not whole raw events.
Custom strategies
Section titled “Custom strategies”For anything else, compose a strategy from position and placeholder:
- Retain N to M / Retain N and M — keep a range, mask the rest.
- Mask N to M — mask a range, keep the rest.
- Keep String — keep a literal token; Mask Before/After String — mask on one side of a token.
Start position, end position and the replacement string are set explicitly.
Rules can be custom or built-in; the raw event text can be masked too (a toggle on the rule). When several rules match the same value, the oldest rule wins.
Masking report
Section titled “Masking report”Configure → Data Masking → Masking Report scans a sample of your indexed events in a chosen repo and time range and tells you, per rule, how many field values the rule would change (findings). Use it to preview a rule’s blast radius before or after applying it — the same impact view Datadog’s Sensitive Data Scanner findings give, and the fastest way to answer “what would this rule touch on my existing data?”.
The report:
- shows events scanned, total findings, and a per-rule findings count alongside the number of matching events;
- only counts a rule against events of its own sourcetype, so a rule’s number is its real blast radius, not the whole repo;
- honours the accuracy levers above (keyword and suppressions), so what the report counts is what the rule actually does;
- is a bounded sample — when it hits the event cap it flags the result as truncated rather than pretending to be exhaustive;
- reads the data as you see it. The scan runs with your own permissions, so
if you are masked on the repository it scans values that are already masked —
and a masked value usually masks to itself, so findings come back at or near
zero on a repository that is in fact full of sensitive data. The result
records who it ran as and sets
scannedUnderMaskingwhen this applies. Run the report as a user who can see the repository unmasked — one holding View Unmasked Data, or in no role that masks it — or treat the numbers as a floor rather than an answer.
Roles and users
Section titled “Roles and users”The rule alone does nothing until a role is restricted to it. Under role settings, choose which masking rules the role is subject to; then assign the role to the users who should see masked data. A user in an unrestricted role sees the data as stored.
Masking is per role, and roles combine with OR (decision #166, 16 Aug 2026): a user is masked on a repository if any role they hold is restricted to the masking rule. Assigning an additional unrestricted role does not remove masking — a role that simply does not mask a repo cannot override one that does. To let a specific user see the unmasked data, grant them a view unmasked data permission instead of relying on role composition.
The built-in
adminrole always holds View Unmasked Data. It is re-seeded with every function permission on each boot, so masking rules never apply to it — the Data Masking toggle on the admin role is permanently inert. That is deliberate (an admin can always edit the rules); to verify masking, use a non-admin role.
Checking that a rule is actually masking
Section titled “Checking that a rule is actually masking”A masking rule is a security control, so it is worth confirming it works rather than assuming it from a saved form. Two things make a check pass for the wrong reason:
- Do not check as
admin. The built-inadminrole always holds View Unmasked Data, so it sees plaintext no matter what any rule says. A check run as admin tells you nothing. Use a non-admin role instead. - A role that simply lacks masking is not a masked role, it is an unrestricted one. Data Masking has to be ticked for that index on the role. Without it the rule is inert and the plaintext you see is expected behavior, not a failure — which is easy to misread as a broken control.
A good check, in order:
- Create a role with read access to the index, Data Masking ticked for that index, and not holding View Unmasked Data.
- Assign it to a test user and sign in as them.
- Search the index and read the field the rule targets.
For Hash, the expected pseudonym is deterministic, so you can decide the
answer before you look. Compute it from the instance key — at
<path.data>/data-masking.key, or wherever sondar.data_masking.key_file
points — and compare:
KEY=var/data/data-masking.keyprintf %s '<value>' | openssl dgst -sha256 -mac HMAC \ -macopt hexkey:$(od -An -tx1 -v "$KEY" | tr -d ' \n') -r \ | cut -c1-16Matching that string means the pseudonym is keyed. A 16-hex value that does not match it is worth investigating rather than accepting — in particular, an unkeyed digest of the same value would look equally plausible in the UI while being reversible by anyone who can enumerate the input space.
Running the masking report as the masked user is a useful second signal: it prints a warning saying the scan ran under masking, which confirms the role really is masked on that index.
Search-time vs ingest-time masking
Section titled “Search-time vs ingest-time masking”The rules above are search-time: the stored data is untouched, and the mask applies per query based on the user’s roles. This is the default, and it is reversible — revoke the role and the data reads as stored.
The agent also offers a collection-time datamask transformer
(Transformers) that rewrites the value at ingest, before
it is stored. Use it only when you need the sensitive value not to exist at
rest (compliance / storage-scrub requirements). Two consequences, decided
16 Aug 2026:
-
It is optional and off by default. Masking rules alone do not touch stored data; you must add the
datamasktransformer to a collection config explicitly. -
It is irreversible. The value is replaced in the stored event; no role or permission can recover it, and search-time masking cannot undo it.
-
It covers most, but not all, of the strategies on this page. The agent transformer offers SSN, credit card, US phone, bank account, passport, driver’s license, date of birth, ZIP, street address, person name, MAC address, API key, bearer token, AWS access key, private key and JWT, alongside the older set (name, enterprise name, ID card, organization number, business registration number, taxpayer ID, telephone, email, banking number, custom range).
Two are not available at ingest.
IpAddressis not offered, because the server’s address-plausibility handling is intricate enough that approximating it in the agent would risk masking the wrong thing.Hashis not offered and will not be: a keyed pseudonym needs the per-instance key, and the agent neither holds it nor should — an unkeyed hash of an enumerable identifier is recoverable by anyone willing to hash the input space. -
The strategies added for the US market match this page exactly, and are kept that way by a fixture generated from the server’s own maskers. The older ones do not:
Name,Tel,BusRegandTaxPayerscrub differently at ingest than at search, because the agent’s original strategies mask to a fixed width while this page’s preserve the value’s length. If you use one of those four, verify the result on your own data before relying on either behavior. -
It applies to agent-collected data only. Events posted straight to the ingest API do not pass through a transformer stage, so there is no ingest-time scrubbing on that path. Point the transformer at fields you are certain should never be stored in the clear, and treat it as a one-way choice for existing data.
For most access-control needs, prefer search-time masking: same protection for every search, reversible, and a single index serves both masked and unmasked roles.
Related
Section titled “Related”Masking complements, and is separate from, field extraction
(Indexes and field extraction) — extraction
makes fields searchable, masking controls who sees the value. The agent also
has a collection-time datamask transformer (Transformers)
for scrubbing at ingest; masking here is the search-time, role-aware
counterpart.