Skip to content

Commit 34e1220

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

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,92 @@ 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"). Raw content
388+
can contain PII, so it must be detected and redacted before it leaves LCORE. **This addendum
389+
defines that redaction strategy.**
390+
391+
### The problem
392+
393+
Once spans carry raw text (prompts, responses, RAG chunks, tool I/O), that text
394+
can contain PII. So it must be scrubbed of PII before the span leaves LCORE — before OTLP export
395+
to a hosted backend such as LangFuse, and before any cross-org sharing. (This is a portfolio-wide
396+
obligation from Red Hat's AI Assessment (AIA/PIA) process, not specific to LCORE.) We do this by
397+
**detecting and redacting PII**.
398+
399+
### The redactor we want
400+
401+
A shared, production-proven redactor already exists:
402+
`data-anonymizer` (used by Ask Red Hat et all). It is
403+
Presidio-based and detects email, hostname, IP (v4/v6), location, organization, person, phone,
404+
and URL. Reusing it — rather than each team writing its own — is the goal. See the
405+
[shared Presidio library strategy doc](https://docs.google.com/document/d/1BNBIDUz-lLmUjT9N9cEMqgaQv9EqfLKwZrA8gtbtlY8/edit?tab=t.0#heading=h.9qfphopokr7u).
406+
407+
### The catch — where it lives
408+
409+
`data-anonymizer` is on Red Hat's **internal GitLab**
410+
(`gitlab.cee.redhat.com/uxe-data-ai-solutions/data-anonymizer`); it is not on PyPI. LCORE is
411+
built on **GitHub**, and a GitHub build/CI can't install a package from internal GitLab without
412+
internal credentials in the pipeline. So we **cannot simply add it to `pyproject.toml`.**
413+
414+
### The proposal — a pluggable redaction slot
415+
416+
Don't hard-wire any one redactor into LCORE. Define a small **redaction slot**
417+
— one interface, e.g. `redact(text) -> text` — that the telemetry path calls without knowing which
418+
redactor is behind it. Then:
419+
420+
- **GitHub build (default):** the slot is filled with public Presidio from PyPI. This gives the
421+
GitHub build a working, dependency-only telemetry redactor with no internal access required.
422+
- **Red Hat's internal build/deployment:** a downstream build of LCORE wires in the downstream
423+
`data-anonymizer` via an adapter, added at the stage where internal GitLab *is* reachable.
424+
425+
Net: LCORE always has a working telemetry redactor and still builds on GitHub, while Red Hat's
426+
deployment gets the shared portfolio library.
427+
428+
### Where redaction runs — redact at emission, through one helper
429+
430+
Content will be redacted as it is put on the span, not after. All content fields will go on spans
431+
through a **single centralized helper** — e.g. `set_content_attribute(span, key, text)` — that runs
432+
each value through the redaction slot (above) before calling `set_attribute`. For any content
433+
serialized as a JSON string (e.g. RAG chunks or tool I/O), the helper redacts each leaf string
434+
(`content`/`args`/`source`) **before** `json.dumps`, so the serialized attribute never contains raw
435+
PII. Properties of this approach:
436+
437+
- **The span never holds raw PII.** R11 ("redact before content leaves the process") is satisfied
438+
by construction — there is no in-memory window where a raw value sits on a span or in the export
439+
queue.
440+
- **One code path to audit.** Because content can only reach a span through the helper, there is a
441+
single place to review and test; individual emission sites cannot forget to redact (there is no
442+
raw path to `set_attribute` for content keys). A review/lint rule reinforces "content goes
443+
through the helper."
444+
- **Fail-closed.** If the redactor raises, the helper **omits** the field rather than emitting raw
445+
text — consistent with R9 (a tracing failure must never leak or break the request).
446+
- **Cost.** Redaction runs on the request path (synchronous), not in the export background thread.
447+
This is minor — a text pass over already-generated output, dwarfed by the LLM call — and only
448+
incurred when raw-content capture is enabled (scoped to the three core spans). The helper is the
449+
single place to make it async/best-effort if it ever becomes a bottleneck.
450+
451+
### Requirements addendum
452+
453+
- **R11 — PII redaction before export.** Raw content on the three core inference spans shall pass
454+
through PII detection/redaction before leaving the process (OTLP export or cross-org sharing).
455+
- **R12 — Pluggable telemetry redaction, shared standard downstream.** LCORE shall expose a
456+
pluggable redaction interface for span content, with a default implementation (public Presidio
457+
from PyPI) that resolves in the GitHub build (no internal access). Red Hat's downstream build
458+
shall wire in the GitLab-hosted `data-anonymizer` via an adapter; the GitHub build shall **not**
459+
hard-depend on the internal package. This is separate from the existing `PiiRedactionCapability`
460+
shield, which is unaffected.
461+
462+
377463
## Appendix A: Jira epics and related tracking
378464

379465
**Epics**

0 commit comments

Comments
 (0)