npm package: @leakguard/guard
Description: Runtime protection against secret leakage in responses and logs
Author: LeakGuard Team
Latest version: 3.0.2

# @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?
```typescript
// ❌ 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 correctly
```

## Installation

```bash
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 logger
```

## Quick Start

### Express

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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 string
```

## Configuration Options

### Basic Configuration

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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
```

```typescript
// 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
});
```

```typescript
// 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:

```typescript
// 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:

```typescript
const config = { mode: 'block' };

// Input with secrets → Output
null // Entire response/log blocked

// Clean input → Output  
{ "clean": "data" } // Passed through unchanged
```

### Custom Redaction

Implement your own redaction logic:

```typescript
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:

```typescript
// ✅ 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 redaction
```

### Performance Tips

1. **Use allowlists** to skip known safe values
2. **Disable entropy detection** if not needed (faster)  
3. **Skip safe routes** like health checks
4. **Use redact mode** instead of block mode (faster)

```typescript
// ✅ 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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
// 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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// Express
function expressSecretGuard(config?: ExpressGuardOptions): RequestHandler

// NestJS  
class SecretGuardInterceptor implements NestInterceptor
function createSecretGuardInterceptor(config?: NestGuardOptions): typeof SecretGuardInterceptor
```

### Logger Wrappers

```typescript
// Console
class SecureConsole
function wrapGlobalConsole(config?: LoggerGuardOptions): void

// Pino
function createSecurePinoLogger(logger: any, config?: LoggerGuardOptions): any

// Winston
function createSecureWinstonLogger(logger: any, config?: LoggerGuardOptions): any
```

### Types

```typescript
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

```typescript
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

```typescript
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); // 🔒 Redacted
```

### Admin Panel Security

```typescript
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](../../LICENSE) for details.