Mask PII
Sensitive values — card numbers, email addresses, phone numbers, national IDs — slip into logs constantly. Once they reach an observability backend they are indexed, retained, and eventually queried by people who should never have seen them.
The PII Masking processor redacts them on the collector, before any data leaves the host. You build rules from a library of maintained detectors; LinkMesh compiles those rules into the collector’s own configuration and pushes it to the edge. There is no “trust the backend to scrub it later” step.
How masking works
Section titled “How masking works”A masking rule is three choices: what to find (a detector, or your own pattern), what to do with it (a mode), and which text to look in (a scope).
What gets stored on the pipeline is those three choices — not the resulting expression. LinkMesh compiles the rules into collector configuration each time it generates a config. This matters more than it looks: a pipeline that stored a rendered expression would keep whatever the pattern looked like on the day it was saved, and would quietly stop matching new formats as the detector library improved. Storing the intent means an improved detector reaches every pipeline already using it.
1. Add the masking processor
Section titled “1. Add the masking processor”Open the pipeline you want to redact in (or create one — see Build your first pipeline) and click + Add Processor, then choose PII Masking.
2. Build a rule
Section titled “2. Build a rule”-
Pick a detector. The picker groups the library by category — contact details, network addresses, financial identifiers, national IDs. Each detector is pinned to a version, so the rule you save keeps behaving the way it did when you tested it.
-
Pick a mode — what replaces the value it finds.
-
Pick a scope — which text the rule looks in.
-
Read the notes. The builder shows the detector’s own over-match and miss notes, plus what the chosen mode costs. These are the two reasons a masking rule behaves differently from what its name suggests, so they are shown before you save rather than discovered later in a dashboard.
-
Save. Modes that cannot work for the detector and scope you picked are never offered in the first place, so a rule you can build is a rule that compiles.
pipelines/pii-mask-rule-builderThe detector library
Section titled “The detector library”| Detector | What it finds |
|---|---|
email |
Email address |
phone_e164 |
Phone number (international) |
phone_nanp |
Phone number (North American) |
ipv4 |
IPv4 address |
ipv6 |
IPv6 address |
card |
Payment card number (shape only) |
card_visa |
Payment card — Visa |
card_mastercard |
Payment card — Mastercard |
card_amex |
Payment card — American Express |
iban |
IBAN (bank account number) |
us_ssn |
US Social Security Number |
ch_ahv |
Swiss AHV/AVS number |
eu_vat |
EU VAT identification number |
Detectors are referenced with their version, as in card_visa@1. The
builder always writes the pinned reference for you, so an improved detector
never changes what a saved rule matches until you move it to the new version.
Every detector carries its own over-match and miss notes, shown in the builder before you save — read them, because they are the difference between what a detector is called and what it actually matches.
| Mode | What it does | What it costs |
|---|---|---|
| Full redaction | Replaces the detected value with a fixed token. Only the matched substring is replaced, so the rest of the line stays readable. | Every occurrence becomes the same token, so two lines about two different people are indistinguishable. Nothing masked this way can be correlated or counted per subject. |
| Consistent hash | Replaces the value with its SHA-256 digest. The same input always produces the same digest, so records about the same subject can still be joined without the value being present. | See the warning below — at body and all-attributes scope the hash is unsalted. |
| Partial reveal | Keeps a bounded, conventional part of the value — the last four digits of a card, the domain of an email — and masks the rest. | A revealed tail is still data leaving your pipeline. Not every detector has a reveal form; where none is defined the mode is unavailable rather than approximated. |
The default replacement token is **** for every detector, deliberately: a
token that named the detector would tell a reader of the log which class of
sensitive data used to be there. You can override it per rule.
Scopes
Section titled “Scopes”| Scope | What it replaces |
|---|---|
| Body | Matched substrings inside the log body. |
| All attributes | Matched substrings in every log attribute value. |
| Named attribute | The entire value of one attribute you name, and only when that whole value is the detected data. This is the only scope that can carry a salt. |
The named-attribute scope matches the whole value rather than “contains”, because it is the only scope that can destroy surrounding text.
3. Mask something the library doesn’t cover
Section titled “3. Mask something the library doesn’t cover”For values specific to your organisation — internal ticket IDs, employee numbers, customer references — choose Custom pattern in the detector picker and supply your own regular expression and a label.
A custom rule carries either a library detector or its own pattern, never both: two sources of truth for what a rule matches would mean nothing could report which one won.
Custom patterns use RE2 — the same engine the collector runs — so there are no backreferences and no lookaround. They offer full redaction and consistent hash only; partial reveal needs a per-detector notion of which part of a value is safe to show, and there is none for a pattern you just wrote. A pattern is also rejected if it can match empty (it would fire at every position and destroy the record) or if it is dramatically slower than the built-in detectors on a large line.
-
Enter the pattern and a label — the label is what identifies the rule in the list and in metrics.
-
Paste a sample line that contains the value you want masked.
-
Click Test. You see exactly what the rule would do to that line.
-
Save. For a new or changed pattern the server requires that the test matched. Read what that does and does not cover below.
4. Verify before you roll it out
Section titled “4. Verify before you roll it out”Rules are validated on the server as you edit them, so the notes and the compiled result you read before saving come from the same code that will compile the rule for your fleet.
For an end-to-end check, open the dry-run panel and paste a sample event:
{
"resourceLogs": [{
"scopeLogs": [{
"logRecords": [{
"body": {
"stringValue": "POST /api/v2/cart/checkout [email protected] card=4242 4242 4242 4242"
}
}]
}]
}]
}
Run it, and the Final Output panel shows the body rewritten with the matched values replaced.
5. Confirm it on a live collector
Section titled “5. Confirm it on a live collector”Save the pipeline and attach it via a route on a collector handling real traffic. The collector picks up the new config on its next fetch.
The fastest confirmation: add a debug exporter alongside your real
destination for a minute, watch journalctl -u alloy -f (or
-u otelcol-contrib -f), and confirm that outbound records carry the
masked values rather than the raw ones. Remove the debug exporter once
you have seen one masked record — it is noisy at sustained traffic.
Related
Section titled “Related”- Pipeline — the reusable processor chain this recipe modifies
- Route — wires the pipeline to a source-destination pair on a collector
- Drop noisy logs — the Filter sibling of this recipe
- Capture live samples — read the records actually leaving a route
- Security & encryption — how secrets such as a masking salt are stored and delivered
- Trust on linkmesh.io — why we redact at the collector edge rather than the backend
- Masking PII in logs — a worked walkthrough of the same approach