Data Masking & Redaction
Often a screen, a log, a support tool, or a test database needs to show that data exists without showing the actual sensitive value. Masking and redaction reveal just enough (the last 4 digits, part of an email) while hiding the rest. This limits who can see full sensitive data to only those who truly need it.
Masking shows a partial value (**** **** **** 4321). Redaction removes it completely ([redacted]). Tokenisation and anonymisation replace it with a safe stand-in. The goal is data minimisation in practice: the fewest people and systems seeing full sensitive values. It supports safe logging, support tools, analytics, exports, and non-production test data.
This connects Data Classification (what is sensitive), Observability & Logging Hygiene (do not log it), Test Data & Environments (mask before reuse), and Privacy. The mistake to avoid: showing or storing the full value when a masked one would do.
Reveal only what's needed
- DoDefault to masked or partial values in UIs, support tools, logs, and exports. Show full sensitive data only to roles that genuinely need it, and record when they do.
- DoMask at the right layer, so the full value is not sent to where it will be displayed. Do not send the whole number to the browser and hide it with CSS.
- DoUse irreversible masking or anonymisation when reusing data outside production (test, analytics), so the original cannot be recovered (see Test Data & Environments).
- ConsiderTokenisation for values you must reference but rarely need in full (for example, card or account numbers), keeping the mapping tightly controlled.
- NeverSend full special-category, card, or secret values to a client, log, analytics tool, or non-production environment when a masked or tokenised form would do.
// API returns the whole number; the frontend shows ****
{ "accountNumber": "12345678" } // full value on the wire and in logs
The full value is sent to the browser (and likely logged on the way) and only visually masked. Anyone inspecting the response or the logs sees it all. Masking must happen before the value leaves the trusted layer.
{ "accountNumberMasked": "****5678" } // only the safe form leaves
// the full value is fetched separately, access-checked and audited, only when needed
Only the masked form is sent by default. The full value needs an authorised, audited request, so it is not casually exposed in responses or logs.
Do it correctly
- DoMake masking consistent through a shared helper, so it is applied the same way everywhere and cannot be forgotten (see Observability & Logging Hygiene).
- DoMask enough to actually protect. Partial values can still identify someone if too much is shown, so reveal the minimum that serves the purpose.
- Avoid"Masking" that is reversible when it should not be (for example, encoding, or sending the full value and blanking it client-side). That is not protection.
- AvoidMasking so heavily that staff cannot do legitimate work and get around it by exporting raw data. Balance protection with usability.
Self-review checklist
- AskDoes this screen, log, or export need the full value, or would a masked one do?
- AskIs masking applied before the data leaves the trusted layer, not just hidden client-side?
- AskWhen reusing data outside production, is it irreversibly masked or anonymised?
- AskWho can see the full value, and is that access limited and recorded?