Two hours a day. That’s how long a commercial defrost cycle normally runs. Attackers across Europe changed that number to “always,” set the target to 50 degrees Celsius, and somewhere between 250 and 350 supermarkets woke up to a wave of heat pouring out of their freezer aisles.
The affected names are ordinary ones: Intermarché, Coopérative U, Carrefour Market, Monoprix, Leclerc, Super U. Investigators believe the attackers went after refrigeration management systems from the Danish manufacturer Danfoss, whose controllers are common in commercial refrigeration. The result was mass food loss, real financial damage, and fires at several stores. One photo making the rounds shows a frozen food aisle near Nantes sealed off entirely.
I build bots for a living. Mostly software ones, but increasingly the kind that touch something physical — a thermostat, a pump, a relay, a door. This story has been sitting in my head for days because it is not really a story about refrigerators. It’s a story about what happens when a control loop accepts an instruction it should have refused.
The attack was a feature call
Nothing here required exotic exploitation of the physics. Automatic defrosting is a legitimate, necessary function. Every freezer needs to shed ice. The attackers didn’t break refrigeration; they used it, at a duty cycle and setpoint no sane operator would ever configure.
That distinction matters enormously for anyone writing automation. If your bot exposes a function, an attacker who reaches your command channel doesn’t need a memory corruption bug. They need your API. And your API, in most of the projects I’ve reviewed, will cheerfully accept any value that parses.
I’ve written that code. A `setTemperature(value)` endpoint that validates the JSON shape and nothing else. A scheduler that accepts a cron expression without asking whether `* * * * *` makes sense for the thing it’s driving. A retry loop that re-sends a command forever because the acknowledgment never came. Each of those is a continuous 50-degree defrost cycle waiting for someone to ask for it.
What I’m changing in my own code
The lesson I’m taking isn’t “patch your controllers,” though obviously patch your controllers. It’s that the bot layer — the automation sitting above the hardware — is the last place where an absurd instruction can be caught before it becomes heat, water, and smoke.
Clamp at the actuator, not the UI
Input validation in the dashboard is a usability feature. The clamp that actually protects you has to live as close to the hardware as possible, ideally in firmware that nothing on the network can rewrite. If 50 degrees is never a valid defrost target, the device itself should reject 50 degrees, no matter who asks or what credentials they present.
Budget your duty cycles
Most physical automation has an implicit time budget that nobody writes down. Defrost: two hours a day. Irrigation: twenty minutes. Door unlock: five seconds. Encode those budgets explicitly and have the controller refuse to exceed them in a rolling window. A bot that physically cannot run a function more than N minutes per day is immune to “set it to continuous,” regardless of how thoroughly its command channel is compromised.
Treat commands as untrusted, always
I used to think of my internal message bus as a trusted zone. That was lazy. Sign commands, validate them at the consumer, and log every state transition with its origin. When something goes wrong at 3 a.m. across hundreds of sites, the question “where did this instruction come from” needs an answer you can query, not reconstruct.
Alert on physics, not on errors
This is the one that stings. A fleet of freezers warming to 50 degrees is an extraordinary telemetry signal. Temperature climbing in parallel across hundreds of sites is not a subtle anomaly; it is the loudest thing a monitoring system could possibly see. Error-based alerting would have stayed quiet, because nothing errored. Everything did exactly what it was told.
So build detectors for implausible reality. Rate of change outside normal bounds. Correlated movement across sites that shouldn’t correlate. A setpoint that drifted outside its historical range. These are cheap to compute and they catch the class of attack that uses your own features against you.
Scale is the multiplier
One misconfigured freezer is a maintenance ticket. The same misconfiguration pushed to hundreds of stores is a national news event with fires attached. Fleet management is what turned a bad setpoint into this.
If you’re building bots that fan out to many devices, your rollout path needs the same scrutiny as your command validation: staged deployment, canary devices, automatic halt when canaries report something physically implausible, and a kill switch that reverts to safe local defaults without needing the network to cooperate.
The uncomfortable part is how familiar all of this feels. I’ve shipped automation with none of these guardrails, told myself the command channel was safe, and moved on. Several hundred supermarkets just paid for that assumption on everyone’s behalf.
🕒 Published: