security: add replay protection, timestamp validation, and input checks

Addresses security audit findings for the operator command channel:

1. Replay protection: track processed management event IDs in a Set,
   reject duplicates. Caps at 1000 entries to prevent unbounded growth.

2. Timestamp validation: reject events created before machine startup
   (prevents processing stale events on relay reconnect) and events
   older than 60 seconds (limits replay window).

3. Input validation: validate bill denomination/count in
   handleManagementCommand (defense in depth — IPC path also validates
   but direct HAL path did not). Caps count at 100 per denomination.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-03-23 00:53:19 -04:00
commit 01e71b0954
2 changed files with 51 additions and 0 deletions

View file

@ -67,6 +67,23 @@ async function handleManagementCommand(
}
}
// Validate bill entries (defense in depth — IPC path also validates)
for (const bill of request.bills) {
if (typeof bill.denomination !== 'number' || typeof bill.count !== 'number') {
return { res: 'GFY', code: GFYCode.InvalidRequest, error: 'Invalid bill entry' }
}
if (!Number.isInteger(bill.count) || bill.count <= 0 || bill.count > 100) {
return { res: 'GFY', code: GFYCode.InvalidRequest, error: `Invalid count: ${bill.count}` }
}
if (!Number.isInteger(bill.denomination) || bill.denomination <= 0) {
return {
res: 'GFY',
code: GFYCode.InvalidRequest,
error: `Invalid denomination: ${bill.denomination}`,
}
}
}
try {
const result = await dispenseFn(request.bills)
const txid = `manual-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`