OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-wallet-desktop / test / input-sanitizer.cjs

98 lines · javascript ref:
// test/input-sanitizer.cjs - Tests for lib/input-sanitizer.cjs

const test = require('brittle')
const {
  sanitizeContactName,
  sanitizeDeviceName,
  sanitizeNotes,
  validateChain,
  validateNetwork,
  sanitizeHex,
  validateInviteCode
} = require('../lib/input-sanitizer.cjs')

test('sanitizeContactName enforces 100 char limit', function (t) {
  const long = 'a'.repeat(200)
  const sanitized = sanitizeContactName(long)

  t.is(sanitized.length, 100, 'truncates to 100 chars')
})

test('sanitizeContactName allows safe characters', function (t) {
  const safe = "John Doe-Smith (Main Wallet) 123"
  const sanitized = sanitizeContactName(safe)

  t.is(sanitized, safe, 'allows alphanumeric and safe chars')
})

test('sanitizeContactName removes null bytes', function (t) {
  const malicious = "name\x00injected"
  const sanitized = sanitizeContactName(malicious)

  t.absent(sanitized.includes('\x00'), 'removes null bytes')
})

test('sanitizeDeviceName enforces 50 char limit', function (t) {
  const long = 'a'.repeat(100)
  const sanitized = sanitizeDeviceName(long)

  t.is(sanitized.length, 50, 'truncates to 50 chars')
})

test('sanitizeNotes enforces 500 char limit', function (t) {
  const long = 'a'.repeat(1000)
  const sanitized = sanitizeNotes(long)

  t.is(sanitized.length, 500, 'truncates to 500 chars')
})

test('sanitizeNotes blocks dangerous characters', function (t) {
  const dangerous = "note with <script>alert('xss')</script> tags"
  const sanitized = sanitizeNotes(dangerous)

  t.absent(sanitized.includes('<'), 'removes <')
  t.absent(sanitized.includes('>'), 'removes >')
})

test('validateChain whitelists bitcoin and ethereum', function (t) {
  t.ok(validateChain('bitcoin'), 'allows bitcoin')
  t.ok(validateChain('ethereum'), 'allows ethereum')
  t.absent(validateChain('litecoin'), 'rejects litecoin')
  t.absent(validateChain('bitcoin; DROP TABLE'), 'rejects injection')
})

test('validateNetwork whitelists known networks', function (t) {
  t.ok(validateNetwork('testnet'), 'allows testnet')
  t.ok(validateNetwork('mainnet'), 'allows mainnet')
  t.ok(validateNetwork('sepolia'), 'allows sepolia')
  t.absent(validateNetwork('custom'), 'rejects custom')
})

test('sanitizeHex allows only hex characters', function (t) {
  const validHex = 'abcdef0123456789'
  const sanitized = sanitizeHex(validHex)

  t.is(sanitized, validHex, 'allows valid hex')

  const invalid = 'abcdefGHIJKL'
  const sanitized2 = sanitizeHex(invalid)

  t.is(sanitized2, 'abcdef', 'removes non-hex')
})

test('validateInviteCode enforces length limits', function (t) {
  const tooShort = 'a'.repeat(40)
  const justRight = 'a'.repeat(100)
  const tooLong = 'a'.repeat(1500)

  t.absent(validateInviteCode(tooShort), 'rejects too short')
  t.ok(validateInviteCode(justRight), 'allows valid length')
  t.absent(validateInviteCode(tooLong), 'rejects too long')
})

test('all sanitizers reject non-string inputs', function (t) {
  t.exception(() => sanitizeContactName(123), 'contact name rejects number')
  t.exception(() => sanitizeDeviceName(null), 'device name rejects null')
  t.exception(() => sanitizeNotes(undefined), 'notes rejects undefined')
})