Security Testing
Comprehensive security testing for cryptographic operations, data exfiltration prevention, and session secret lifecycle.
Test Categories
- Cryptographic Security — Entropy, key generation, and crypto protocol tests
- Data Exfiltration — Network and logging leak prevention
- Session Secret Lifecycle — Memory zeroisation on lock
Running the Suite
bun run security:comprehensive
# Run individual suites:
bun run security:crypto # Cryptographic tests
bun tests/security/suites/data-exfiltration-tests.ts
bun tests/security/suites/session-secret-lifecycle-tests.ts1. Cryptographic Security Testing
Validates all cryptographic operations used in the Keythings Wallet, ensuring proper implementation, randomness quality, and protection against cryptographic attacks.
Entropy Quality Testing
Tests the quality of entropy sources used for key generation.
function entropyQualityTesting() {
const entropy = new Uint8Array(256)
crypto.getRandomValues(entropy)
// Calculate Shannon entropy
const frequencies = new Array(256).fill(0)
for (const byte of entropy) {
frequencies[byte]++
}
let shannonEntropy = 0
for (const freq of frequencies) {
if (freq > 0) {
const p = freq / entropy.length
shannonEntropy -= p * Math.log2(p)
}
}
const maxEntropy = Math.log2(256)
const score = shannonEntropy / maxEntropy
return { score, isRandom: score >= 0.8 }
}Mnemonic Security Testing
Validates BIP39 mnemonic generation, validation, and parsing security.
function mnemonicSecurityTesting() {
// Property-based testing with fast-check
fc.assert(fc.property(entropyArb, (entropy) => {
const mnemonic1 = entropyToMnemonic(entropy, english)
const mnemonic2 = entropyToMnemonic(entropy, english)
assert.equal(mnemonic1, mnemonic2) // Deterministic
assert.ok(validateMnemonic(mnemonic1, english))
}), { numRuns: 100 })
}Key Generation Testing
Ensures cryptographic keys are generated securely and uniquely.
function keyGenerationTesting() {
const generatedKeys = new Set<string>()
for (let i = 0; i < 1000; i++) {
const entropy = new Uint8Array(32)
crypto.getRandomValues(entropy)
const mnemonic = entropyToMnemonic(entropy, english)
const parsed = parseSecretInput(mnemonic)
if (generatedKeys.has(parsed.seedHex)) {
findings.push({ severity: 'critical', observation: 'Duplicate key' })
}
generatedKeys.add(parsed.seedHex)
}
}Side-Channel Attack Testing
Detects information leakage through error messages and timing.
function sideChannelAttackTesting() {
const timings = ['valid', 'invalid', 'wrong'].map(input => {
const start = performance.now()
try { validateMnemonic(input, english) } catch {}
return performance.now() - start
})
const diff = Math.max(...timings) - Math.min(...timings)
if (diff > 5) {
findings.push({ severity: 'medium', observation: 'Timing leak' })
}
}2. Data Exfiltration & Non-Custodial Assurance
Ensures the wallet never leaks mnemonics or seeds through network requests or logging, preserving non-custodial guarantees.
Network Exfiltration Test
Monkey-patches fetch to capture request bodies during session lifecycle.
async function runNetworkLeakTest() {
const capturedBodies: unknown[] = []
globalThis.fetch = (async (_input, init?: RequestInit) => {
if (init?.body) capturedBodies.push(init.body)
return new Response('ok')
}) as typeof fetch
await sessionController.saveSession(address, network, seed, mnemonic, walletId)
await sessionController.lockSession()
const asJson = JSON.stringify(capturedBodies)
assert(!/mnemonic|seed/i.test(asJson), 'No secrets in network bodies')
}Logger Exfiltration Test
Ensures secureLogger never logs mnemonics or seeds.
async function runLoggerLeakTest() {
const captured: string[] = []
const originalWarn = secureLogger.warn.bind(secureLogger)
;(secureLogger as any).warn = (...args: unknown[]) => {
captured.push(args.map((arg) => String(arg)).join(' '))
originalWarn(...args)
}
await sessionController.clearSession()
const joined = captured.join('\n')
assert(!/mnemonic|seed/i.test(joined), 'No secrets in logs')
}Vulnerability Classes
| Check | Severity |
|---|---|
| Secrets in network request bodies | Critical |
| Secrets in secureLogger output | High |
3. Session Secret Lifecycle Security
Verifies that unlocked seeds and mnemonics are cleared on lock and never persisted beyond the session.
Lock Zeroisation Test
Confirms secrets are zeroised from memory when locking the session.
async function runLockZeroisationTest() {
await sessionController.saveSession(address, network, seed, mnemonic, walletId)
const before = await sessionController.exportSecretsForSender(sender)
assert(before?.secrets.unlockedSeed, 'Seed present before lock')
await sessionController.lockSession()
const after = await sessionController.exportSecretsForSender(sender)
assert.equal(after?.secrets.unlockedSeed, null, 'Seed cleared after lock')
assert.equal(after?.secrets.mnemonic, null, 'Mnemonic cleared after lock')
}Memory Handling Requirements
- Unlocked secrets available only while session is active and unlocked
lockSessionclears unlocked secrets and marks session as locked- Subsequent exports expose
nullforunlockedSeedandmnemonic - Secrets zeroised using explicit memory clearing before lock
Test Results Example
{
"timestamp": "2025-01-15T12:00:00.000Z",
"suites": {
"cryptographic": { "status": "PASSED", "findings": 0 },
"dataExfiltration": { "status": "PASSED", "findings": 0 },
"sessionLifecycle": { "status": "PASSED", "findings": 0 }
},
"totalFindings": 0,
"securityStatus": "PASSED"
}Best Practices
- Always use
crypto.getRandomValues()for cryptographic randomness - Validate entropy quality before key generation
- Implement constant-time operations for sensitive data
- Use generic error messages to prevent information leakage
- Never log mnemonics, seeds, or derived keys
- Zeroise memory buffers after use with
buffer.fill(0) - Run security suites before each release