Langfuse Security Basics
Overview
Security practices for Langfuse LLM observability: credential management, PII scrubbing before tracing, self-hosted hardening, data retention, and secret scanning.
Prerequisites
- Langfuse instance (cloud or self-hosted)
- API keys provisioned
- Understanding of data privacy requirements (GDPR, SOC2, HIPAA)
Instructions
Step 1: Credential Security
Langfuse uses two keys with different security profiles:
function validateLangfuseCredentials() {
const publicKey = process.env.LANGFUSE_PUBLIC_KEY;
const secretKey = process.env.LANGFUSE_SECRET_KEY;
if (!publicKey || !secretKey) {
throw new Error("LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are required");
}
if (secretKey.startsWith("pk-lf-")) {
throw new Error("LANGFUSE_SECRET_KEY contains a public key (pk-lf-). Keys are swapped.");
}
if (publicKey.startsWith("sk-lf-")) {
throw new Error("LANGFUSE_PUBLIC_KEY contains a secret key (sk-lf-). Keys are swapped.");
}
return { publicKey, secretKey };
}
const { publicKey, secretKey } = validateLangfuseCredentials();
Key security rules:
- Public key (
pk-lf-...): Identifies the project. Safe in client-side code.
- Secret key (
sk-lf-...): Grants write access. Server-side only.
- Store in environment variables or secret manager -- never in source code.
- Rotate keys immediately if exposed in logs, git, or error reports.
Step 2: PII Scrubbing Before Tracing
Langfuse stores everything you send. Scrub PII from inputs and outputs before tracing.
const PII_PATTERNS: Array<{ regex: RegExp; replacement: string }> = [
{ regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}\b/gi, replacement: "[EMAIL]" },
{ regex: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, replacement: "[PHONE]" },
{ regex: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: "[SSN]" },
{ regex: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, replacement: "[CARD]" },
{ regex: /\b(?:sk|pk)-[a-zA-Z0-9_-]{20,}\b/g, replacement: "[API_KEY]" },
];
export function scrubPII(text: string): string {
let scrubbed = text;
for (const { regex, replacement } of PII_PATTERNS) {
scrubbed = scrubbed.replace(regex, replacement);
}
return scrubbed;
}
export function scrubObject(obj: any): any {
if (typeof obj === ) (obj);
(.(obj)) obj.(scrubObject);
( obj === && obj !== ) {
: <, > = {};
( [key, value] .(obj)) {
result[key] = (value);
}
result;
}
obj;
}
import { observe, updateActiveObservation } from "@langfuse/tracing";
import { scrubPII, scrubObject } from "./lib/pii-scrubber";
const tracedChat = observe(async (userMessage: string) => {
updateActiveObservation({ input: scrubPII(userMessage) });
const response = await callLLM(userMessage);
updateActiveObservation({ output: scrubPII(response) });
return response;
});
Step 3: Self-Hosted Hardening
services:
langfuse:
image: langfuse/langfuse:latest
environment:
- AUTH_DISABLE_SIGNUP=true
- AUTH_DOMAINS_WITH_SSO_ENFORCEMENT=company.com
- LANGFUSE_DEFAULT_PROJECT_ROLE=VIEWER
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- LANGFUSE_RETENTION_DAYS=90
- DATABASE_URL=${DATABASE_URL}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- SALT=${SALT}
Step 4: Git Secret Scanning
Prevent Langfuse keys from being committed:
.env
.env.local
.env.production
name: Secret Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for Langfuse keys
run: |
if grep -rn "sk-lf-[a-zA-Z0-9]" --include="*.ts" --include="*.js" --include="*.py" .; then
echo "ERROR: Secret key found in source code!"
exit 1
fi
Step 5: Scoped API Keys and Least Privilege
Security Checklist
| Category | Check | Status |
|---|
| Credentials | Secret key in env vars / secret manager only | |
| Credentials | Keys validated at startup (no swap) | |
| Credentials | .env files in .gitignore | |
| Data Privacy | PII scrubbed from trace inputs/outputs | |
| Data Privacy | Retention policy configured | |
| Self-Hosted | Signup disabled, SSO enforced | |
| Self-Hosted | Encryption key set for data at rest | |
| CI/CD | Secret scanning in pipeline | |
| Access | Least-privilege roles per team member | |
Error Handling
| Issue | Cause | Solution |
|---|
| PII in traces | Not scrubbing before trace | Apply scrubPII() to all inputs/outputs |
| Secret key leaked | Key in source code | Rotate immediately, add secret scanning |
| Unauthorized access | Default roles too permissive | Set LANGFUSE_DEFAULT_PROJECT_ROLE=VIEWER |
| Data accumulation | No retention policy | Set LANGFUSE_RETENTION_DAYS |
Resources