Keep secrets off the record - secure secret handling, redaction, and memory safety utilities.
- π Extensible Redaction - Register your own patterns, don't rely on hardcoded lists
- π§΅ SecureString - Memory-safe secret wrapper preventing accidental exposure
β οΈ Safe Errors - Sanitize errors before they leak to logs or users- π Secure Buffers - Timing-safe comparison and memory zeroing utilities
- π― Zero Dependencies - Maximum portability, minimal attack surface
npm install @theunwalked/offrecordimport { getRedactor } from '@theunwalked/offrecord';
const redactor = getRedactor();
// Redact secrets from a string
const log = 'Connecting with api_key="sk-abc123xyz789" to server';
const safe = redactor.redact(log);
// Output: 'Connecting with [REDACTED] to server'
// Detect secrets without redacting
const result = redactor.detect(log);
// result.found === true
// result.matches contains detected secretsimport { getRedactor } from '@theunwalked/offrecord';
const redactor = getRedactor();
// Register a custom pattern (e.g., for your API provider)
redactor.register({
name: 'my-api',
patterns: [/myapi-[a-zA-Z0-9]{32}/g],
validator: (key) => key.startsWith('myapi-'),
envVar: 'MY_API_KEY',
description: 'My API service keys',
});import { SecureString, secure, secureFromEnv } from '@theunwalked/offrecord';
// Create a secure string
const apiKey = secure('sk-secret-key-12345');
// Safe to log - won't expose the secret
console.log(apiKey); // Output: [SecureString]
console.log(JSON.stringify({ key: apiKey })); // Output: {"key":"[SecureString]"}
// Explicitly reveal when needed
const value = apiKey.reveal();
// Use and dispose pattern
apiKey.use((secret) => {
// Use the secret
callApi(secret);
}); // Automatically disposed after use
// From environment variable
const envKey = secureFromEnv('API_KEY');import { createSafeError, withSafeErrors } from '@theunwalked/offrecord';
// Sanitize an existing error
try {
throw new Error('Failed to connect with key sk-secret123');
} catch (error) {
const safeError = createSafeError(error);
// safeError.message: 'Failed to connect with key [REDACTED]'
}
// Wrap a function to automatically sanitize errors
const safeFetch = withSafeErrors(async (url: string, apiKey: string) => {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!response.ok) {
throw new Error(`Request failed with key ${apiKey}`);
}
return response.json();
});
// Errors thrown by safeFetch will have secrets redactedoffrecord includes patterns for common secrets:
- Generic API keys and secrets
- Passwords in configuration
- Bearer tokens
- AWS credentials (Access Key ID, Secret Access Key)
- GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
- GitLab tokens (glpat-)
- Slack tokens (xox-)
- Private keys (RSA, EC)
- JSON Web Tokens (JWT)
getRedactor()- Get the global redactor instanceconfigureRedactor(config, registry?)- Configure the global redactorSecretRedactor- Class for creating custom redactor instances
getPatternRegistry()- Get the global pattern registryPatternRegistry- Class for managing patternsDEFAULT_PATTERNS- Built-in pattern definitions
SecureString- Class for wrapping secretssecure(value)- Create a SecureStringsecureFromEnv(envVar)- Create from environment variable
createSafeError(error, redactor?, options?)- Create a sanitized errorwithSafeErrors(fn, redactor?, context?)- Wrap sync functionwithSafeErrorsAsync(fn, redactor?, context?)- Wrap async functionsanitizeMessage(message, redactor?)- Sanitize a messagesanitizeStack(stack, redactor?)- Sanitize a stack trace
secureZero(buffer)- Zero out buffer contentssecureCompare(a, b)- Timing-safe string comparisonsecureCompareBuffers(a, b)- Timing-safe buffer comparisoncreateSecureBuffer(size)- Create buffer with cleanup hintstringToSecureBuffer(value)- Convert string to buffersecureBufferToString(buffer)- Read string and zero buffer
offrecord is designed to work with provider-specific libraries. Each provider registers its own patterns:
// In your provider library (e.g., execution-openai)
import { getRedactor } from '@theunwalked/offrecord';
getRedactor().register({
name: 'openai',
patterns: [/sk-[a-zA-Z0-9]{20,}/g],
validator: (key) => /^sk-(proj-)?[a-zA-Z0-9_-]{20,}$/.test(key),
envVar: 'OPENAI_API_KEY',
});SecureString provides best-effort memory protection, but JavaScript cannot guarantee secrets are cleared from memory due to:
- Non-deterministic garbage collection
- String immutability (copies may exist)
- V8 engine optimizations
For high-security applications, consider:
- Using native modules for secret handling
- Minimizing secret lifetime in memory
- Using environment variables with restricted access
Pattern-based detection cannot catch all secrets:
- Custom or unusual formats may not match
- Encoded or encrypted values won't be detected
- Context-dependent secrets (like UUIDs used as tokens) require custom patterns
Apache-2.0
TEST