The logging system automatically redacts sensitive data before it reaches any log transport layer. This ensures that secrets (Stellar seeds, HMAC keys, Bearer tokens) are never exposed in logs, even if developers accidentally log them.
Pattern: S[A-Z2-7]{55} (56 characters total, base32 alphabet)
Example:
SALAACGR7QWWI7WQMXLA7YJKHQWZQMQOCQBJXTQXCWZGM7QWWI7WQMXLA
Redacted Output:
[REDACTED_STELLAR_SECRET]
CRITICAL: Stellar public keys (starting with G) are NOT redacted. Public keys are safe to log and are essential for debugging.
Example (Public Key — NOT Redacted):
GALAACGR7QWWI7WQMXLA7YJKHQWZQMQOCQBJXTQXCWZGM7QWWI7WQMXLA
Key Names (Case-Insensitive):
secretsigning_keywebhook_secrethmac_secretapi_secretclient_secretconsumer_secret
Behavior: Any value associated with these keys is redacted.
Example:
logger.log('Webhook config', {
url: 'https://example.com/hook',
signing_key: 'a'.repeat(64), // HMAC secret
});
// Output:
// Webhook config { url: 'https://example.com/hook', signing_key: '[REDACTED]' }Pattern: Bearer <token> where token is base64url-encoded (JWT/OAuth).
Example:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
Redacted Output:
Bearer [REDACTED_TOKEN]
Key Names (Case-Insensitive):
authorizationtokenaccess_tokenrefresh_tokenbeareroauth_tokensession_token
Key Names (Case-Insensitive):
passwordpasswdpwdapi_keyapikeyprivate_keyprivatekey
Long Secrets (40+ Characters): Any string of 40+ base64/hex characters is redacted (unless it's a Stellar public key).
Example:
logger.log('API key', { api_key: 'sk_live_1234567890abcdef' });
// Output:
// API key { api_key: '[REDACTED]' }Replace all console.log, console.error, console.warn calls with:
import { logger } from './logger';
// Before (UNSAFE):
console.log('User secret:', stellarSecret);
// After (SAFE):
logger.log('User secret:', stellarSecret);
// Output: User secret: [REDACTED_STELLAR_SECRET]All arguments passed to logger.log, logger.error, logger.warn are automatically redacted before being written to the console.
Example:
logger.log('Webhook delivery', {
providerId: 'acme',
secret: 'SALAACGR7QWWI7WQMXLA7YJKHQWZQMQOCQBJXTQXCWZGM7QWWI7WQMXLA',
publicKey: 'GALAACGR7QWWI7WQMXLA7YJKHQWZQMQOCQBJXTQXCWZGM7QWWI7WQMXLA',
});
// Output:
// Webhook delivery {
// providerId: 'acme',
// secret: '[REDACTED]',
// publicKey: 'GALAACGR7QWWI7WQMXLA7YJKHQWZQMQOCQBJXTQXCWZGM7QWWI7WQMXLA'
// }The redaction utility recursively traverses deeply nested objects, arrays, and error objects.
logger.log('User data', {
user: {
profile: {
stellarAccount: {
publicKey: 'G...',
secretKey: 'S...', // Redacted
},
},
},
});
// Output:
// User data {
// user: {
// profile: {
// stellarAccount: {
// publicKey: 'G...',
// secretKey: '[REDACTED_STELLAR_SECRET]'
// }
// }
// }
// }const error = new Error('Authentication failed with secret: S...');
logger.error('Error occurred', error);
// Output:
// Error occurred {
// name: 'Error',
// message: 'Authentication failed with secret: [REDACTED_STELLAR_SECRET]',
// stack: '...' // Stack trace also redacted
// }const error = new Error('Test error');
error.stack = `Error: Test error
at processEvent (idempotency.ts:100:20)
Secret seed: SALAACGR7QWWI7WQMXLA7YJKHQWZQMQOCQBJXTQXCWZGM7QWWI7WQMXLA`;
logger.error('Stack trace', error);
// Output:
// Stack trace {
// name: 'Error',
// message: 'Test error',
// stack: 'Error: Test error\n at processEvent (idempotency.ts:100:20)\n Secret seed: [REDACTED_STELLAR_SECRET]'
// }Developers can register custom sensitive keys that should always be redacted:
import { registerSensitiveKey } from './redact';
// Register a new sensitive key
registerSensitiveKey('my_custom_secret');
// Now this key will be redacted
logger.log('Config', { my_custom_secret: 'value123' });
// Output: Config { my_custom_secret: '[REDACTED]' }Recommended Keys to Register:
- Custom API keys (e.g.,
stripe_secret_key,twilio_auth_token) - Database credentials (e.g.,
db_password,redis_password) - Third-party service secrets (e.g.,
sendgrid_api_key,aws_secret_key)
Max Depth: 20 levels
Objects nested deeper than 20 levels are replaced with [MAX_DEPTH_EXCEEDED] to prevent stack overflow.
Example:
// Create a deeply nested object (25 levels)
let obj = { value: 'deep' };
for (let i = 0; i < 25; i++) {
obj = { nested: obj };
}
logger.log('Deep object', obj);
// Output: Deep object { nested: { nested: ... { nested: [MAX_DEPTH_EXCEEDED] } } }Circular references are detected using a WeakSet and replaced with [CIRCULAR_REFERENCE].
Example:
const obj = { name: 'test' };
obj.self = obj; // Circular reference
logger.log('Circular object', obj);
// Output: Circular object { name: 'test', self: '[CIRCULAR_REFERENCE]' }Strings shorter than 10 characters skip regex redaction (performance optimization).
Rationale: Secrets are typically 40+ characters. Short strings are unlikely to contain secrets.
All regex patterns are compiled once at module load time and reused for all redaction operations.
Patterns:
STELLAR_SECRET_REGEX:/S[A-Z2-7]{55}/gSTELLAR_PUBLIC_REGEX:/G[A-Z2-7]{55}/gBEARER_TOKEN_REGEX:/Bearer\s+[A-Za-z0-9\-._~+/]+=*/giLONG_SECRET_REGEX:/[A-Za-z0-9+/=]{40,}/g
Before:
console.log('[webhookDelivery] Delivered', { providerId, secret });After:
import { logger } from './logger';
logger.log('[webhookDelivery] Delivered', { providerId, secret });Before:
console.error('[idempotency] Side effect failed:', err);After:
import { logger } from './logger';
logger.error('[idempotency] Side effect failed:', err);Before:
console.warn('[rateLimit] Provider throttled:', providerId);After:
import { logger } from './logger';
logger.warn('[rateLimit] Provider throttled:', providerId);If you want to ensure that ALL console.log calls (including third-party libraries) are redacted, wrap the global console object:
import { wrapConsole } from './logger';
// Enable global redaction (at application startup)
wrapConsole();
// Now all console.log calls are automatically redacted
console.log('Secret:', 'S...'); // Automatically redactedWARNING: This is a global monkey-patch. Use with caution.
npm test -- redact.test.ts
npm run test:ci -- redact.test.tsAll redaction code paths are covered by tests (95%+ line coverage).
Test Cases:
- ✅ Stellar secret seeds (S...) are redacted
- ✅ Stellar public keys (G...) are preserved
- ✅ Bearer tokens are redacted
- ✅ HMAC secrets are redacted
- ✅ Deep nested objects are handled
- ✅ Error objects and stack traces are sanitized
- ✅ Circular references are handled
- ✅ Max depth is enforced
- ✅ Stellar secret seeds (S...) are redacted
- ✅ Stellar public keys (G...) are preserved
- ✅ Webhook HMAC secrets are redacted
- ✅ Bearer tokens are redacted
- ✅ Generic secrets (password, api_key, etc.) are redacted
- ✅ Deep nested objects are traversed
- ✅ Error messages are sanitized
- ✅ Stack traces are sanitized
- ✅ Circular references are handled
- ✅ Max depth is enforced
- ✅ No real secrets in test files
Symptom: Secrets are not being redacted.
Possible Causes:
- Using
console.loginstead oflogger.log. - Secret pattern not recognized (e.g., custom key name).
- Secret is in a non-enumerable property (e.g., Symbol key).
Solutions:
- Replace all
console.logwithlogger.log. - Register custom sensitive keys:
registerSensitiveKey('my_custom_secret'). - Use
wrapConsole()to wrap the globalconsoleobject.
Symptom: Logging is slow.
Possible Causes:
- Very deeply nested objects (20+ levels).
- Very large objects (1000+ properties).
- Circular references causing repeated traversal.
Solutions:
- Reduce object nesting depth.
- Log only relevant properties (not entire objects).
- Use
logger.debugfor verbose logs (can be disabled in production).
Symptom: Stellar public keys (G...) are being redacted.
Possible Causes:
- Public key is associated with a sensitive key name (e.g.,
secret). - Public key is in a string that also contains a secret seed.
Solutions:
- Use a non-sensitive key name (e.g.,
publicKeyinstead ofsecret). - Log public keys separately from secret seeds.
| File | Purpose |
|---|---|
src/redact.ts |
Core redaction utility (regex patterns, deep traversal) |
src/redact.test.ts |
Comprehensive test suite (95%+ coverage) |
src/logger.ts |
Centralized logger with automatic redaction |
docs/backend/logging-security.md |
This document |