Security Testing

Comprehensive security testing for cryptographic operations, data exfiltration prevention, and session secret lifecycle.

Test Categories

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.ts

1. 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

CheckSeverity
Secrets in network request bodiesCritical
Secrets in secureLogger outputHigh

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
  • lockSession clears unlocked secrets and marks session as locked
  • Subsequent exports expose null for unlockedSeed and mnemonic
  • 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