@leakguard/guard
v3.0.2
Published
Runtime protection against secret leakage in responses and logs
Maintainers
Readme
@leakguard/guard
🛡️ Runtime protection against secret leakage in responses and logs
Automatically detect and redact sensitive information like API keys, JWTs, private keys, and more from HTTP responses and application logs.
Why @leakguard/guard?
The Problem
Sensitive data frequently leaks through:
- API responses containing database records with secrets
- Error messages exposing connection strings and tokens
- Application logs recording sensitive request/response data
- Debug output showing internal configuration
- Stack traces containing environment variables
Why It's Hard
- Pattern complexity: Secrets come in many formats (API keys, JWTs, private keys, etc.)
- Performance impact: Scanning every response can slow down your API
- Framework differences: Each web framework handles responses differently
- False positives: Distinguishing secrets from legitimate data
- Logger integration: Different logging libraries have different APIs
Why Not Roll Your Own?
// ❌ Incomplete pattern matching
if (response.includes('api_key')) {
response = response.replace(/api_key=\w+/g, 'api_key=[REDACTED]');
}
// ❌ Misses many secret types
// What about JWTs? Private keys? Database URLs? Credit cards?
// ❌ Performance problems
JSON.stringify(response).replace(/secret/g, '[REDACTED]'); // Scans everything
// ❌ No framework integration
// Manual response interception in every route
// ✅ LeakGuard handles all of this correctlyInstallation
npm install @leakguard/guard
# Peer dependencies for specific frameworks
npm install express @types/express # For Express
npm install @nestjs/common @nestjs/core # For NestJS
npm install pino # For Pino logger
npm install winston # For Winston loggerQuick Start
Express
import express from 'express';
import { expressSecretGuard } from '@leakguard/guard';
const app = express();
// Protect all responses from secret leakage
app.use(expressSecretGuard({
mode: 'redact', // or 'block'
skipRoutes: ['/health', '/metrics']
}));
app.get('/api/config', (req, res) => {
res.json({
apiKey: 'sk_live_secret123', // 🔒 Will be redacted
databaseUrl: 'postgres://user:pass@host/db', // 🔒 Will be redacted
publicConfig: 'safe to expose', // ✅ Will remain visible
debugMode: true // ✅ Will remain visible
});
});
// Response will be:
// {
// "apiKey": "[REDACTED_API_KEY:sk***23]",
// "databaseUrl": "[REDACTED_CONNECTION_STRING:po***db]",
// "publicConfig": "safe to expose",
// "debugMode": true
// }NestJS
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { SecretGuardInterceptor } from '@leakguard/guard';
@Controller('api')
@UseInterceptors(new SecretGuardInterceptor({
mode: 'redact',
allowlist: ['public-api-key'] // Don't redact specific values
}))
export class ApiController {
@Get('config')
getConfig() {
return {
privateKey: process.env.PRIVATE_KEY, // 🔒 Will be redacted
jwt: getAuthToken(), // 🔒 Will be redacted
publicEndpoint: '/api/public' // ✅ Will remain visible
};
}
@Get('user/:id')
getUser(@Param('id') id: string) {
// Even if user object contains secrets from database,
// they will be automatically redacted 🔒
return this.userService.findById(id);
}
}Logger Protection
import { wrapGlobalConsole, createSecurePinoLogger } from '@leakguard/guard';
import pino from 'pino';
// Option 1: Wrap global console
wrapGlobalConsole({ mode: 'redact' });
console.log('API Key:', 'sk_live_secret123');
// Output: API Key: [REDACTED_API_KEY:sk***23]
// Option 2: Wrap specific logger
const logger = pino();
const secureLogger = createSecurePinoLogger(logger, { mode: 'redact' });
secureLogger.info({
user: 'john',
apiKey: 'sk_live_secret123', // 🔒 Will be redacted
action: 'login' // ✅ Will remain visible
});Detection Capabilities
Automatically Detected Secrets
✅ API Keys
- AWS Access Keys (
AKIA...) - Stripe Keys (
sk_live_...,pk_live_...) - Google API Keys (
AIza...) - GitHub Tokens (
ghp_...) - Generic API key patterns
✅ Authentication Tokens
- JWT Tokens (
eyJ...) - Bearer Tokens
- OAuth tokens
- Session tokens
✅ Private Keys
- RSA Private Keys
- EC Private Keys
- DSA Private Keys
- OpenSSH Private Keys
✅ Database & Connection Strings
- MongoDB URLs
- PostgreSQL URLs
- MySQL URLs
- Redis URLs
✅ Sensitive Personal Data
- Credit Card Numbers
- US Social Security Numbers
- Phone numbers (configurable)
✅ High Entropy Strings
- Cryptographic keys
- Random tokens
- Base64 encoded secrets
Pattern Examples
// These will be automatically detected and redacted:
"sk_live_51234567890abcdef" // Stripe API key
"AIzaSyB1234567890abcdef" // Google API key
"ghp_1234567890abcdefghijklmnop" // GitHub personal access token
"AKIAIOSFODNN7EXAMPLE" // AWS access key
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" // JWT
"-----BEGIN RSA PRIVATE KEY-----\n..." // Private key
"mongodb://user:password@host:27017/db" // Database URL
"postgres://user:pass@localhost/db" // PostgreSQL URL
"Bearer sk_live_1234567890abcdef" // Bearer token
"4111 1111 1111 1111" // Credit card
"123-45-6789" // SSN
// High entropy detection (when enabled)
"Ak9f8s7d6g5h4j3k2l1m0n9b8v7c6x5z" // Random stringConfiguration Options
Basic Configuration
import { CoreSecretDetector, ContentRedactor } from '@leakguard/guard';
// Create detector with custom config
const detector = new CoreSecretDetector({
// Enable/disable entropy-based detection
enableEntropyDetection: true,
entropyThreshold: 4.5, // Higher = more strict
// Allowlist specific values
allowlist: [
'public-api-key',
'demo-token-123',
'localhost'
],
// Custom secret patterns
patterns: [
{
name: 'custom_secret',
pattern: /CUSTOM_[A-Z0-9]{16}/g,
confidence: 0.9,
description: 'Custom secret format'
}
]
});
// Create redactor
const redactor = new ContentRedactor({
mode: 'redact', // or 'block'
// Custom redaction function
customRedactor: (content) => {
return typeof content === 'string'
? '***CUSTOM REDACTED***'
: { error: 'Sensitive data removed' };
}
});Framework-Specific Options
Express Configuration
import { expressSecretGuard } from '@leakguard/guard';
app.use(expressSecretGuard({
mode: 'redact',
// Skip specific routes
skipRoutes: ['/health', '/metrics', '/public/*'],
// Skip specific HTTP methods
skipMethods: ['GET', 'OPTIONS'],
// Custom handling
onBlock: (req, res, next) => {
res.status(500).json({
error: 'Response blocked due to sensitive content',
timestamp: new Date().toISOString()
});
},
// Secret detection callback
onSecretDetected: (detection, context) => {
console.warn('Secret detected:', {
secretCount: detection.detectedSecrets.length,
types: detection.detectedSecrets.map(s => s.type),
route: context.url,
method: context.method
});
}
}));NestJS Configuration
import { SecretGuardInterceptor, createSecretGuardInterceptor } from '@leakguard/guard';
// Option 1: Use directly
@UseInterceptors(new SecretGuardInterceptor({
mode: 'redact',
skipRoutes: ['/health'],
onSecretDetected: (detection, context) => {
// Log to your monitoring system
}
}))
// Option 2: Create configured class
const CustomSecretGuard = createSecretGuardInterceptor({
mode: 'block',
skipMethods: ['GET']
});
@UseInterceptors(CustomSecretGuard)
@Controller('sensitive')
export class SensitiveController {
// Will be protected
}Logger Configuration
// Console wrapper
import { SecureConsole, wrapGlobalConsole } from '@leakguard/guard';
// Replace global console
wrapGlobalConsole({
mode: 'redact',
skipLevels: ['debug'], // Don't scan debug logs
onSecretDetected: (detection) => {
// Alert on secrets in logs
sendSecurityAlert(detection);
}
});
// Create secure console instance
const secureConsole = new SecureConsole({
mode: 'redact'
});
secureConsole.log('User token:', userToken); // Automatically redacted// Pino logger wrapper
import { createSecurePinoLogger } from '@leakguard/guard';
import pino from 'pino';
const logger = pino();
const secureLogger = createSecurePinoLogger(logger, {
mode: 'redact',
allowlist: ['public-values']
});
secureLogger.info({
user: 'john',
apiKey: 'secret123', // 🔒 Redacted
action: 'login' // ✅ Preserved
});// Winston logger wrapper
import { createSecureWinstonLogger } from '@leakguard/guard';
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [new winston.transports.Console()]
});
const secureLogger = createSecureWinstonLogger(logger, {
mode: 'redact'
});
secureLogger.error('Database connection failed', {
connectionString: 'postgres://user:pass@host/db' // 🔒 Redacted
});Redaction Modes
Redact Mode (Default)
Replaces secrets with redacted placeholders while preserving data structure:
// Input
{
"user": "john",
"apiKey": "sk_live_1234567890abcdefghij",
"config": {
"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.payload.signature"
}
}
// Output (redacted)
{
"user": "john",
"apiKey": "[REDACTED_API_KEY:sk***ij]",
"config": {
"jwt": "[REDACTED_JWT:ey***re]"
}
}Block Mode
Blocks the entire response/log entry when secrets are detected:
const config = { mode: 'block' };
// Input with secrets → Output
null // Entire response/log blocked
// Clean input → Output
{ "clean": "data" } // Passed through unchangedCustom Redaction
Implement your own redaction logic:
const config = {
mode: 'redact',
customRedactor: (content) => {
if (typeof content === 'string') {
return '***SENSITIVE***';
}
if (typeof content === 'object') {
return {
message: 'This response contained sensitive data',
timestamp: new Date().toISOString()
};
}
return '[BLOCKED]';
}
};Performance
Benchmarks
- Clean content (fast path): 10,000+ ops/sec
- Content with secrets: 1,000-5,000 ops/sec
- Large objects (1MB): ~500 ops/sec
- Memory overhead: <5% for typical payloads
Zero-Cost Fast Path
LeakGuard is optimized for clean content:
// ✅ Fast path - no secrets detected
const cleanResponse = { user: 'john', status: 'active' };
// → Nearly zero overhead
// 🔍 Scan path - secrets detected
const secretResponse = { user: 'john', apiKey: 'sk_live_123' };
// → Full pattern matching and redactionPerformance Tips
- Use allowlists to skip known safe values
- Disable entropy detection if not needed (faster)
- Skip safe routes like health checks
- Use redact mode instead of block mode (faster)
// ✅ Optimized configuration
const optimizedConfig = {
mode: 'redact',
enableEntropyDetection: false, // Faster
skipRoutes: ['/health', '/metrics', '/static/*'],
skipMethods: ['GET'], // Skip read-only endpoints
allowlist: ['public-key', 'demo-token']
};Security Best Practices
1. Use Allowlists for Known Safe Values
const config = {
allowlist: [
'demo-api-key', // Demo/test keys
'public-stripe-key', // Publishable keys
'localhost', // Local development
'development-token' // Non-production secrets
]
};2. Configure Appropriate Skip Rules
const config = {
// Skip high-traffic, low-risk endpoints
skipRoutes: [
'/health',
'/metrics',
'/static/*',
'/public/*'
],
// Skip read-only methods (if secrets unlikely in responses)
skipMethods: ['GET', 'HEAD', 'OPTIONS']
};3. Monitor Secret Detection Events
const config = {
onSecretDetected: (detection, context) => {
// Send to monitoring/alerting system
monitoringService.recordEvent('secret_detected', {
secretTypes: detection.detectedSecrets.map(s => s.type),
route: context?.url,
method: context?.method,
timestamp: new Date().toISOString()
});
// Alert on high-confidence secrets
const highConfidenceSecrets = detection.detectedSecrets
.filter(s => s.confidence > 0.8);
if (highConfidenceSecrets.length > 0) {
alertingService.sendAlert('high_confidence_secret_leak', {
secrets: highConfidenceSecrets,
context
});
}
}
};4. Use Block Mode for Critical Endpoints
// Block mode for admin/sensitive endpoints
app.use('/admin/*', expressSecretGuard({ mode: 'block' }));
// Redact mode for regular API endpoints
app.use('/api/*', expressSecretGuard({ mode: 'redact' }));5. Protect Logs in Production
// Only wrap console in production
if (process.env.NODE_ENV === 'production') {
wrapGlobalConsole({
mode: 'redact',
// More aggressive scanning in production
enableEntropyDetection: true
});
}Custom Patterns
Adding Custom Secret Types
import { DEFAULT_PATTERNS, CoreSecretDetector } from '@leakguard/guard';
const customPatterns = [
{
name: 'internal_token',
pattern: /INT_[A-Z0-9]{32}/g,
confidence: 0.95,
description: 'Internal system token'
},
{
name: 'employee_id',
pattern: /EMP\d{8}/g,
confidence: 0.8,
description: 'Employee identifier'
},
{
name: 'api_secret',
pattern: /secret_[a-z0-9]{24}/gi,
confidence: 0.9,
description: 'API secret key'
}
];
const detector = new CoreSecretDetector({
patterns: [...DEFAULT_PATTERNS, ...customPatterns]
});Entropy-Based Detection
const detector = new CoreSecretDetector({
enableEntropyDetection: true,
entropyThreshold: 4.5, // Higher = more strict
// Fine-tune entropy detection
minLength: 16, // Minimum string length to check
maxLength: 100 // Maximum string length to check
});
// Will detect high-randomness strings like:
// "Ak9f8s7d6g5h4j3k2l1m0n9b8v7c6x5z" // High entropy
// "aaaaaaaaaaaaaaaaaaaa" // Low entropy (ignored)Context-Aware Patterns
const detector = new CoreSecretDetector({
patterns: [
{
name: 'admin_session',
pattern: /admin_sess_[a-f0-9]{40}/g,
confidence: 1.0,
description: 'Admin session token'
}
],
// Custom detection based on context
contextualDetection: (content, context) => {
// More aggressive detection for admin routes
if (context?.url?.includes('/admin/')) {
return {
enableEntropyDetection: true,
entropyThreshold: 3.5 // Lower threshold = more sensitive
};
}
return {};
}
});Troubleshooting
Common Issues
1. False Positives
// Issue: Legitimate data being redacted
const response = { orderId: 'ORDER_1234567890ABCDEF' }; // Looks like API key
// ✅ Solution: Use allowlist
const config = {
allowlist: ['ORDER_', 'USER_', 'TEMP_'] // Prefixes to ignore
};
// ✅ Alternative: Adjust pattern confidence
const customPatterns = DEFAULT_PATTERNS.map(pattern => ({
...pattern,
confidence: pattern.name === 'api_key' ? 0.95 : pattern.confidence
}));2. Performance Issues
// Issue: Slow response times
// ✅ Debug performance
const detector = new CoreSecretDetector({
onDetectionStart: () => console.time('detection'),
onDetectionEnd: () => console.timeEnd('detection')
});
// ✅ Optimize configuration
const optimizedConfig = {
enableEntropyDetection: false, // Disable if not needed
skipLargeObjects: true, // Skip objects > 1MB
skipMethods: ['GET'], // Skip read-only endpoints
patterns: customPatterns.slice(0, 5) // Use fewer patterns
};3. Missing Secrets
// Issue: Expected secrets not being detected
// ✅ Debug detection
const detector = new CoreSecretDetector();
const result = detector.detect(content);
console.log('Detection result:', {
hasSecrets: result.hasSecrets,
secretCount: result.detectedSecrets.length,
detectedTypes: result.detectedSecrets.map(s => s.type)
});
// ✅ Test specific patterns
DEFAULT_PATTERNS.forEach(pattern => {
pattern.pattern.lastIndex = 0; // Reset regex
if (pattern.pattern.test(content)) {
console.log('Matched pattern:', pattern.name);
}
});4. Framework Integration Issues
// Issue: Middleware not working
// ✅ Check middleware order
app.use(express.json()); // Must be before guard
app.use(expressSecretGuard({})); // Guard processes parsed body
// ✅ Debug request processing
app.use('/api', (req, res, next) => {
console.log('Request body type:', typeof req.body);
console.log('Body content:', req.body);
next();
}, expressSecretGuard({}));
// ✅ Check response interception
const originalJson = res.json;
res.json = function(body) {
console.log('Response body:', body);
return originalJson.call(this, body);
};API Reference
Core Classes
// Secret detection
class CoreSecretDetector {
constructor(config?: GuardConfig)
detect(content: any, context?: any): SecretDetectionResult
}
// Content redaction
class ContentRedactor {
constructor(config?: GuardConfig)
redactContent(content: any, detection: SecretDetectionResult): {
redacted: any;
blocked: boolean;
}
}Framework Adapters
// Express
function expressSecretGuard(config?: ExpressGuardOptions): RequestHandler
// NestJS
class SecretGuardInterceptor implements NestInterceptor
function createSecretGuardInterceptor(config?: NestGuardOptions): typeof SecretGuardInterceptorLogger Wrappers
// Console
class SecureConsole
function wrapGlobalConsole(config?: LoggerGuardOptions): void
// Pino
function createSecurePinoLogger(logger: any, config?: LoggerGuardOptions): any
// Winston
function createSecureWinstonLogger(logger: any, config?: LoggerGuardOptions): anyTypes
interface GuardConfig {
mode: 'redact' | 'block';
patterns?: SecretPattern[];
allowlist?: string[];
enableEntropyDetection?: boolean;
entropyThreshold?: number;
onSecretDetected?: (detection: SecretDetectionResult, context?: any) => void;
customRedactor?: (content: any) => any;
}
interface SecretDetectionResult {
hasSecrets: boolean;
detectedSecrets: DetectedSecret[];
redactedContent?: any;
}
interface DetectedSecret {
type: string;
field?: string;
value: string;
start: number;
end: number;
confidence: number;
}Examples
E-commerce API Protection
import express from 'express';
import { expressSecretGuard } from '@leakguard/guard';
const app = express();
// Protect customer data endpoints
app.use('/api/customers', expressSecretGuard({
mode: 'redact',
// E-commerce specific patterns
patterns: [
{
name: 'credit_card',
pattern: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
confidence: 0.8
},
{
name: 'payment_token',
pattern: /pay_[a-zA-Z0-9]{24}/g,
confidence: 0.95
}
],
onSecretDetected: (detection, context) => {
// Alert on PII exposure
if (detection.detectedSecrets.some(s => s.type === 'credit_card')) {
securityAlert('PII_EXPOSURE', { route: context.url });
}
}
}));
app.get('/api/customers/:id', async (req, res) => {
const customer = await db.customers.findById(req.params.id);
// Credit cards, payment tokens automatically redacted 🔒
res.json(customer);
});Microservice Logging
import { createSecurePinoLogger, wrapGlobalConsole } from '@leakguard/guard';
import pino from 'pino';
// Service-wide console protection
wrapGlobalConsole({
mode: 'redact',
allowlist: ['service-name', 'public-endpoints']
});
// Structured logging with protection
const logger = pino();
const secureLogger = createSecurePinoLogger(logger, {
mode: 'redact',
// Microservice specific patterns
patterns: [
{
name: 'service_token',
pattern: /svc_[a-z]{8}_[a-f0-9]{32}/g,
confidence: 0.9
}
]
});
// Usage throughout application
secureLogger.info('User authenticated', {
userId: '12345',
serviceToken: 'svc_auth_abc123...', // 🔒 Redacted
endpoint: '/api/auth' // ✅ Preserved
});
console.log('Database connected:', dbConnectionString); // 🔒 RedactedAdmin Panel Security
import { SecretGuardInterceptor } from '@leakguard/guard';
// Strict protection for admin endpoints
const AdminSecretGuard = createSecretGuardInterceptor({
mode: 'block', // Block any response with secrets
enableEntropyDetection: true,
entropyThreshold: 3.0, // Very sensitive
onSecretDetected: (detection, context) => {
// Immediate security alert for admin endpoints
securityIncident('ADMIN_SECRET_EXPOSURE', {
secrets: detection.detectedSecrets,
admin: context.user?.id,
timestamp: new Date().toISOString()
});
}
});
@Controller('admin')
@UseInterceptors(AdminSecretGuard)
export class AdminController {
@Get('system/config')
getSystemConfig() {
// Any response with secrets will be blocked 🚫
return systemConfig;
}
@Get('users/:id/details')
getUserDetails(@Param('id') id: string) {
// Comprehensive user data (may contain secrets) 🚫
return userService.getFullUserDetails(id);
}
}License
MIT - see LICENSE for details.
