민감한 LLM 데이터 마스킹
민감한 LLM 데이터 마스킹 (Masking sensitive LLM data)
마스킹은 Langfuse로 보내지는 tracing 데이터를 제어할 수 있게 해줘요. 마스킹 함수를 사용해 trace 데이터가 애플리케이션을 떠나기 전에 민감한 정보를 편집(redact)할 수 있어요. 예를 들어 trace·observation의 입력·출력·메타데이터에서 민감한 정보를 편집하거나, 내보내기 전에 OpenTelemetry span 속성을 변환하거나, 컴플라이언스·개인정보 요구사항을 위한 세밀한 데이터 필터링을 구현할 수 있어요.
출처: 문서
본문
마스킹은 Langfuse로 보내지는 tracing 데이터를 제어할 수 있게 해줘요. 마스킹 함수를 사용해 trace 데이터가 애플리케이션을 떠나기 전에 민감한 정보를 편집할 수 있어요. 예를 들어:
- trace·observation 입력·출력·메타데이터에서 민감한 정보를 편집해요.
- 내보내기 전에 OpenTelemetry span 속성을 변환해요.
- 컴플라이언스·개인정보 요구사항을 위한 세밀한 데이터 필터링을 구현해요.
저장된 데이터에 관한 Langfuse의 데이터 보안·개인정보 조치는 security and compliance overview에서 자세히 알아볼 수 있어요.
마스킹 구성 (Configure masking)
Python SDK — 마스킹 훅 두 가지를 지원해요. 새 Python SDK 설정에서는 mask_otel_spans를 선호하세요.
| Method | Status | When it runs | What it covers |
|---|---|---|---|
| mask_otel_spans | 권장 | 내보내기 단계, Langfuse가 이 클라이언트가 내보낼 OpenTelemetry span을 결정한 후, Langfuse 미디어 처리 후 | 이 Langfuse 클라이언트가 내보낸 Langfuse SDK span 및 서드파티 계측의 원시 OpenTelemetry span 속성 |
| mask | 레거시 | Langfuse SDK 속성이 생성될 때 동기적으로 | start_observation(), update(), set_trace_io() 같은 Langfuse SDK API로 설정된 데이터 |
from typing import Optional
from langfuse import Langfuse
from langfuse.types import (
MaskOtelSpansParams,
MaskOtelSpansResult,
OtelSpanPatch,
)
def mask_otel_spans(
*, params: MaskOtelSpansParams
) -> Optional[MaskOtelSpansResult]:
patches = {}
for identifier, span in params.spans.items():
if span.instrumentation_scope_name == "openai":
patches[identifier] = OtelSpanPatch(
delete_attributes=(
"gen_ai.prompt.0.content",
"gen_ai.completion.0.content",
),
set_attributes={"masking.applied": True},
)
return MaskOtelSpansResult(span_patches=patches)
langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
mask_otel_spans를 사용해 OpenTelemetry span 속성을 Langfuse 클라이언트가 내보내기 전에 패치하세요. 훅은 하나의 OpenTelemetry 내보내기 배치의 읽기 전용 스냅샷을 받고, 변경할 span에 대한 희소(sparse) 패치를 반환해요.
mask_otel_spans 동작 (mask_otel_spans behavior)
mask_otel_spans는 공개 Python SDK 타입 계약을 따릅니다:
- 하나의 OpenTelemetry 내보내기 배치를
params.spans로 받아요. 배치가 완전한 trace, 요청, Langfuse observation 트리를 포함한다는 보장은 없어요. - 각 키는
OtelSpanIdentifier(trace_id, span_id)예요. 패치를 반환할 때params.spans의 식별자 객체를 재사용하세요. - 각 값은
should_export_span필터링과 내보내기 단계 미디어 처리 후의OtelSpanData스냅샷이에요. 그 attributes와 resource_attributes 매핑은 읽기 전용이에요. None을 반환해 전체 배치를 변경 없이 두세요.MaskOtelSpansResult(span_patches=...)를 반환해 선택된 span의 속성을 삭제하거나 교체하세요.- 패치는 희소해요. 변경이 필요하지 않은 span은 생략하세요.
OtelSpanPatch는 먼저delete_attributes를 삭제한 다음set_attributes를 적용하므로, 같은 키가 둘 다에 있으면set_attributes가 이겨요.set_attributes값은 유효한 OpenTelemetry 속성 값이어야 해요: 문자열, 불리언, 정수, 실수, 또는 그 스칼라 타입들의 동질 시퀀스.- 훅은 span 속성만 변경할 수 있어요. span 이름, ID, 부모 관계, 리소스 속성, 이벤트, 링크, 계측 범위는 변경할 수 없어요.
- 훅은 이 Langfuse 클라이언트가 내보낸 span에만 영향을 줘요. 같은 OpenTelemetry span이 다른 exporter로 보내지면 그 exporter는 자체 수정되지 않은 복사본을 받아요.
mask_otel_spans는 Langfuse Python SDK span processor를 통과해 이 Langfuse 클라이언트가 내보내는 span에만 영향을 줘요. 별도 OpenTelemetry span processor나 exporter로 다른 observability 백엔드에도 텔레메트리를 보낸다면, 그 백엔드는 수정되지 않은 자체 복사본을 받아요. Langfuse가 아닌 exporter에는 마스킹을 별도로 구성하세요.
mask_otel_spans는 동기적이에요. 보통 OpenTelemetry 배치 span processor 워커 스레드에서 실행되므로 애플리케이션의 메인 호출자 스레드를 막지 않아야 해요.flush()와shutdown()동안에는 호출자 스레드에서 실행될 수 있어요.
함수를 결정적이고 빠르게 유지하세요. 네트워크 호출은 가능하지만, 느린 마스킹은 OpenTelemetry 내보내기 큐를 막고 span 내보내기를 지연시켜요. 마스킹 함수 안에서 장기 실행 작업, 무제한 재시도, 요청 로컬 상태, 현재 활성 span, async I/O를 피하세요.
mask_otel_spans가 예외를 던지거나 잘못된MaskOtelSpansResult를 반환하면 Langfuse는 전체 내보내기 배치를 버려요. 개별OtelSpanPatch가 유효하지 않으면 Langfuse는 Langfuse 내보내기에서 그 span만 버려요. 잘못된 반환 속성 값은 해당 속성만 삭제해요.
레거시 mask 동작 (Legacy mask behavior)
mask파라미터는 레거시 Python SDK 마스킹 훅이에요. Langfuse SDK 속성이 생성될 때 동기적으로 실행되며,start_observation(),update(),set_trace_io()같은 Langfuse SDK API로 설정된 데이터에만 적용돼요. 서드파티 계측의 최종 원시 OpenTelemetry span 속성은 검사하지 않아요.
from typing import Any
from langfuse import Langfuse
def masking_function(*, data: Any, **kwargs: Any) -> Any:
if isinstance(data, str) and data.startswith("SECRET_"):
return "REDACTED"
if isinstance(data, dict):
return {key: masking_function(data=value) for key, value in data.items()}
if isinstance(data, list):
return [masking_function(data=item) for item in data]
return data
langfuse = Langfuse(mask=masking_function)
mask는 Langfuse SDK 속성 생성 시점에 데이터를 변환해야 할 때만 사용하세요.
새 Python SDK 마스킹 설정에서는
mask_otel_spans를 선호하세요.
예시 (Examples)
mask_otel_spans로 신용카드 번호 편집 (Redact credit card numbers with mask_otel_spans)
import re
from typing import Optional
from langfuse import Langfuse, observe
from langfuse.types import (
MaskOtelSpansParams,
MaskOtelSpansResult,
OtelSpanPatch,
)
credit_card_pattern = re.compile(r"\b(?:\d[ -]*?){13,19}\b")
def mask_otel_spans(
*, params: MaskOtelSpansParams
) -> Optional[MaskOtelSpansResult]:
patches = {}
for identifier, span in params.spans.items():
replacements = {}
for key, value in span.attributes.items():
if isinstance(value, str):
masked_value = credit_card_pattern.sub(
"[REDACTED CREDIT CARD]", value
)
if masked_value != value:
replacements[key] = masked_value
if replacements:
patches[identifier] = OtelSpanPatch(set_attributes=replacements)
return MaskOtelSpansResult(span_patches=patches)
langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
@observe()
def process_payment():
return "Customer paid with card number 4111 1111 1111 1111."
result = process_payment()
print(result)
# Output: Customer paid with card number 4111 1111 1111 1111.
# Flush spans in short-lived applications.
langfuse.flush()
이 예시는 내보낸 OpenTelemetry 문자열 속성에서 신용카드류 패턴을 스캔하고, span이 Langfuse로 내보내지기 전에 일치 항목을 교체해요.
애플리케이션에서 출력된 함수 결과는 변경되지 않아요. Langfuse로 보내진 내보낸 span 속성에는 편집된 값이 들어 있어요.
이메일 주소와 전화번호 편집 (Redact email addresses and phone numbers)
import re
from typing import Optional
from langfuse import Langfuse
from langfuse.types import (
MaskOtelSpansParams,
MaskOtelSpansResult,
OtelSpanPatch,
)
email_pattern = re.compile(r"\b[\w.-]+?@[\w.-]+?\.\w+?\b")
phone_pattern = re.compile(r"\b\d{3}[-. ]?\d{3}[-. ]?\d{4}\b")
def mask_otel_spans(
*, params: MaskOtelSpansParams
) -> Optional[MaskOtelSpansResult]:
patches = {}
for identifier, span in params.spans.items():
replacements = {}
for key, value in span.attributes.items():
if isinstance(value, str):
masked_value = email_pattern.sub("[REDACTED EMAIL]", value)
masked_value = phone_pattern.sub("[REDACTED PHONE]", masked_value)
if masked_value != value:
replacements[key] = masked_value
if replacements:
patches[identifier] = OtelSpanPatch(set_attributes=replacements)
return MaskOtelSpansResult(span_patches=patches)
langfuse = Langfuse(mask_otel_spans=mask_otel_spans)
JS/TS SDK (LangChain)
민감한 데이터가 Langfuse로 보내지지 않도록 LangfuseSpanProcessor에 mask 함수를 제공할 수 있어요. 이 함수는 모든 observation의 입력·출력·메타데이터에 적용돼요.
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
const spanProcessor = new LangfuseSpanProcessor({
mask: ({ data }) => {
const maskedData = data.replace(
/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g,
"***MASKED_CREDIT_CARD***",
);
return maskedData;
},
});
const sdk = new NodeSDK({
spanProcessors: [spanProcessor],
});
sdk.start();
이 함수는 속성 값의 문자열화된 JSON인 { data } 객체를 받아 마스킹된 데이터를 반환해요.
자세한 내용은 JS/TS SDK docs를 참고하세요.
마스킹은 LangfuseSpanProcessor에 구성돼요 — LangChain CallbackHandler가 만든 span은 자동으로 이를 통과하므로 LangChain 전용 마스킹 설정이 필요 없어요:
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { CallbackHandler } from "@langfuse/langchain";
const spanProcessor = new LangfuseSpanProcessor({
mask: ({ data }) => {
if (typeof data === "string" && data.startsWith("SECRET_")) {
return "REDACTED";
}
return data;
},
});
const sdk = new NodeSDK({ spanProcessors: [spanProcessor] });
sdk.start();
const handler = new CallbackHandler();
OpenTelemetry로 마스킹 (Masking with OpenTelemetry)
순수 OpenTelemetry tracing 설정에서는 민감한 span 속성을 Langfuse에 도달하기 전에 마스킹하세요. 두 위치에서 마스킹을 적용할 수 있어요:
- 애플리케이션에서: 가능하면 민감한 속성을 기록하지 마세요. 그렇지 않으면 언어별 또는 커스텀 span processor나 내보내기 계층을 사용해 OTLP 내보내기 전에 변환하세요. 민감한 데이터가 애플리케이션을 떠나면 안 될 때 이 접근을 선택하세요.
- OpenTelemetry Collector에서: 선택적 Collector로 trace를 라우팅하고 attributes·redaction·transform processor를 사용해 중앙에서 속성을 마스킹하세요. filter processor는 전체 span을 버리므로, span을 의도적으로 제거하고 싶을 때만 사용하세요. 이 접근은 여러 서비스에 일관된 정책을 적용할 때 유용해요.
Collector 측 마스킹은 텔레메트리가 애플리케이션을 떠난 후에 일어나요. Collector를 적절한 트러스트 경계 안에 배포하고 애플리케이션과 Collector 사이의 연결을 보안하세요.
일반적인 Collector processor 패턴은 OpenTelemetry의 handling sensitive data 가이드를, 내보내기 설정은 Langfuse OpenTelemetry integration 가이드를 참고하세요.
관련 자료 (Related resources)
- Data Retention — 구성된 보존 기간 후 trace·observation·score·미디어 자산을 자동 삭제해요.
- Data Deletion — 개별 또는 배치 trace를 수동 삭제해요.