From 1e3de32c515d83e6153d061707a5a99c81eba313 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Sat, 7 Mar 2026 09:34:20 -0500 Subject: [PATCH] security(H2): validate IPC dispense input from renderer Add input validation to hal:dispense IPC handler: - Reject non-array or empty amounts - Validate denomination and count are numbers - Reject non-positive or non-integer counts - Verify denomination exists in loaded cassettes - Verify requested count does not exceed available inventory Prevents a compromised renderer from sending crafted dispense requests (negative counts, unknown denominations, over-capacity). Co-Authored-By: Claude Opus 4.6 --- apps/machine/electron/main.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apps/machine/electron/main.ts b/apps/machine/electron/main.ts index 00b0ecc..e3085d3 100644 --- a/apps/machine/electron/main.ts +++ b/apps/machine/electron/main.ts @@ -204,6 +204,32 @@ ipcMain.handle( 'hal:dispense', async (_event, amounts: { denomination: number; count: number }[]) => { if (!halInstance) throw new Error('HAL not initialized') + + // Validate input from renderer (untrusted) + if (!Array.isArray(amounts) || amounts.length === 0) { + throw new Error('Invalid dispense request: amounts must be a non-empty array') + } + + const inventory = halInstance.getInventory() + for (const item of amounts) { + if (typeof item.denomination !== 'number' || typeof item.count !== 'number') { + throw new Error('Invalid dispense request: denomination and count must be numbers') + } + if (!Number.isInteger(item.count) || item.count <= 0) { + throw new Error( + `Invalid count for denomination ${item.denomination}: must be a positive integer` + ) + } + if (!(item.denomination in inventory)) { + throw new Error(`No cassette loaded with denomination: ${item.denomination}`) + } + if (item.count > (inventory[item.denomination] ?? 0)) { + throw new Error( + `Insufficient bills for denomination ${item.denomination}: requested ${item.count}, available ${inventory[item.denomination] ?? 0}` + ) + } + } + return await halInstance.dispenseCash(amounts) } )