PII redaction
Turn on PII pseudonymization in noveum-trace and add your own regex patterns.
noveum-trace can replace personal data in your traces before they leave your application. Each match is swapped for a deterministic token such as EMAIL_3f9a1c0b7e21: the same value always produces the same token (for a given salt), so you can still group and correlate traces without seeing the raw value.
Tokens ignore spaces and letter case: 050 123 4567 and 0501234567 get the same token, and so do John.Smith@Corp.com and john.smith@corp.com. Other characters are kept, so +971501234567 and 0501234567 get different tokens.
PII redaction is off by default.
What gets detected
| Token prefix | Detects |
|---|---|
EMAIL | Email addresses |
PHONE | Phone numbers for any country written with + or 00 (e.g. +971 50 123 4567, 00966 55 123 4567); local formats (no country code, e.g. 050 123 4567) for UAE, Saudi Arabia, Qatar, Egypt, Turkey, UK, India, and US by default; see Choose phone countries; US-style 123-456-7890 and bare 10-digit numbers |
EMIRATES_ID | Emirates ID, with or without hyphens (784-1990-0000001-0, 784199000000010) |
IBAN | IBANs for all registered countries, with or without spaces (AE07 0331 2345 6789 0123 456), validated by checksum |
CARD | 16-digit card numbers |
BANK_ACCOUNT | Standalone runs of 11–16 digits |
SSN | US social security numbers |
IP | IPv4 addresses |
URL | http:// and https:// URLs |
Person names are not detected by the SDK.
Turn on PII redaction
You need two settings:
pii_enabled: turns redaction on.pii_salt: a secret string used to generate the tokens. It is required when redaction is on, andinit()raises aConfigurationErrorwithout it.
Use a long, random salt, keep it secret, and keep it the same across deployments. Changing the salt changes every token, so the same email will no longer match across old and new traces.
Option 1: environment variables
export NOVEUM_PII_ENABLED=true
export NOVEUM_PII_SALT="your-long-random-secret"import noveum_trace
noveum_trace.init(api_key="...", project="my-agent")Option 2: in code
import os
import noveum_trace
noveum_trace.init(
api_key=os.environ["NOVEUM_API_KEY"],
project="my-agent",
security_config={
"pii_enabled": True,
"pii_salt": os.environ["NOVEUM_PII_SALT"],
},
)init() only applies configuration on its first call, so pass these settings in that call.
Option 3: config file
Put a noveum-trace.yaml in the directory your application runs from:
security:
pii_enabled: true
pii_salt: your-long-random-secretEnvironment variables take precedence over the file.
Redaction is applied to every string in the trace just before it is sent to Noveum. In dev_mode, the trace files written to your local disk are the original, unredacted data.
Add your own regex patterns
Use custom_redaction_patterns to redact formats specific to your business, such as employee IDs, policy numbers, or internal customer references. Patterns use Python regular expression syntax and only apply when pii_enabled is on.
Named patterns (recommended)
Pass a mapping of label to pattern. The label becomes the token prefix:
noveum_trace.init(
api_key=os.environ["NOVEUM_API_KEY"],
project="my-agent",
security_config={
"pii_enabled": True,
"pii_salt": os.environ["NOVEUM_PII_SALT"],
"custom_redaction_patterns": {
"EMPLOYEE_ID": r"EMP-\d{6}",
"POLICY_NO": r"POL/\d{4}/\d+",
},
},
)"Employee EMP-123456 asked about POL/2024/991" is sent as "Employee EMPLOYEE_ID_cda1bf726729 asked about POLICY_NO_34fcac8f6c6b".
Unnamed patterns
Pass a list instead. Every match gets the CUSTOM prefix:
security_config={
"pii_enabled": True,
"pii_salt": os.environ["NOVEUM_PII_SALT"],
"custom_redaction_patterns": [r"EMP-\d{6}", r"POL/\d{4}/\d+"],
}In a config file
security:
pii_enabled: true
pii_salt: your-long-random-secret
custom_redaction_patterns:
EMPLOYEE_ID: 'EMP-\d{6}'
POLICY_NO: 'POL/\d{4}/\d+'Use single quotes in YAML so backslashes are kept as-is.
How custom patterns behave
- Your patterns take precedence. Where a custom pattern matches, it wins over any built-in detector, even one that would have matched a longer piece of text. Built-in detectors only apply to the text your patterns don't cover.
- Match the whole value. Because your pattern wins, a pattern that matches only part of a value leaves the rest visible. For example, a pattern for
1990inside an Emirates ID would leave the other digits of the ID unredacted. - Invalid patterns fail at startup. When PII redaction is on, a pattern that is not a valid regular expression raises a
ConfigurationErrorfrominit(), naming the label and pattern. When it is off, patterns are not checked.
Choose phone countries
Phone numbers written with a country code (+971 50 123 4567) are detected for every country. Numbers written without one (050 123 4567) are only detected for the countries in pii_phone_regions, given as ISO 3166 country codes:
security_config={
"pii_enabled": True,
"pii_salt": os.environ["NOVEUM_PII_SALT"],
"pii_phone_regions": ["AE", "SA"],
}security:
pii_enabled: true
pii_salt: your-long-random-secret
pii_phone_regions: ["AE", "SA"]| Setting | Local formats detected |
|---|---|
| Not set | UAE, Saudi Arabia, Qatar, Egypt, Turkey, UK, India, US |
["AE", "SA"] | UAE and Saudi Arabia only |
[] | None: only numbers written with + and a country code |
Choose only the countries your traffic comes from. Some countries use short local numbers that look like ordinary data: Qatar numbers are 8 digits, so with Qatar enabled an order number or amount such as 55123456 is treated as a phone number.
When PII redaction is on, an unknown country code raises a ConfigurationError from init().
Things to know
- Trace structure is left alone. Fields named
trace_id,span_id,parent_span_id,timestamp,start_time,end_time,duration, orduration_msare never redacted, at any level of the trace, so spans keep their parent links. Other ID fields such asuser_idorsession_idare redacted like any other field, since they often hold an email or phone number. - UUIDs, dates, and times are left alone. A value that is exactly a UUID is never changed. Dates and times inside text (
2026-10-07,07/10/2026,12:28:58.495185,9:30 PM) are never treated as phone or account numbers. Your custom patterns still apply to them. - Strings only. Numbers, booleans, and other non-string values in a trace are not inspected.
- Long digit strings. Any standalone string of 11–16 digits is treated as a bank account number. This includes values such as 13-digit millisecond timestamps sent as strings.
- Back-to-back numbers. A phone number immediately followed by another number with only a space between them (e.g.
+971 50 123 4567 2024) may not be detected. Punctuation between them (+971 50 123 4567, 2024) avoids this.
