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) } )