PII masking
Requires a license with the
pii-maskfeature. See pricing.
PII masking replaces personal data with a placeholder before it leaves the
instance, so ada@example.com in a response becomes [redacted-email]. It
masks log lines too, and keeps a log of who received a response that carried
personal data. What is stored stays as it was written. Only what goes out is
masked.
How it works
Section titled “How it works”Every response body from the Content API (/api/v1/*) and the Admin API passes
through the rules, in priority order, lowest first. Each match is replaced.
When a signed-in caller receives a response that matched, the access log
records the caller and the kinds of data it held. Log messages and their string
fields go through the built-in rules before they are written. Your own rules
apply to responses only.
| Rule | Replaced with | Priority |
|---|---|---|
TOKEN | [redacted-token] | 5 |
EMAIL | [redacted-email] | 10 |
PHONE | [redacted-phone] | 20 |
IP | [redacted-ip], for IPv4 addresses | 30 |
CREDIT_CARD | [redacted-cc], only for numbers that start with a card issuer's prefix and pass the Luhn check | 40 |
SSN | [redacted-ssn] | 50 |
Bodies of type JSON, XML, form data, JavaScript and any text/* type are
scanned. Server-sent event streams and binary bodies pass through.
Some Admin API paths return real values, because the console needs them to do
its job: /api/admin/auth, /api/admin/users, /api/admin/invitations,
/api/admin/gdpr, /api/admin/api-keys and /api/admin/admin-tokens. Reads on
those paths are still recorded in the access log. The Content API has no such
exceptions, and no role sees unmasked output on a masked path.
Matching is by pattern and cannot catch every form of personal data. Keep personal data out of fields that do not need it.
Try it
Section titled “Try it”This adds a rule for staff numbers and watches it mask a response. You need an
admin token in TOKEN, and a content type to write to, such as the post type
from the quickstart, which also shows how
to get a token.
-
Test the pattern on a sample. Nothing is saved:
Terminal window curl -X POST http://localhost:3001/api/admin/pii/rules/test \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"pattern": "EMP-[0-9]{6}", "replacement": "[redacted-employee]", "sample": "Owner EMP-004211 approved"}'{"valid": true, "matches": ["EMP-004211"], "count": 1, "masked": "Owner [redacted-employee] approved"}A pattern that is not valid answers
200with"valid": falseand anerror. -
Save it as a rule. A rule is off until you send
"enabled": true:Terminal window curl -X POST http://localhost:3001/api/admin/pii/rules \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"name": "EMPLOYEE_ID", "description": "Staff numbers", "pattern": "EMP-[0-9]{6}", "replacement": "[redacted-employee]", "enabled": true}'The answer is
201with the rule, itsidand"priority": 100. Copy theidintoRULE_ID. -
Write an entry that carries a staff number:
Terminal window curl -X POST http://localhost:3002/api/v1/content/post \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"data": {"title": "Approved by EMP-004211"}}'The entry is stored as written, but the answer already reads
"title": "Approved by [redacted-employee]". Every later read is masked the same way. -
See the access log:
Terminal window curl "http://localhost:3001/api/admin/pii/access-log?limit=5" \-H "Authorization: Bearer $TOKEN"The newest entry names you as
viewer_user_id, your role, and"pii_type": "EMPLOYEE_ID". -
Remove the rule:
Terminal window curl -X DELETE http://localhost:3001/api/admin/pii/rules/$RULE_ID \-H "Authorization: Bearer $TOKEN"The answer is
204.
A super admin manages the same rules and reads the log under Settings > PII masking in the admin console.
Add your own rules
Section titled “Add your own rules”A tenant can hold up to 50 enabled rules of its own. A custom rule runs after
the built-in rules unless you give it a lower priority. A custom rule with the
same name as a built-in rule replaces it, so a disabled rule named EMAIL turns
email masking off for that tenant. A saved rule applies to the next response,
with no restart.
| Field | Rule | Default |
|---|---|---|
name | Required, up to 255 characters | none |
description | Optional | empty |
pattern | Required, an RE2 regular expression up to 512 characters. It must not match the empty string | none |
replacement | Required, up to 255 characters | none |
priority | Lower runs first. 0 or missing means the default, and a negative number is refused | 100 |
enabled | Whether the rule runs | false |
RE2 runs in linear time, so no pattern can stall a response. PUT replaces a
rule, so send every field.
Read the access log
Section titled “Read the access log”GET /api/admin/pii/access-log takes limit (default 50, at most 500) and
offset. Each entry carries viewer_user_id, viewer_role, pii_type (one
rule name, or several joined by commas), accessed_at and tenant_id. An admin
sees their own tenant. A super admin sees every tenant.
Entries older than PII_MASK_RETENTION_DAYS are deleted every hour. Deleting a
tenant deletes its entries.
Settings
Section titled “Settings”| Variable | What it does | Default |
|---|---|---|
PII_MASK_RETENTION_DAYS | Days the access log keeps an entry. 0 or less keeps entries forever. | 90 |
If you set LYEVE_PLUGINS, include pii-mask. See
Licensing and tiers.
Routes
Section titled “Routes”All routes need the admin role and act on the caller's tenant.
Rules and access log
| Method | Path | Purpose |
|---|---|---|
GET | /api/admin/pii/rules | The built-in rules (presets), the tenant's own (custom) and the limits. |
POST | /api/admin/pii/rules | Add a rule. |
POST | /api/admin/pii/rules/test | Try a pattern on a sample of up to 4096 characters. |
PUT | /api/admin/pii/rules/{id} | Replace a rule. Send every field. |
DELETE | /api/admin/pii/rules/{id} | Remove a rule. Answers 204. |
GET | /api/admin/pii/access-log | Who received responses that carried personal data. |
Errors
Section titled “Errors”| Status | Message | Cause |
|---|---|---|
422 | invalid rule: pattern does not compile as RE2: ... | The pattern is not valid RE2. |
422 | invalid rule: pattern matches the empty string, which would redact every response | The pattern would match everywhere. |
422 | this tenant already has the maximum number of enabled rules | 50 rules are already enabled. |
Every error
| Status | Message | Cause |
|---|---|---|
422 | sample is longer than 4096 characters | The test sample is too long. |
400 | body must be a JSON object describing one rule | The body is not JSON or has unknown fields. |
404 | failed to update the rule or failed to delete the rule | No rule with that id in this tenant. |
402 | payment_required | The license no longer carries pii-mask. |
Related
Section titled “Related”- Logs: where the masked log lines go.
- Audit log: who changed what, beside who read what.
- Data protection: masking in a vendor review.