Skip to content

PII redaction (FI_PII_REDACTION) corrupts session.id/user.id/metadata - credit-card regex matches any 13+ digit run #195

Description

@Koustav-github

Affected package

fi-instrumentation-otel (Python)

Package version

fi-instrumentation-otel==1.0.0 (note: pyproject.toml reports 1.0.0 but fi_instrumentation/instruments/version.py reports __version__ = "0.1.19" - these disagree; flagging in case it's relevant to triage)

What happened?

With TraceConfig(pii_redaction=True) (or FI_PII_REDACTION=true), the regex-based PII scanner in pii_redaction.py is applied unconditionally to every span attribute value in TraceConfig.mask(), with no key-value based masking rule in that method, which is scoped to a specific attribute key (INPUT_VALUE, message content, etc.).

Two consequences:

  1. "Trace correlation identifiers get corrupted." session.id, user.id, and metadata are injected via get_attributes_from_context() and pass through the same mask() call as message content. Because the credit-card pattern "|\b(?:\d[ -]*?){13,19}\b" is just a bare 13-19 digit run with no Luhn check or grouping requirement, any UUID whose hex segments happen to contain a long enough digit run gets partially replaced with <CREDIT_CARD>. Measured over 100 random uuid.uuid4() values, ~2% were corrupted. This silently breaks the exact session/user correlation the tracing products exists to provide exists to provide, and it's unattributed from the UI (looks like the session ID was never consistent).
  2. "Non-PII numeric content gets over-redacted." Timestamps, float values, ISBNs, and other incidentals digit runs of 13+ characters are replaced with <CREDIT_CARD> even though they're not remotely credit-card shaped (no-grouping, fails Luhn). This degrades trace readability and defeats debugging on any span containing numeric metadata (costs, durations, request IDs, etc.).
    Expected: PII redaction should apply only to attributes that hold actual free-text content (input/output values, messages, content) - not to structured identifiers like session.id, user.id, or metadata - and the credit-card pattern should require actual card-like structure, not just "13-19 digits in a row.".

Minimal reproduction

from fi_instrumentation.instrumentation.config import TraceConfig
from fi_instrumentation.fi_types import SpanAttributes

cfg = TraceConfig(pii_redaction=True)

# 1. session.id corruption (this specific UUID reproduces it; ~2% of random
# UUIDs hit this due to landing on a 13+ digit run)
sid = "73630065-0794-4450-a1f9-8cc987a02b09"
print(cfg.mask(SpanAttributes.SESSION_ID, sid))
# -> "<CREDIT_CARD>-a1f9-8cc987a02b09" (expected: unchanged)

# 2. user.id corruption
uid = "1234567890123"
print(cfg.mask(SpanAttributes.USER_ID, uid))
# -> "<CREDIT_CARD>" (expected: unchanged)

# 3. metadata corruption (numeric cost/timestamp fields mangled)
md = '{"request_id": "20240115103045123", "const_usd": 3.14159}'
print(cfg.mask(SpanAttributes.METADATA, md))
# -> '{"request_id":"<CREDIT_CARD>", "cost_usd":3.<CREDIT_CARD>}'

Error output / stack trace

N/A - This is a silent data-corruption bug, not an exception. No error is raised; the returned values are simply wrong.

Environment

Python 3.12.5
Windows 11

Anything else?

Root cause is in two places:

  1. fi_instrumentation/instrumentation/pii_redaction.py - has no grouping requirement and no Luhn check, so it matches any sufficiently long digit run regardless of shape.
  2. fi_instrumentation/instrumentation/config.py, TraceConfig.mask() - the if self.pii_redaction: resolved = redact_pii_in_value(resolved) branch runs unconditionally on every attribute key, unlike the hide_* rules above it in the same method, which are all gated with key == SpanAttributes.X / spanAttributes.X in key checks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions