Skip to content

Commit 93c08c8

Browse files
committed
LCORE-4313: (design) PII redaction strategy
Signed-off-by: Anik Bhattacharjee <anbhatta@redhat.com>
1 parent 5208801 commit 93c08c8

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

‎docs/design/observability-opentelemetry/observability-opentelemetry-design.md‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,93 @@ No **required** change to JSON requests/responses. The `/config` response gains
374374
- Which `OTEL_*` variables are included in the `/config` scrape?
375375

376376

377+
## Addendum: PII redaction strategy
378+
379+
| | |
380+
|--------------------------|-----------------------------------------------------------------------------------|
381+
| **Date** | 2026-09-22 |
382+
| **Authors** | Anik Bhattacharjee |
383+
| **Feature / Initiative** | [UIESTRAT-229: Enable collection & upload of Observability OTel data stripped of PII/sensitive data](https://redhat.atlassian.net/browse/UIESTRAT-229) |
384+
385+
The three core inference spans (`/v1/query`, `/v1/streaming_query`, `/v1/responses`) carry
386+
raw, un-hashed request/response content for evaluation, relaxing the original metadata-only rule
387+
(**R7**; §Why "Safe observability by design"). This addendum covers how that content is
388+
redacted of PII.
389+
390+
### The problem
391+
392+
Raw span text (prompts, responses, RAG chunks, tool I/O) can contain PII, so it must be scrubbed
393+
before the span leaves LCORE — before OTLP export to a hosted backend such as LangFuse, and before
394+
any cross-org sharing. This is a portfolio-wide obligation from Red Hat's AI Assessment (AIA/PIA)
395+
process, not specific to LCORE.
396+
397+
### The approach — Presidio from PyPI
398+
399+
Use the upstream Presidio library directly. Add `presidio-analyzer` and `presidio-anonymizer`
400+
(both on PyPI) to `pyproject.toml`, and wrap them in a small `redact(text) -> str` helper that
401+
detects and replaces email, hostname, IP (v4/v6), location, organization, person, phone, and URL.
402+
No new infrastructure — a dependency plus one helper, applied wherever content is set on a span
403+
(next section).
404+
405+
Note: Presidio needs a spaCy model for named-entity detection, which is a sizable download.
406+
407+
### Where redaction runs — redact at emission, through one helper
408+
409+
Content is redacted at the point it's set on the span. All content fields will go on spans
410+
through a **single centralized helper** — e.g. `set_content_attribute(span, key, text)` — that runs
411+
each value through the Presidio `redact()` helper before calling `set_attribute`. For any content
412+
serialized as a JSON string (e.g. RAG chunks or tool I/O), the helper redacts each leaf string
413+
(`content`/`args`/`source`) before `json.dumps`, so the serialized attribute never contains raw
414+
PII. Properties of this approach:
415+
416+
- **The span never holds raw PII.** R11 ("redact before content leaves the process") is satisfied
417+
by construction — there is no in-memory window where a raw value sits on a span or in the export
418+
queue.
419+
- **One code path to audit.** Because content can only reach a span through the helper, there is a
420+
single place to review and test; individual emission sites cannot forget to redact (there is no
421+
raw path to `set_attribute` for content keys). A review/lint rule reinforces "content goes
422+
through the helper."
423+
- **Fail-closed.** If the redactor raises, the helper omits the field rather than emitting raw
424+
text — consistent with R9 (a tracing failure must never leak or break the request).
425+
- **Cost.** Redaction runs on the request path (synchronous), not in the export background thread.
426+
This is minor. It's a text pass over already-generated output, dwarfed by the LLM call, and only
427+
incurred when raw-content capture is enabled (scoped to the three core spans). The helper is the
428+
single place to make it async/best-effort if it ever becomes a bottleneck.
429+
430+
### Future: adopting the shared `data-anonymizer` library
431+
432+
A shared, Presidio-based redactor already exists: `data-anonymizer` (used by Ask Red Hat et al.;
433+
see the [shared Presidio library strategy doc](https://docs.google.com/document/d/1BNBIDUz-lLmUjT9N9cEMqgaQv9EqfLKwZrA8gtbtlY8/edit?tab=t.0#heading=h.9qfphopokr7u)).
434+
LCORE may move to it eventually, so redaction is maintained once across teams rather than
435+
per-project. The only obstacle is distribution. `data-anonymizer` lives on Red Hat's internal GitLab
436+
(`gitlab.cee.redhat.com/uxe-data-ai-solutions/data-anonymizer`), not PyPI, and a GitHub build/CI
437+
can't install from internal GitLab without internal credentials — so it can't go in
438+
`pyproject.toml`.
439+
440+
The swap stays small because redaction is already funneled through the single `redact()` helper
441+
(§ Where redaction runs). To adopt the shared library without breaking the GitHub build, turn that
442+
helper into a **redaction slot** — one interface the telemetry path calls without knowing the
443+
backend:
444+
445+
- **GitHub build (default):** the slot keeps using public Presidio from PyPI.
446+
- **Red Hat's internal build:** a downstream build fills the slot with `data-anonymizer` via an
447+
adapter, added at the stage where internal GitLab is reachable.
448+
449+
Until such a move is deemed required, the PyPI Presidio helper above is the whole
450+
implementation.
451+
452+
### Requirements addendum
453+
454+
- **R11 — PII redaction before export.** Raw content on the three core inference spans shall pass
455+
through PII detection/redaction before leaving the process (OTLP export or cross-org sharing).
456+
377457
## Appendix A: Jira epics and related tracking
378458

379459
**Epics**
380460

381461
- [LCORE-1791](https://redhat.atlassian.net/browse/LCORE-1791)
382462
- [LCORE-1799](https://redhat.atlassian.net/browse/LCORE-1799)
463+
- [UIESTRAT-229](https://redhat.atlassian.net/browse/UIESTRAT-229)
383464

384465
## Appendix B: External references
385466

0 commit comments

Comments
 (0)