Redaction
Protect sensitive data with built-in redaction capabilities
Redaction
Cenglu provides powerful, built-in redaction to automatically detect and mask sensitive information in your logs.
Why Redaction Matters
Logging sensitive data can lead to:
- Security breaches: Exposed credentials, API keys, tokens
- Compliance violations: PCI DSS, GDPR, HIPAA, SOC 2
- Data leaks: Personal information, financial data, health records
- Incident response: Compromised logs require immediate rotation of secrets
Quick Start
Enable basic redaction:
import { createLogger } from "cenglu";
const logger = createLogger({
redaction: {
enabled: true,
},
});
logger.info("User registered", {
email: "john@example.com",
password: "super-secret", // -> [REDACTED]
creditCard: "4242-4242-4242-4242", // -> [REDACTED]
apiKey: "sk_live_abc123", // -> [REDACTED]
});Output:
{
"level": "info",
"msg": "User registered",
"context": {
"email": "john@example.com",
"password": "[REDACTED]",
"creditCard": "[REDACTED]",
"apiKey": "[REDACTED]"
}
}Default Patterns
When enabled: true with useDefaults: true (default), cenglu automatically redacts:
Credentials
password,passwd,pwdsecret,secretKeyapiKey,api_keyaccessToken,access_tokenauthToken,auth_tokenprivateKey,private_key
Financial Data
- Credit card numbers (Visa, Mastercard, Amex, Discover)
- CVV codes
- Bank account numbers
Personal Information
- Email addresses
- Social Security Numbers (SSN)
- Phone numbers
API Tokens
- Bearer tokens
- JWT tokens
- AWS access keys
- GitHub tokens
- Stripe keys
Path-Based Redaction
Redact specific object paths:
const logger = createLogger({
redaction: {
enabled: true,
paths: [
"password",
"user.password",
"creditCard",
"payment.cvv",
"config.database.password",
],
},
});
logger.info("Payment processed", {
user: {
name: "John",
password: "secret123", // -> [REDACTED]
},
payment: {
amount: 100,
cvv: "123", // -> [REDACTED]
},
});Path syntax:
password- Redacts all keys named "password"user.password- Redacts nested path- Supports nested objects and arrays
Pattern-Based Redaction
Use regex patterns for flexible redaction:
const logger = createLogger({
redaction: {
enabled: true,
patterns: [
{
name: "bearer-token",
pattern: /Bearer\s+[A-Za-z0-9\-._~+/]+=*/g,
replacement: "Bearer [REDACTED]",
},
{
name: "email",
pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
replacement: "[EMAIL]",
},
{
name: "phone",
pattern: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
replacement: "[PHONE]",
},
{
name: "ip-address",
pattern: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
replacement: "[IP]",
},
],
},
});
logger.info("Request received", {
authorization: "Bearer sk_live_abcdef123456", // -> "Bearer [REDACTED]"
from: "john@example.com", // -> "[EMAIL]"
phone: "555-123-4567", // -> "[PHONE]"
ip: "192.168.1.1", // -> "[IP]"
});Custom Redactor Function
Implement custom redaction logic:
const logger = createLogger({
redaction: {
enabled: true,
customRedactor: (value, key) => {
// Redact values longer than 100 characters
if (typeof value === "string" && value.length > 100) {
return `[LONG_STRING:${value.length}]`;
}
// Redact specific keys
if (key === "sensitiveData") {
return "[SENSITIVE]";
}
// Partial redaction for emails
if (key === "email" && typeof value === "string") {
const [local, domain] = value.split("@");
return `${local.slice(0, 2)}***@${domain}`;
}
// Return undefined to skip custom redaction
return undefined;
},
},
});
logger.info("User data", {
email: "john.doe@example.com", // -> "jo***@example.com"
sensitiveData: "secret", // -> "[SENSITIVE]"
longText: "a".repeat(200), // -> "[LONG_STRING:200]"
});Compliance Presets
PCI DSS Compliance
For payment card industry compliance:
import { createLogger, createPCIRedactor } from "cenglu";
const logger = createLogger({
redaction: createPCIRedactor(),
});
logger.info("Payment processed", {
cardNumber: "4242424242424242", // -> [REDACTED]
cvv: "123", // -> [REDACTED]
expiryDate: "12/25", // Logged (not PII)
amount: 99.99, // Logged (not PII)
});PCI Redactor includes:
- Credit card numbers (all major brands)
- CVV/CVC codes
- Card security codes
- Track data
GDPR Compliance
For EU data protection compliance:
import { createLogger, createGDPRRedactor } from "cenglu";
const logger = createLogger({
redaction: createGDPRRedactor(),
});
logger.info("User updated", {
email: "john@example.com", // -> [REDACTED]
name: "John Doe", // -> [REDACTED]
address: "123 Main St", // -> [REDACTED]
userId: "user-123", // Logged (identifier, not PII)
});GDPR Redactor includes:
- Email addresses
- Names
- Addresses
- Phone numbers
- IP addresses
- Geolocation data
HIPAA Compliance
For healthcare data protection:
import { createLogger, createHIPAARedactor } from "cenglu";
const logger = createLogger({
redaction: createHIPAARedactor(),
});
logger.info("Patient record accessed", {
ssn: "123-45-6789", // -> [REDACTED]
medicalRecordNumber: "MRN123", // -> [REDACTED]
email: "patient@example.com", // -> [REDACTED]
patientId: "patient-123", // Logged (de-identified)
});HIPAA Redactor includes:
- Social Security Numbers
- Medical record numbers
- Health plan numbers
- Email addresses
- Phone numbers
- Full names
- Addresses
Combining Redaction Configs
Merge multiple redaction configurations:
import {
createLogger,
createPCIRedactor,
mergeRedactionOptions,
} from "cenglu";
const customRedaction = {
paths: ["internalId", "sessionToken"],
patterns: [
{
pattern: /internal-[0-9]+/g,
replacement: "[INTERNAL_ID]",
},
],
};
const logger = createLogger({
redaction: mergeRedactionOptions(
createPCIRedactor(),
customRedaction
),
});Message Redaction
Redaction applies to log messages, not just context:
const logger = createLogger({
redaction: {
enabled: true,
patterns: [
{
pattern: /password=\S+/g,
replacement: "password=[REDACTED]",
},
],
},
});
logger.info(
"Login failed: password=secret123 for user=john"
);
// Output: "Login failed: password=[REDACTED] for user=john"Error Redaction
Redaction applies to error messages and stack traces:
const logger = createLogger({
redaction: {
enabled: true,
},
});
try {
throw new Error("API key sk_live_abc123 is invalid");
} catch (error) {
logger.error("Authentication failed", error);
// Error message: "API key [REDACTED] is invalid"
}Standalone Redactor
Use redaction outside of logging:
import { createRedactor } from "cenglu";
const redactor = createRedactor({
paths: ["password", "apiKey"],
patterns: [
{
pattern: /Bearer\s+\S+/g,
replacement: "Bearer [REDACTED]",
},
],
});
const data = {
username: "john",
password: "secret123",
apiKey: "sk_live_abc",
authorization: "Bearer token123",
};
const redacted = redactor.redact(data);
console.log(redacted);
// {
// username: "john",
// password: "[REDACTED]",
// apiKey: "[REDACTED]",
// authorization: "Bearer [REDACTED]"
// }String Redaction
Redact strings directly:
import { redactString } from "cenglu";
const text = "Password: secret123, API Key: sk_live_abc";
const redacted = redactString(text, [
{
pattern: /Password:\s*\S+/g,
replacement: "Password: [REDACTED]",
},
{
pattern: /API Key:\s*\S+/g,
replacement: "API Key: [REDACTED]",
},
]);
console.log(redacted);
// "Password: [REDACTED], API Key: [REDACTED]"Performance Considerations
Redaction has minimal performance impact when designed properly:
Fast paths:
- Disabled redaction: No overhead
- Path-based redaction: O(1) lookups
- Pattern matching: Lazy evaluation
Best practices:
- Use path-based redaction when possible (faster than patterns)
- Compile patterns outside of redactor creation
- Keep pattern count reasonable (< 20)
- Use specific patterns (avoid
.*)
Testing Redaction
Verify redaction works correctly:
import { createLogger } from "cenglu";
import { expect, test } from "vitest";
test("redacts passwords", () => {
const logs: any[] = [];
const logger = createLogger({
redaction: { enabled: true },
adapters: [
{
name: "test",
handle: (record) => logs.push(record),
},
],
});
logger.info("User created", {
username: "john",
password: "secret123",
});
expect(logs[0].context.password).toBe("[REDACTED]");
expect(logs[0].context.username).toBe("john");
});Troubleshooting
Pattern Not Matching
// Bad: Patterns must be global (/g flag)
{
pattern: /Bearer\s+\S+/,
replacement: "Bearer [REDACTED]",
}
// Good: Use /g flag
{
pattern: /Bearer\s+\S+/g,
replacement: "Bearer [REDACTED]",
}Path Not Working
// Paths are case-sensitive
paths: ["Password"] // Won't match "password"
paths: ["password"] // Matches "password"
// Use both if needed
paths: ["password", "Password", "PASSWORD"]Custom Redactor Not Applied
// Must return undefined to skip
customRedactor: (value, key) => {
if (shouldRedact(value)) {
return "[REDACTED]";
}
// IMPORTANT: Return undefined, not the original value
return undefined;
}Security Best Practices
- Enable in all environments: Don't disable redaction in dev/staging
- Use compliance presets: Start with PCI/GDPR/HIPAA redactors
- Review logs regularly: Audit for missed sensitive data
- Test redaction: Write tests to verify sensitive data is masked
- Use secure defaults: Keep
useDefaults: true - Combine strategies: Use paths + patterns + custom redactor
- Rotate on exposure: If secrets are logged, rotate immediately